// *************************************************************************************************************************** | |
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * | |
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * | |
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * | |
// * with the License. You may obtain a copy of the License at * | |
// * * | |
// * http://www.apache.org/licenses/LICENSE-2.0 * | |
// * * | |
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * | |
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * | |
// * specific language governing permissions and limitations under the License. * | |
// *************************************************************************************************************************** | |
package org.apache.juneau.rest; | |
import static java.util.logging.Level.*; | |
import static javax.servlet.http.HttpServletResponse.*; | |
import static org.apache.juneau.internal.StringUtils.*; | |
import static org.apache.juneau.rest.HttpRuntimeException.*; | |
import java.io.*; | |
import java.text.*; | |
import java.util.logging.*; | |
import javax.servlet.*; | |
import javax.servlet.http.*; | |
import org.apache.juneau.internal.*; | |
import org.apache.juneau.reflect.*; | |
import org.apache.juneau.rest.annotation.*; | |
import org.apache.juneau.http.exception.*; | |
/** | |
* Servlet implementation of a REST resource. | |
* | |
* <ul class='seealso'> | |
* <li class='link'>{@doc juneau-rest-server.Instantiation.RestServlet} | |
* </ul> | |
*/ | |
public abstract class RestServlet extends HttpServlet { | |
private static final long serialVersionUID = 1L; | |
private RestContextBuilder builder; | |
private volatile RestContext context; | |
private volatile Exception initException; | |
private boolean isInitialized = false; // Should not be volatile. | |
private volatile RestResourceResolver resourceResolver; | |
private JuneauLogger logger = JuneauLogger.getLogger(getClass()); | |
@Override /* Servlet */ | |
public final synchronized void init(ServletConfig servletConfig) throws ServletException { | |
try { | |
if (context != null) | |
return; | |
builder = RestContext.create(servletConfig, this.getClass(), null); | |
if (resourceResolver != null) | |
builder.resourceResolver(resourceResolver); | |
builder.init(this); | |
super.init(servletConfig); | |
builder.servletContext(this.getServletContext()); | |
setContext(builder.build()); | |
context.postInitChildFirst(); | |
} catch (ServletException e) { | |
initException = e; | |
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); | |
throw e; | |
} catch (Throwable e) { | |
initException = toHttpException(e, InternalServerError.class); | |
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName()); | |
} | |
} | |
/* | |
* Bypasses the init(ServletConfig) method and just calls the super.init(ServletConfig) method directly. | |
* Used when subclasses of RestServlet are attached as child resources. | |
*/ | |
synchronized void innerInit(ServletConfig servletConfig) throws ServletException { | |
super.init(servletConfig); | |
} | |
/** | |
* Sets the context object for this servlet. | |
* | |
* @param context Sets the context object on this servlet. | |
* @throws ServletException If error occurred during post-initialiation. | |
*/ | |
public synchronized void setContext(RestContext context) throws ServletException { | |
this.builder = context.builder; | |
this.context = context; | |
isInitialized = true; | |
context.postInit(); | |
} | |
/** | |
* Sets the resource resolver to use for this servlet and all child servlets. | |
* <p> | |
* This method can be called immediately following object construction, but must be called before {@link #init(ServletConfig)} is called. | |
* Otherwise calling this method will have no effect. | |
* | |
* @param resourceResolver The resolver instance. Can be <jk>null</jk>. | |
* @return This object (for method chaining). | |
*/ | |
public synchronized RestServlet setRestResourceResolver(RestResourceResolver resourceResolver) { | |
this.resourceResolver = resourceResolver; | |
return this; | |
} | |
/** | |
* Returns the path defined on this servlet if it's defined via {@link Rest#path()}. | |
* | |
* @return The path defined on this servlet, or an empty string if not specified. | |
*/ | |
@SuppressWarnings("deprecation") | |
public synchronized String getPath() { | |
if (context != null) | |
return context.getPath(); | |
ClassInfo ci = ClassInfo.of(getClass()); | |
for (Rest rr : ci.getAnnotations(Rest.class)) | |
if (! rr.path().isEmpty()) | |
return trimSlashes(rr.path()); | |
for (RestResource rr : ci.getAnnotations(RestResource.class)) | |
if (! rr.path().isEmpty()) | |
return trimSlashes(rr.path()); | |
return ""; | |
} | |
@Override /* GenericServlet */ | |
public synchronized RestContextBuilder getServletConfig() { | |
return builder; | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Context methods. | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* Returns the read-only context object that contains all the configuration information about this resource. | |
* | |
* <p> | |
* This object is <jk>null</jk> during the call to {@link #init(ServletConfig)} but is populated by the time | |
* {@link #init()} is called. | |
* | |
* <p> | |
* Resource classes that don't extend from {@link RestServlet} can add the following method to their class to get | |
* access to this context object: | |
* <p class='bcode w800'> | |
* <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception; | |
* </p> | |
* | |
* @return The context information on this servlet. | |
*/ | |
protected synchronized RestContext getContext() { | |
if (context == null) | |
throw new InternalServerError("RestContext object not set on resource."); | |
return context; | |
} | |
/** | |
* Convenience method for calling <c>getContext().getProperties();</c> | |
* | |
* @return The resource properties as an {@link RestContextProperties}. | |
* @see RestContext#getProperties() | |
*/ | |
public RestContextProperties getProperties() { | |
return getContext().getProperties(); | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Convenience logger methods | |
//----------------------------------------------------------------------------------------------------------------- | |
@Override /* GenericServlet */ | |
public void log(String msg) { | |
logger.info(msg); | |
} | |
@Override /* GenericServlet */ | |
public void log(String msg, Throwable cause) { | |
logger.info(cause, msg); | |
} | |
/** | |
* Log a message. | |
* | |
* @param level The log level. | |
* @param msg The message to log. | |
* @param args Optional {@link MessageFormat}-style arguments. | |
*/ | |
public void log(Level level, String msg, Object...args) { | |
logger.log(level, msg, args); | |
} | |
/** | |
* Log a message. | |
* | |
* @param level The log level. | |
* @param msg The message to log. | |
* @param args Optional {@link MessageFormat}-style arguments. | |
*/ | |
public void logObjects(Level level, String msg, Object...args) { | |
logger.logObjects(level, msg, args); | |
} | |
/** | |
* Log a message. | |
* | |
* @param level The log level. | |
* @param cause The cause. | |
* @param msg The message to log. | |
* @param args Optional {@link MessageFormat}-style arguments. | |
*/ | |
public void log(Level level, Throwable cause, String msg, Object...args) { | |
logger.log(level, cause, msg, args); | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Lifecycle methods | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* The main service method. | |
* | |
* <p> | |
* Subclasses can optionally override this method if they want to tailor the behavior of requests. | |
*/ | |
@Override /* Servlet */ | |
public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, InternalServerError, IOException { | |
try { | |
// To avoid checking the volatile field context on every call, use the non-volatile isInitialized field as a first-check check. | |
if (! isInitialized) { | |
if (initException != null) | |
throw initException; | |
if (context == null) | |
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()); | |
isInitialized = true; | |
} | |
context.getCallHandler().service(r1, r2); | |
} catch (Throwable e) { | |
r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()); | |
} | |
} | |
@Override /* GenericServlet */ | |
public synchronized void destroy() { | |
if (context != null) | |
context.destroy(); | |
super.destroy(); | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Request-time methods. | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* Returns the current HTTP request. | |
* | |
* @return The current HTTP request, or <jk>null</jk> if it wasn't created. | |
*/ | |
public synchronized RestRequest getRequest() { | |
return getContext().getRequest(); | |
} | |
/** | |
* Returns the current HTTP response. | |
* | |
* @return The current HTTP response, or <jk>null</jk> if it wasn't created. | |
*/ | |
public synchronized RestResponse getResponse() { | |
return getContext().getResponse(); | |
} | |
} |