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}