com.themodernway.server.rest.servlet.RESTServlet Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2018, The Modern Way. All rights reserved.
*
* Licensed 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 com.themodernway.server.rest.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import com.themodernway.common.api.types.ParserException;
import com.themodernway.server.core.NanoTimer;
import com.themodernway.server.core.file.FileUtils;
import com.themodernway.server.core.io.IO;
import com.themodernway.server.core.json.JSONObject;
import com.themodernway.server.core.json.validation.IJSONValidator;
import com.themodernway.server.core.json.validation.IValidationContext;
import com.themodernway.server.core.logging.LoggingOps;
import com.themodernway.server.core.security.IAuthorizationResult;
import com.themodernway.server.core.security.session.IServerSession;
import com.themodernway.server.core.servlet.DefaultHeaderNameSessionIDFromRequestExtractor;
import com.themodernway.server.core.servlet.HTTPServletBase;
import com.themodernway.server.core.servlet.IResponseAction;
import com.themodernway.server.core.servlet.IServletExceptionHandler;
import com.themodernway.server.core.servlet.IServletResponseErrorCodeManager;
import com.themodernway.server.core.servlet.ISessionIDFromRequestExtractor;
import com.themodernway.server.rest.IRESTRequestContext;
import com.themodernway.server.rest.IRESTService;
import com.themodernway.server.rest.RESTErrorStateException;
import com.themodernway.server.rest.RESTException;
import com.themodernway.server.rest.RESTRequestContext;
import com.themodernway.server.rest.support.spring.IRESTContext;
import com.themodernway.server.rest.support.spring.RESTContextInstance;
public class RESTServlet extends HTTPServletBase
{
private static final long serialVersionUID = 1L;
private static final RESTBinderCache STRICT_CACHE = new RESTBinderCache("strict", true);
private static final RESTBinderCache NORMAL_CACHE = new RESTBinderCache("normal", false);
private final long m_size;
private final List m_tags;
public RESTServlet(final long size, final List tags, final double rate, final List role, final IServletResponseErrorCodeManager code, final ISessionIDFromRequestExtractor extr, final IServletExceptionHandler excp)
{
super(rate, role, code, extr, excp);
m_size = size;
if (null != tags)
{
m_tags = toUnique(tags);
}
else
{
m_tags = arrayList();
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, "null tags ignored");
}
}
}
@Override
public void doHead(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doNeverCache(request, response);
response.setContentLengthLong(0L);
response.setStatus(HttpServletResponse.SC_OK);
}
@Override
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doService(request, response, false, HttpMethod.GET, getJSONParametersFromRequest(request));
}
@Override
public void doPut(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doService(request, response, true, HttpMethod.PUT, null);
}
@Override
public void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doService(request, response, true, HttpMethod.POST, null);
}
@Override
public void doPatch(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doService(request, response, true, HttpMethod.PATCH, null);
}
@Override
public void doDelete(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
doService(request, response, true, HttpMethod.DELETE, null);
}
protected void doService(final HttpServletRequest request, final HttpServletResponse response, final boolean read, final HttpMethod type, JSONObject body)
{
final String bind = FileUtils.fixPathBinding(toTrimOrElse(request.getPathInfo(), FileUtils.SINGLE_SLASH));
if (null == bind)
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, "empty service path found.");
}
sendErrorCode(request, response, HttpServletResponse.SC_NOT_FOUND);
return;
}
final IRESTService service = getRESTContext().getBinding(bind, type);
if (null == service)
{
if (getRESTContext().isBindingRegistered(bind))
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service (%s) not type (%s).", bind, type));
}
sendErrorCode(request, response, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
else
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service or binding not found (%s).", bind));
}
sendErrorCode(request, response, HttpServletResponse.SC_NOT_FOUND);
return;
}
}
final List tags = m_tags;
if ((null != tags) && (false == tags.isEmpty()))
{
boolean find = false;
final List vals = service.getTaggingValues();
if (null != vals)
{
for (final String valu : vals)
{
if (tags.contains(valu))
{
find = true;
break;
}
}
}
if (false == find)
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service (%s) for tags not found (%s).", bind, toPrintableString(tags)));
}
sendErrorCode(request, response, HttpServletResponse.SC_NOT_FOUND);
return;
}
}
if (type != service.getRequestMethodType())
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service (%s) not type (%s).", bind, type));
}
sendErrorCode(request, response, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
List uroles = getDefaultRoles();
final IServerSession session = getSession(requireNonNullOrElse(getSessionIDFromRequestExtractor(), DefaultHeaderNameSessionIDFromRequestExtractor.DEFAULT).getSessionID(request));
if (null != session)
{
uroles = session.getRoles();
}
if ((null == uroles) || (uroles.isEmpty()))
{
uroles = getDefaultRoles();
}
final IAuthorizationResult auth = isAuthorized(request, session, service, uroles);
if (false == auth.isAuthorized())
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service authorization failed for (%s) type (%s) reason (%s).", bind, type, auth.getText()));
}
response.addHeader(WWW_AUTHENTICATE, "unauthorized");
sendErrorCode(request, response, HttpServletResponse.SC_FORBIDDEN);
return;
}
if (read)
{
body = parseBODY(service, request, response, type, bind);
}
if (null == body)
{
if (response.getStatus() == HttpServletResponse.SC_OK)
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service (%s) type (%s) null body.", bind, type));
}
sendErrorCode(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return;
}
body = clean(body, false);
final IJSONValidator validator = service.getValidator();
if (null != validator)
{
final IValidationContext context = validator.validate(body);
if ((null != context) && (false == context.isValid()))
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("service (%s) type (%s) invalid body (%s).", bind, type, context.getErrorString()));
}
sendErrorCode(request, response, HttpServletResponse.SC_BAD_REQUEST);
return;
}
}
final IRESTRequestContext context = new RESTRequestContext(service, session, uroles, getServletContext(), request, response, type);
try
{
service.acquire();
final NanoTimer timer = new NanoTimer();
final Object object = service.exec(context, body);
if (logger().isInfoEnabled())
{
logger().info(LoggingOps.THE_MODERN_WAY_MARKER, format("calling service (%s) took %s.", bind, timer.toString()));
}
if (context.isOpen())
{
if (object instanceof IResponseAction)
{
((IResponseAction) object).call(request, response, getServletResponseErrorCodeManager());
return;
}
JSONObject result = json(object);
if (null != result)
{
result = clean(result, true);
}
writeBODY(context, request, response, result);
}
else if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("calling service (%s) context closed.", bind));
}
}
catch (final RESTException e)
{
final IServletExceptionHandler handler = getServletExceptionHandler();
if ((null == handler) || (false == handler.handle(request, response, getServletResponseErrorCodeManagerOrDefault(), e)))
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, "captured overall exception for security.", e);
}
if (context.isOpen())
{
sendErrorCode(request, response, e.getCode(), e.getReason());
}
}
}
catch (final RESTErrorStateException e)
{
final IServletExceptionHandler handler = getServletExceptionHandler();
if ((null == handler) || (false == handler.handle(request, response, getServletResponseErrorCodeManagerOrDefault(), e)))
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) message (%s) state (%s).", bind, e.getMessage(), e.getState().toString()), e);
}
if (context.isOpen())
{
sendErrorCode(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}
catch (final Exception e)
{
final IServletExceptionHandler handler = getServletExceptionHandler();
if ((null == handler) || (false == handler.handle(request, response, getServletResponseErrorCodeManagerOrDefault(), e)))
{
final String uuid = uuid();
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) uuid (%s).", bind, uuid), e);
}
sendErrorCode(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, uuid);
}
}
}
protected JSONObject json(final Object object)
{
if (null == object)
{
return null;
}
if (object instanceof JSONObject)
{
return ((JSONObject) object);
}
return getRESTContext().json(object);
}
protected JSONObject clean(final JSONObject json, final boolean outbound)
{
return json;
}
protected boolean isStrict(final HttpServletRequest request)
{
return Boolean.parseBoolean(toTrimOrNull(request.getHeader(X_STRICT_JSON_FORMAT_HEADER)));
}
protected JSONObject parseBODY(final IRESTService service, final HttpServletRequest request, final HttpServletResponse response, final HttpMethod type, final String bind)
{
if (type != HttpMethod.GET)
{
final long leng = request.getContentLengthLong();
if (leng > 0L)
{
long size = service.getMaxRequestBodySize();
if (false == (size > 0L))
{
size = m_size;
}
if ((size > 0L) && (leng > size))
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) length (%d) greater than (%d).", bind, leng, size));
}
sendErrorCode(request, response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return null;
}
try
{
if (size > 0L)
{
final String buff = IO.getStringAtMost(request.getReader(), size);
if (buff.length() > size)
{
if (logger().isErrorEnabled())
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) length (%d) greater than (%d).", bind, buff.length(), size));
}
sendErrorCode(request, response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return null;
}
return NORMAL_CACHE.get(request.getContentType()).bindJSON(buff);
}
return NORMAL_CACHE.get(request.getContentType()).bindJSON(request.getReader());
}
catch (final ParserException e)
{
final IServletExceptionHandler handler = getServletExceptionHandler();
if (((null == handler) || (false == handler.handle(request, response, getServletResponseErrorCodeManagerOrDefault(), e))) && (logger().isErrorEnabled()))
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) ParserException.", bind), e);
}
return null;
}
catch (final IOException e)
{
final IServletExceptionHandler handler = getServletExceptionHandler();
if (((null == handler) || (false == handler.handle(request, response, getServletResponseErrorCodeManagerOrDefault(), e))) && (logger().isErrorEnabled()))
{
logger().error(LoggingOps.THE_MODERN_WAY_MARKER, format("error calling (%s) IOException.", bind), e);
}
return null;
}
}
if (leng == 0L)
{
return new JSONObject();
}
}
return new JSONObject();
}
protected void writeBODY(final IRESTRequestContext context, final HttpServletRequest request, final HttpServletResponse response, final JSONObject output) throws IOException
{
doNeverCache(request, response);
final String type = toTrimOrElse(request.getHeader(ACCEPT), CONTENT_TYPE_APPLICATION_JSON).toLowerCase();
if (type.contains(CONTENT_TYPE_APPLICATION_JSON))
{
response.setContentType(CONTENT_TYPE_APPLICATION_JSON);
}
if (type.contains(CONTENT_TYPE_TEXT_PROPERTIES))
{
response.setContentType(CONTENT_TYPE_TEXT_PROPERTIES);
}
else if (type.contains(CONTENT_TYPE_TEXT_XML))
{
response.setContentType(CONTENT_TYPE_TEXT_XML);
}
else if (type.contains(CONTENT_TYPE_APPLICATION_XML))
{
response.setContentType(CONTENT_TYPE_APPLICATION_XML);
}
else if (type.contains(CONTENT_TYPE_TEXT_YAML))
{
response.setContentType(CONTENT_TYPE_TEXT_YAML);
}
else if (type.contains(CONTENT_TYPE_APPLICATION_YAML))
{
response.setContentType(CONTENT_TYPE_APPLICATION_YAML);
}
else
{
response.setContentType(CONTENT_TYPE_APPLICATION_JSON);
}
if (null == output)
{
response.setContentLengthLong(0L);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
response.setStatus(HttpServletResponse.SC_OK);
final PrintWriter stream = response.getWriter();
stream.flush();
try
{
if (isStrict(request))
{
STRICT_CACHE.get(type).send(stream, output);
}
else
{
NORMAL_CACHE.get(type).send(stream, output);
}
}
catch (final ParserException e)
{
throw new IOException(e);
}
stream.flush();
}
protected IRESTContext getRESTContext()
{
return RESTContextInstance.getRESTContextInstance();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy