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}