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 boolean isInitialized = false;
043   private Exception initException;
044
045
046   @Override /* Servlet */
047   public final synchronized void init(ServletConfig servletConfig) throws ServletException {
048      try {
049         builder = RestContext.create(servletConfig, this.getClass(), null).init(this);
050         super.init(servletConfig);
051         if (! isInitialized) {
052            builder.servletContext(this.getServletContext());
053            context = builder.build();
054            isInitialized = true;
055         }
056         context.postInit();
057         context.postInitChildFirst();
058      } catch (RestException e) {
059         // Thrown RestExceptions are simply caught and re-thrown on subsequent calls to service().
060         initException = e;
061         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
062      } catch (ServletException e) {
063         initException = e;
064         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
065         throw e;
066      } catch (Exception e) {
067         initException = e;
068         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
069         throw new ServletException(e);
070      } catch (Throwable e) {
071         initException = new Exception(e);
072         log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
073         throw new ServletException(e);
074      } finally {
075         isInitialized = true;
076      }
077   }
078
079   /*
080    * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly.
081    * Used when subclasses of RestServlet are attached as child resources.
082    */
083   synchronized void innerInit(ServletConfig servletConfig) throws ServletException {
084      super.init(servletConfig);
085   }
086
087   /*
088    * Sets the context object for this servlet.
089    * Used when subclasses of RestServlet are attached as child resources.
090    */
091   synchronized void setContext(RestContext context) {
092      this.builder = context.builder;
093      this.context = context;
094   }
095
096   @Override /* GenericServlet */
097   public synchronized RestContextBuilder getServletConfig() {
098      return builder;
099   }
100
101   /**
102    * Returns the read-only context object that contains all the configuration information about this resource.
103    *
104    * <p>
105    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
106    * {@link #init()} is called.
107    *
108    * <p>
109    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
110    * access to this context object:
111    * <p class='bcode w800'>
112    *    <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
113    * </p>
114    *
115    * @return The context information on this servlet.
116    */
117   protected synchronized RestContext getContext() {
118      return context;
119   }
120
121
122   //-----------------------------------------------------------------------------------------------------------------
123   // Other methods
124   //-----------------------------------------------------------------------------------------------------------------
125
126   /**
127    * The main service method.
128    *
129    * <p>
130    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
131    */
132   @Override /* Servlet */
133   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
134      try {
135         if (initException != null) {
136            if (initException instanceof RestException)
137               throw (RestException)initException;
138            throw new InternalServerError(initException);
139         }
140         if (context == null)
141            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());
142         if (! isInitialized)
143            throw new InternalServerError("Servlet {0} has not been initialized", getClass().getName());
144
145         context.getCallHandler().service(r1, r2);
146
147      } catch (RestException e) {
148         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
149      } catch (Throwable e) {
150         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
151      }
152   }
153
154   @Override /* GenericServlet */
155   public void log(String msg) {
156      if (context != null)
157         context.getLogger().log(INFO, msg);
158   }
159
160   @Override /* GenericServlet */
161   public void log(String msg, Throwable cause) {
162      if (context != null)
163         context.getLogger().log(INFO, cause, msg);
164   }
165
166   /**
167    * Convenience method for calling <code>getContext().getLogger().log(level, msg, args);</code>
168    *
169    * @param level The log level.
170    * @param msg The message to log.
171    * @param args Optional {@link MessageFormat}-style arguments.
172    */
173   public void log(Level level, String msg, Object...args) {
174      if (context != null)
175         context.getLogger().log(level, msg, args);
176   }
177
178   /**
179    * Convenience method for calling <code>getContext().getLogger().logObjects(level, msg, args);</code>
180    *
181    * @param level The log level.
182    * @param msg The message to log.
183    * @param args Optional {@link MessageFormat}-style arguments.
184    */
185   public void logObjects(Level level, String msg, Object...args) {
186      if (context != null)
187         context.getLogger().logObjects(level, msg, args);
188   }
189
190   /**
191    * Convenience method for calling <code>getContext().getLogger().log(level, cause, msg, args);</code>
192    *
193    * @param level The log level.
194    * @param cause The cause.
195    * @param msg The message to log.
196    * @param args Optional {@link MessageFormat}-style arguments.
197    */
198   public void log(Level level, Throwable cause, String msg, Object...args) {
199      if (context != null)
200         context.getLogger().log(level, cause, msg, args);
201      else {
202         // If context failed to initialize, log to the console.
203         System.err.println(format(msg, args));
204         if (cause != null)
205            cause.printStackTrace();
206      }
207   }
208
209   @Override /* GenericServlet */
210   public synchronized void destroy() {
211      if (context != null)
212         context.destroy();
213      super.destroy();
214   }
215}