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