001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.servlet;
018
019import static jakarta.servlet.http.HttpServletResponse.*;
020import static java.util.logging.Level.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.io.*;
026import java.text.*;
027import java.util.concurrent.atomic.*;
028import java.util.function.*;
029import java.util.logging.*;
030
031import org.apache.juneau.commons.reflect.*;
032import org.apache.juneau.commons.utils.*;
033import org.apache.juneau.http.response.*;
034import org.apache.juneau.rest.*;
035import org.apache.juneau.rest.annotation.*;
036
037import jakarta.servlet.*;
038import jakarta.servlet.http.*;
039
040/**
041 * Servlet implementation of a REST resource.
042 *
043 * <p>
044 *    The {@link RestServlet} class is the entry point for your REST resources.
045 *    It extends directly from <l>HttpServlet</l> and is deployed like any other servlet.
046 * </p>
047 * <p>
048 *    When the servlet <l>init()</l> method is called, it triggers the code to find and process the <l>@Rest</l>
049 *    annotations on that class and all child classes.
050 *    These get constructed into a {@link RestContext} object that holds all the configuration
051 *    information about your resource in a read-only object.
052 * </p>
053 *
054 * <h5 class='section'>Notes:</h5><ul>
055 *    <li class='note'>
056 *       Users will typically extend from {@link BasicRestServlet} or {@link BasicRestServletGroup}
057 *       instead of this class directly.
058 * </ul>
059 *
060 * <h5 class='section'>See Also:</h5><ul>
061 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
062 * </ul>
063 *
064 * @serial exclude
065 */
066public abstract class RestServlet extends HttpServlet {
067
068   private static final long serialVersionUID = 1L;
069   private static final AnnotationProvider AP = AnnotationProvider.INSTANCE;
070
071   private AtomicReference<RestContext> context = new AtomicReference<>();
072   private AtomicReference<Exception> initException = new AtomicReference<>();
073
074   @Override /* Overridden from GenericServlet */
075   public synchronized void destroy() {
076      if (nn(context.get()))
077         context.get().destroy();
078      super.destroy();
079   }
080
081   /**
082    * Returns the read-only context object that contains all the configuration information about this resource.
083    *
084    * <p>
085    * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time
086    * {@link #init()} is called.
087    *
088    * <p>
089    * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get
090    * access to this context object:
091    * <p class='bjava'>
092    *    <jk>public void</jk> init(RestServletContext <jv>context</jv>) <jk>throws</jk> Exception;
093    * </p>
094    *
095    * @return The context information on this servlet.
096    */
097   public synchronized RestContext getContext() {
098      var rc = context.get();
099      if (rc == null)
100         throw new InternalServerError("RestContext object not set on resource.");
101      return rc;
102   }
103
104   /**
105    * Returns the path for this resource as defined by the @Rest(path) annotation or RestContext.Builder.path(String) method
106    * concatenated with those on all parent classes.
107    *
108    * @return The path defined on this servlet, or an empty string if not specified.
109    */
110   public synchronized String getPath() {
111      var context = this.context.get();
112      if (nn(context))
113         return context.getFullPath();
114      var ci = ClassInfo.of(getClass());
115      // @formatter:off
116      return rstream(AP.find(Rest.class, ci))
117         .map(x -> x.inner().path())
118         .filter(Utils::ne)
119         .map(StringUtils::trimSlashes)
120         .findFirst()
121         .orElse("");
122      // @formatter:on
123   }
124
125   /**
126    * Returns the current thread-local HTTP request.
127    *
128    * @return The current thread-local HTTP request, or <jk>null</jk> if it wasn't created.
129    */
130   public synchronized RestRequest getRequest() { return getContext().getLocalSession().getOpSession().getRequest(); }
131
132   /**
133    * Returns the current thread-local HTTP response.
134    *
135    * @return The current thread-local HTTP response, or <jk>null</jk> if it wasn't created.
136    */
137   public synchronized RestResponse getResponse() { return getContext().getLocalSession().getOpSession().getResponse(); }
138
139   @Override /* Overridden from Servlet */
140   public synchronized void init(ServletConfig servletConfig) throws ServletException {
141      try {
142         if (nn(context.get()))
143            return;
144         super.init(servletConfig);
145         context.set(RestContext.create(this.getClass(), null, servletConfig).init(() -> this).build());
146         context.get().postInit();
147         context.get().postInitChildFirst();
148      } catch (ServletException e) {
149         initException.set(e);
150         log(SEVERE, e, "Servlet init error on class ''{0}''", cn(this));
151         throw e;
152      } catch (BasicHttpException e) {
153         initException.set(e);
154         log(SEVERE, e, "Servlet init error on class ''{0}''", cn(this));
155      } catch (Throwable e) {
156         initException.set(new InternalServerError(e));
157         log(SEVERE, e, "Servlet init error on class ''{0}''", cn(this));
158      }
159   }
160
161   /**
162    * Log a message.
163    *
164    * <p>
165    * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}.
166    *
167    * @param level The log level.
168    * @param msg The message to log.
169    * @param args Optional {@link MessageFormat}-style arguments.
170    */
171   public void log(Level level, String msg, Object...args) {
172      doLog(level, null, fs(msg, args));
173   }
174
175   /**
176    * Log a message.
177    *
178    * <p>
179    * Subclasses can intercept the handling of these messages by overriding {@link #doLog(Level, Throwable, Supplier)}.
180    *
181    * @param level The log level.
182    * @param cause The cause.
183    * @param msg The message to log.
184    * @param args Optional {@link MessageFormat}-style arguments.
185    */
186   public void log(Level level, Throwable cause, String msg, Object...args) {
187      doLog(level, cause, fs(msg, args));
188   }
189
190   /**
191    * The main service method.
192    *
193    * <p>
194    * Subclasses can optionally override this method if they want to tailor the behavior of requests.
195    */
196   @Override /* Overridden from Servlet */
197   public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException {
198      try {
199         if (nn(initException.get()))
200            throw initException.get();
201         if (context.get() == null)
202            throw new InternalServerError(
203               "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).", cn(this));
204         getContext().execute(this, r1, r2);
205
206      } catch (Throwable e) {
207         r2.sendError(SC_INTERNAL_SERVER_ERROR, lm(e));
208      }
209   }
210
211   /**
212    * Main logger method.
213    *
214    * <p>
215    * The default behavior logs a message to the Java logger of the class name.
216    *
217    * <p>
218    * Subclasses can override this method to implement their own logger handling.
219    *
220    * @param level The log level.
221    * @param cause Optional throwable.
222    * @param msg The message to log.
223    */
224   protected void doLog(Level level, Throwable cause, Supplier<String> msg) {
225      var c = context.get();
226      var logger = c == null ? null : c.getLogger();
227      if (logger == null)
228         logger = Logger.getLogger(cn(this));
229      logger.log(level, cause, msg);
230   }
231
232   /**
233    * Sets the context object for this servlet.
234    *
235    * <p>
236    * This method is effectively a no-op if {@link #init(ServletConfig)} has already been called.
237    *
238    * @param context Sets the context object on this servlet.
239    * @throws ServletException If error occurred during initialization.
240    */
241   protected void setContext(RestContext context) throws ServletException {
242      if (this.context.get() == null) {
243         super.init(context.getBuilder());
244         this.context.set(context);
245      }
246   }
247}