/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.http;
import java.io.IOException;
import java.util.Properties;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.services.application.ApplicationService;
import nl.strohalm.cyclos.utils.Pair;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.logging.LoggingHandler;
import nl.strohalm.cyclos.utils.logging.RestLogDTO;
import nl.strohalm.cyclos.webservices.WebServiceContext;
import nl.strohalm.cyclos.webservices.model.ServerErrorVO;
import nl.strohalm.cyclos.webservices.rest.RestHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus;
/**
* Filter which handles the REST services context
* @author luis
*/
public class RestFilter extends BaseWebServiceTransactionFilter {
private static final Log LOG = LogFactory.getLog(RestFilter.class);
private ApplicationService applicationService;
private LoggingHandler loggingHandler;
private boolean restDisabled;
@Inject
public void setApplicationService(final ApplicationService applicationService) {
this.applicationService = applicationService;
}
@Inject
public void setCyclosProperties(final Properties properties) {
final String disabled = properties.getProperty("cyclos.disableRestServices", "false");
restDisabled = Boolean.parseBoolean(disabled);
}
@Inject
public void setLoggingHandler(final LoggingHandler loggingHandler) {
this.loggingHandler = loggingHandler;
}
@Override
protected boolean applyResponseStateOnRollback() {
return false;
}
@Override
protected void execute(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
// If Rest is disabled by configuration, always send a 404 error
if (restDisabled) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Check if the application is online
if (!applicationService.isOnline()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
// Add no cache control
response.setHeader("Cache-control", "no-cache, no-store, must-revalidate");
// Check non-secure access when HTTP is enabled
if (Boolean.TRUE.equals(getServletContext().getAttribute("cyclos.httpEnabled"))) {
if (!"https".equalsIgnoreCase(request.getProtocol())) {
response.sendError(HttpStatus.UPGRADE_REQUIRED.value(), HttpStatus.UPGRADE_REQUIRED.getReasonPhrase());
response.addHeader("Upgrade", "TLS/1.0, HTTP/1.1");
response.addHeader("Connection", "Upgrade");
return;
}
}
// When logging REST parameters, we need to wrap the request with StringBodyRequest in order to have the body available
final boolean logParameters = loggingHandler.isRestParametersLogEnabled();
super.execute(logParameters ? new StringBodyRequest(request) : request, response, chain);
}
@Override
protected Log getLog() {
return LOG;
}
@Override
protected String getServiceName() {
return "REST web service";
}
@Override
protected boolean handleSilencedErrors() {
return true;
}
@Override
protected void onBeforeRunInTransaction(final HttpServletRequest request, final HttpServletResponse response) {
WebServiceContext.set((Member) null, getServletContext(), request, response);
}
@Override
@SuppressWarnings("unchecked")
protected void onError(final HttpServletRequest request, final HttpServletResponse response, final Throwable t) throws IOException {
log(request, t);
LOG.error("Error on REST call", t);
final Pair<ServerErrorVO, Integer> error = RestHelper.resolveError(t);
final ServerErrorVO vo = error.getFirst();
final JSONObject json = new JSONObject();
if (StringUtils.isNotEmpty(vo.getErrorCode())) {
json.put("errorCode", vo.getErrorCode());
}
if (StringUtils.isNotEmpty(vo.getErrorDetails())) {
json.put("errorDetails", vo.getErrorDetails());
}
response.setStatus(error.getSecond());
response.setContentType("application/json");
json.writeJSONString(response.getWriter());
response.flushBuffer();
}
@Override
protected void onTransactionEnd(final HttpServletRequest request, final HttpServletResponse response) {
// Generate the trace log
log(request, null);
}
private RestLogDTO buildLogDTO(final HttpServletRequest request, final Throwable error) {
String body;
if (request instanceof StringBodyRequest) {
try {
body = ((StringBodyRequest) request).getBody();
} catch (final Exception e) {
body = "<Error obtaining request body: " + e.toString() + ">";
}
} else {
body = null;
}
final RestLogDTO dto = new RestLogDTO();
dto.setRemoteAddress(request.getRemoteAddr());
dto.setMember(LoggedUser.hasUser() ? LoggedUser.member() : null);
dto.setMethod(request.getMethod());
dto.setUri(request.getRequestURI());
dto.setQueryString(request.getQueryString());
dto.setRequestBody(body);
return dto;
}
private void log(final HttpServletRequest request, final Throwable error) {
final RestLogDTO dto = buildLogDTO(request, error);
loggingHandler.traceRest(dto);
}
}