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.internal.ClassUtils.*; 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.rest.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 (RestException e) { 065 // Thrown RestExceptions are simply caught and re-thrown on subsequent calls to service(). 066 initException = e; 067 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 068 } catch (ServletException e) { 069 initException = e; 070 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 071 throw e; 072 } catch (Exception e) { 073 initException = e; 074 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 075 throw new ServletException(e); 076 } catch (Throwable e) { 077 initException = new Exception(e); 078 log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); 079 throw new ServletException(e); 080 } 081 } 082 083 /* 084 * Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly. 085 * Used when subclasses of RestServlet are attached as child resources. 086 */ 087 synchronized void innerInit(ServletConfig servletConfig) throws ServletException { 088 super.init(servletConfig); 089 } 090 091 /** 092 * Sets the context object for this servlet. 093 * 094 * @param context Sets the context object on this servlet. 095 * @throws ServletException If error occurred during post-initialiation. 096 */ 097 public synchronized void setContext(RestContext context) throws ServletException { 098 this.builder = context.builder; 099 this.context = context; 100 isInitialized = true; 101 context.postInit(); 102 } 103 104 /** 105 * Sets the resource resolver to use for this servlet and all child servlets. 106 * <p> 107 * This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called. 108 * Otherwise calling this method will have no effect. 109 * 110 * @param resourceResolver The resolver instance. Can be <jk>null</jk>. 111 * @return This object (for method chaining). 112 */ 113 public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) { 114 this.resourceResolver = resourceResolver; 115 return this; 116 } 117 118 /** 119 * Returns the path defined on this servlet if it's defined via {@link RestResource#path()}. 120 * 121 * @return The path defined on this servlet, or an empty string if not specified. 122 */ 123 public synchronized String getPath() { 124 if (context != null) 125 return context.getPath(); 126 ClassInfo ci = getClassInfo(getClass()); 127 for (RestResource rr : ci.getAnnotations(RestResource.class)) 128 if (! rr.path().isEmpty()) 129 return trimSlashes(rr.path()); 130 return ""; 131 } 132 133 @Override /* GenericServlet */ 134 public synchronized RestContextBuilder getServletConfig() { 135 return builder; 136 } 137 138 /** 139 * Returns the read-only context object that contains all the configuration information about this resource. 140 * 141 * <p> 142 * This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time 143 * {@link #init()} is called. 144 * 145 * <p> 146 * Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get 147 * access to this context object: 148 * <p class='bcode w800'> 149 * <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception; 150 * </p> 151 * 152 * @return The context information on this servlet. 153 */ 154 protected synchronized RestContext getContext() { 155 return context; 156 } 157 158 159 //----------------------------------------------------------------------------------------------------------------- 160 // Other methods 161 //----------------------------------------------------------------------------------------------------------------- 162 163 /** 164 * The main service method. 165 * 166 * <p> 167 * Subclasses can optionally override this method if they want to tailor the behavior of requests. 168 */ 169 @Override /* Servlet */ 170 public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException { 171 try { 172 // To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check. 173 if (! isInitialized) { 174 if (initException != null) { 175 if (initException instanceof RestException) 176 throw (RestException)initException; 177 throw new InternalServerError(initException); 178 } 179 if (context == null) 180 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()); 181 isInitialized = true; 182 } 183 184 context.getCallHandler().service(r1, r2); 185 186 } catch (RestException e) { 187 r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); 188 } catch (Throwable e) { 189 r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); 190 } 191 } 192 193 @Override /* GenericServlet */ 194 public void log(String msg) { 195 logger.info(msg); 196 } 197 198 @Override /* GenericServlet */ 199 public void log(String msg, Throwable cause) { 200 logger.info(cause, msg); 201 } 202 203 /** 204 * Convenience method for calling <c>getContext().getLogger().log(level, msg, args);</c> 205 * 206 * @param level The log level. 207 * @param msg The message to log. 208 * @param args Optional {@link MessageFormat}-style arguments. 209 */ 210 public void log(Level level, String msg, Object...args) { 211 logger.log(level, msg, args); 212 } 213 214 /** 215 * Convenience method for calling <c>getContext().getLogger().logObjects(level, msg, args);</c> 216 * 217 * @param level The log level. 218 * @param msg The message to log. 219 * @param args Optional {@link MessageFormat}-style arguments. 220 */ 221 public void logObjects(Level level, String msg, Object...args) { 222 logger.logObjects(level, msg, args); 223 } 224 225 /** 226 * Convenience method for calling <c>getContext().getLogger().log(level, cause, msg, args);</c> 227 * 228 * @param level The log level. 229 * @param cause The cause. 230 * @param msg The message to log. 231 * @param args Optional {@link MessageFormat}-style arguments. 232 */ 233 public void log(Level level, Throwable cause, String msg, Object...args) { 234 logger.log(level, cause, msg, args); 235 } 236 237 /** 238 * Returns the current HTTP request. 239 * 240 * @return The current HTTP request, or <jk>null</jk> if it wasn't created. 241 */ 242 public RestRequest getRequest() { 243 if (context == null) 244 return null; 245 return context.getRequest(); 246 } 247 248 /** 249 * Returns the current HTTP response. 250 * 251 * @return The current HTTP response, or <jk>null</jk> if it wasn't created. 252 */ 253 public RestResponse getResponse() { 254 if (context == null) 255 return null; 256 return context.getResponse(); 257 } 258 259 @Override /* GenericServlet */ 260 public synchronized void destroy() { 261 if (context != null) 262 context.destroy(); 263 super.destroy(); 264 } 265 266 /** 267 * Convenience method for calling <c>getContext().getProperties();</c> 268 * 269 * @return The resource properties as an {@link RestContextProperties}. 270 * @see RestContext#getProperties() 271 */ 272 public RestContextProperties getProperties() { 273 return getContext().getProperties(); 274 } 275}