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.servlet;
014
015import static java.util.logging.Level.*;
016import static jakarta.servlet.http.HttpServletResponse.*;
017import static org.apache.juneau.common.internal.StringUtils.*;
018import static org.apache.juneau.internal.ClassUtils.*;
019
020import java.io.*;
021import java.text.*;
022import java.util.concurrent.atomic.*;
023import java.util.function.*;
024import java.util.logging.*;
025
026import jakarta.servlet.*;
027import jakarta.servlet.http.*;
028
029import org.apache.juneau.reflect.*;
030import org.apache.juneau.rest.*;
031import org.apache.juneau.rest.annotation.*;
032import org.apache.juneau.*;
033import org.apache.juneau.common.internal.*;
034import org.apache.juneau.http.response.*;
035
036/**
037 * Servlet implementation of a REST resource.
038 *
039 * <p>
040 *    The {@link RestServlet} class is the entry point for your REST resources.
041 *    It extends directly from <l>HttpServlet</l> and is deployed like any other servlet.
042 * </p>
043 * <p>
044 *    When the servlet <l>init()</l> method is called, it triggers the code to find and process the <l>@Rest</l>
045 *    annotations on that class and all child classes.
046 *    These get constructed into a {@link RestContext} object that holds all the configuration
047 *    information about your resource in a read-only object.
048 * </p>
049 *
050 * <h5 class='section'>Notes:</h5><ul>
051 *    <li class='note'>
052 *       Users will typically extend from {@link BasicRestServlet} or {@link BasicRestServletGroup}
053 *       instead of this class directly.
054 * </ul>
055 *
056 * <h5 class='section'>See Also:</h5><ul>
057 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.AnnotatedClasses">@Rest-Annotated Classes</a>
058 * </ul>
059 *
060 * @serial exclude
061 */
062public abstract class RestServlet extends HttpServlet {
063
064   private static final long serialVersionUID = 1L;
065
066   private AtomicReference<RestContext> context = new AtomicReference<>();
067   private AtomicReference<Exception> initException = new AtomicReference<>();
068
069   @Override /* Servlet */
070   public synchronized void init(ServletConfig servletConfig) throws ServletException {
071      try {
072         if (context.get() != null)
073            return;
074         super.init(servletConfig);
075         context.set(RestContext.create(this.getClass(), null, servletConfig).init(()->this).build());
076         context.get().postInit();
077         context.get().postInitChildFirst();
078      } catch (ServletException e) {
079         initException.set(e);
080         log(SEVERE, e, "Servlet init error on class ''{0}''", className(this));
081         throw e;
082      } catch (BasicHttpException e) {
083         initException.set(e);
084         log(SEVERE, e, "Servlet init error on class ''{0}''", className(this));
085      } catch (Throwable e) {
086         initException.set(new InternalServerError(e));
087         log(SEVERE, e, "Servlet init error on class ''{0}''", className(this));
088      }
089   }
090
091   /**
092    * Sets the context object for this servlet.
093    *
094    * <p>
095    * This method is effectively a no-op if {@link #init(ServletConfig)} has already been called.
096    *
097    * @param context Sets the context object on this servlet.
098    * @throws ServletException If error occurred during initialization.
099    */
100   protected void setContext(RestContext context) throws ServletException {
101      if (this.context.get() == null) {
102         super.init(context.getBuilder());
103         this.context.set(context);
104      }
105   }
106
107   /**
108    * Returns the path for this resource as defined by the @Rest(path) annotation or RestContext.Builder.path(String) method
109    * concatenated with those on all parent classes.
110    *
111    * @return The path defined on this servlet, or an empty string if not specified.
112    */
113   public synchronized String getPath() {
114      RestContext context = this.context.get();
115      if (context != null)
116         return context.getFullPath();
117      ClassInfo ci = ClassInfo.of(getClass());
118      Value<String> path = Value.empty();
119      ci.forEachAnnotation(Rest.class, x -> isNotEmpty(x.path()), x -> path.set(trimSlashes(x.path())));
120      return path.orElse("");
121   }
122
123   //-----------------------------------------------------------------------------------------------------------------
124   // Lifecycle methods
125   //-----------------------------------------------------------------------------------------------------------------
126
127   /**
128    * The main service method.
129    *
130    * <p>
131    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
132    */
133   @Override /* Servlet */
134   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
135      try {
136         if (initException.get() != null)
137            throw initException.get();
138         if (context.get() == null)
139            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).", className(this));
140         getContext().execute(this, r1, r2);
141
142      } catch (Throwable e) {
143         r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
144      }
145   }
146
147   @Override /* GenericServlet */
148   public synchronized void destroy() {
149      if (context.get() != null)
150         context.get().destroy();
151      super.destroy();
152   }
153
154   //-----------------------------------------------------------------------------------------------------------------
155   // Context methods.
156   //-----------------------------------------------------------------------------------------------------------------
157
158   /**
159    * Returns the read-only context object that contains all the configuration information about this resource.
160    *
161    * <p>
162    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
163    * {@link #init()} is called.
164    *
165    * <p>
166    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
167    * access to this context object:
168    * <p class='bjava'>
169    *    <jk>public void</jk> init(RestServletContext <jv>context</jv>) <jk>throws</jk> Exception;
170    * </p>
171    *
172    * @return The context information on this servlet.
173    */
174   public synchronized RestContext getContext() {
175      RestContext rc = context.get();
176      if (rc == null)
177         throw new InternalServerError("RestContext object not set on resource.");
178      return rc;
179   }
180
181   //-----------------------------------------------------------------------------------------------------------------
182   // Convenience logger methods
183   //-----------------------------------------------------------------------------------------------------------------
184
185   /**
186    * Log a message.
187    *
188    * <p>
189    * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}.
190    *
191    * @param level The log level.
192    * @param msg The message to log.
193    * @param args Optional {@link MessageFormat}-style arguments.
194    */
195   public void log(Level level, String msg, Object...args) {
196      doLog(level, null, () -> StringUtils.format(msg, args));
197   }
198
199   /**
200    * Log a message.
201    *
202    * <p>
203    * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}.
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      doLog(level, cause, () -> StringUtils.format(msg, args));
212   }
213
214   /**
215    * Main logger method.
216    *
217    * <p>
218    * The default behavior logs a message to the Java logger of the class name.
219    *
220    * <p>
221    * Subclasses can override this method to implement their own logger handling.
222    *
223    * @param level The log level.
224    * @param cause Optional throwable.
225    * @param msg The message to log.
226    */
227   protected void doLog(Level level, Throwable cause, Supplier<String> msg) {
228      RestContext c = context.get();
229      Logger logger = c == null ? null : c.getLogger();
230      if (logger == null)
231         logger = Logger.getLogger(className(this));
232      logger.log(level, cause, msg);
233   }
234
235   //-----------------------------------------------------------------------------------------------------------------
236   // Other methods
237   //-----------------------------------------------------------------------------------------------------------------
238
239   /**
240    * Returns the current thread-local HTTP request.
241    *
242    * @return The current thread-local HTTP request, or <jk>null</jk> if it wasn't created.
243    */
244   public synchronized RestRequest getRequest() {
245      return getContext().getLocalSession().getOpSession().getRequest();
246   }
247
248   /**
249    * Returns the current thread-local HTTP response.
250    *
251    * @return The current thread-local HTTP response, or <jk>null</jk> if it wasn't created.
252    */
253   public synchronized RestResponse getResponse() {
254      return getContext().getLocalSession().getOpSession().getResponse();
255   }
256}