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}