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