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; 014 015import static java.util.logging.Level.*; 016import static javax.servlet.http.HttpServletResponse.*; 017import static org.apache.juneau.internal.StringUtils.*; 018import static org.apache.juneau.rest.HttpRuntimeException.*; 019 020import java.io.*; 021import java.text.*; 022import java.util.logging.*; 023 024import javax.servlet.*; 025import javax.servlet.http.*; 026 027import org.apache.juneau.internal.*; 028import org.apache.juneau.reflect.*; 029import org.apache.juneau.rest.annotation.*; 030import org.apache.juneau.http.exception.*; 031 032/** 033 * Servlet implementation of a REST resource. 034 * 035 * <ul class='seealso'> 036 * <li class='link'>{@doc juneau-rest-server.Instantiation.RestServlet} 037 * </ul> 038 */ 039public abstract class RestServlet extends HttpServlet { 040 041 private static final long serialVersionUID = 1L; 042 043 private RestContextBuilder builder; 044 private volatile RestContext context; 045 private volatile Exception initException; 046 private boolean isInitialized = false; // Should not be volatile. 047 private volatile RestResourceResolver resourceResolver; 048 private JuneauLogger logger = JuneauLogger.getLogger(getClass()); 049 050 051 @Override /* Servlet */ 052 public final synchronized void init(ServletConfig servletConfig) throws ServletException { 053 try { 054 if (context != null) 055 return; 056 builder = RestContext.create(servletConfig, this.getClass(), null); 057 if (resourceResolver != null) 058 builder.resourceResolver(resourceResolver); 059 builder.init(this); 060 super.init(servletConfig); 061 builder.servletContext(this.getServletContext()); 062 setContext(builder.build()); 063 context.postInitChildFirst(); 064 } catch (ServletException e) { 065 initException = e; 066 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 067 throw e; 068 } catch (Throwable e) { 069 initException = toHttpException(e, InternalServerError.class); 070 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 071 } 072 } 073 074 /* 075 * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly. 076 * Used when subclasses of RestServlet are attached as child resources. 077 */ 078 synchronized void innerInit(ServletConfig servletConfig) throws ServletException { 079 super.init(servletConfig); 080 } 081 082 /** 083 * Sets the context object for this servlet. 084 * 085 * @param context Sets the context object on this servlet. 086 * @throws ServletException If error occurred during post-initialiation. 087 */ 088 public synchronized void setContext(RestContext context) throws ServletException { 089 this.builder = context.builder; 090 this.context = context; 091 isInitialized = true; 092 context.postInit(); 093 } 094 095 /** 096 * Sets the resource resolver to use for this servlet and all child servlets. 097 * <p> 098 * This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called. 099 * Otherwise calling this method will have no effect. 100 * 101 * @param resourceResolver The resolver instance. Can be <jk>null</jk>. 102 * @return This object (for method chaining). 103 */ 104 public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) { 105 this.resourceResolver = resourceResolver; 106 return this; 107 } 108 109 /** 110 * Returns the path defined on this servlet if it's defined via {@link RestResource#path()}. 111 * 112 * @return The path defined on this servlet, or an empty string if not specified. 113 */ 114 public synchronized String getPath() { 115 if (context != null) 116 return context.getPath(); 117 ClassInfo ci = ClassInfo.of(getClass()); 118 for (RestResource rr : ci.getAnnotations(RestResource.class)) 119 if (! rr.path().isEmpty()) 120 return trimSlashes(rr.path()); 121 return ""; 122 } 123 124 @Override /* GenericServlet */ 125 public synchronized RestContextBuilder getServletConfig() { 126 return builder; 127 } 128 129 //----------------------------------------------------------------------------------------------------------------- 130 // Context methods. 131 //----------------------------------------------------------------------------------------------------------------- 132 133 /** 134 * Returns the read-only context object that contains all the configuration information about this resource. 135 * 136 * <p> 137 * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time 138 * {@link #init()} is called. 139 * 140 * <p> 141 * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get 142 * access to this context object: 143 * <p class='bcode w800'> 144 * <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception; 145 * </p> 146 * 147 * @return The context information on this servlet. 148 */ 149 protected synchronized RestContext getContext() { 150 if (context == null) 151 throw new InternalServerError("RestContext object not set on resource."); 152 return context; 153 } 154 155 /** 156 * Convenience method for calling <c>getContext().getProperties();</c> 157 * 158 * @return The resource properties as an {@link RestContextProperties}. 159 * @see RestContext#getProperties() 160 */ 161 public RestContextProperties getProperties() { 162 return getContext().getProperties(); 163 } 164 165 166 //----------------------------------------------------------------------------------------------------------------- 167 // Convenience logger methods 168 //----------------------------------------------------------------------------------------------------------------- 169 170 @Override /* GenericServlet */ 171 public void log(String msg) { 172 logger.info(msg); 173 } 174 175 @Override /* GenericServlet */ 176 public void log(String msg, Throwable cause) { 177 logger.info(cause, msg); 178 } 179 180 /** 181 * Log a message. 182 * 183 * @param level The log level. 184 * @param msg The message to log. 185 * @param args Optional {@link MessageFormat}-style arguments. 186 */ 187 public void log(Level level, String msg, Object...args) { 188 logger.log(level, msg, args); 189 } 190 191 /** 192 * Log a message. 193 * 194 * @param level The log level. 195 * @param msg The message to log. 196 * @param args Optional {@link MessageFormat}-style arguments. 197 */ 198 public void logObjects(Level level, String msg, Object...args) { 199 logger.logObjects(level, msg, args); 200 } 201 202 /** 203 * Log a message. 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 logger.log(level, cause, msg, args); 212 } 213 214 //----------------------------------------------------------------------------------------------------------------- 215 // Lifecycle methods 216 //----------------------------------------------------------------------------------------------------------------- 217 218 /** 219 * The main service method. 220 * 221 * <p> 222 * Subclasses can optionally override this method if they want to tailor the behavior of requests. 223 */ 224 @Override /* Servlet */ 225 public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException { 226 try { 227 // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check. 228 if (! isInitialized) { 229 if (initException != null) 230 throw initException; 231 if (context == null) 232 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).", getClass().getName()); 233 isInitialized = true; 234 } 235 236 context.getCallHandler().service(r1, r2); 237 238 } catch (Throwable e) { 239 r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); 240 } 241 } 242 243 @Override /* GenericServlet */ 244 public synchronized void destroy() { 245 if (context != null) 246 context.destroy(); 247 super.destroy(); 248 } 249 250 //----------------------------------------------------------------------------------------------------------------- 251 // Request-time methods. 252 //----------------------------------------------------------------------------------------------------------------- 253 254 /** 255 * Returns the current HTTP request. 256 * 257 * @return The current HTTP request, or <jk>null</jk> if it wasn't created. 258 */ 259 public synchronized RestRequest getRequest() { 260 return getContext().getRequest(); 261 } 262 263 /** 264 * Returns the current HTTP response. 265 * 266 * @return The current HTTP response, or <jk>null</jk> if it wasn't created. 267 */ 268 public synchronized RestResponse getResponse() { 269 return getContext().getResponse(); 270 } 271}