/******************************************************************************* * Copyright (c) 2013 GigaSpaces Technologies Ltd. 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 org.cloudifysource.rest.interceptors; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.cloudifysource.dsl.internal.CloudifyConstants; import org.cloudifysource.dsl.internal.CloudifyMessageKeys; import org.cloudifysource.dsl.rest.response.Response; import org.cloudifysource.rest.controllers.RestErrorException; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.validation.BindingResult; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.j_spaces.kernel.PlatformVersion; /** * This intercepter has two goals. * <br><br> * 1. Validate the request is made with the current API version of the REST Gateway. * <br> * 2. Construct the {@link Response} Object after the controller has finished handling the request. * @author elip * */ public class ApiVersionValidationAndRestResponseBuilderInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = Logger .getLogger(ApiVersionValidationAndRestResponseBuilderInterceptor.class.getName()); private static final String CURRENT_API_VERSION = PlatformVersion.getVersion(); @Autowired(required = true) private MessageSource messageSource; @Override public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception { String requestVersion = extractVersionFromRequest(request); if (CloudifyConstants.SERVICE_CONTROLLER_URL.equals(requestVersion) || CloudifyConstants.ADMIN_API_CONTROLLER_URL.equals(requestVersion)) { // if this is a request to service/** or to admin/** than this is not the right interceptor. // (for example service/templates request will arrive here because of the 'templates' suffix // but it is not a template controller and it will be processed in the VersionValidateInterceptor) return true; } if (logger.isLoggable(Level.FINEST)) { logger.finest("pre handle request from " + request.getRequestURI()); } // checks the request's version if (!CURRENT_API_VERSION.equalsIgnoreCase(requestVersion)) { throw new RestErrorException(CloudifyMessageKeys.API_VERSION_MISMATCH.getName(), requestVersion, CURRENT_API_VERSION); } return true; } private String extractVersionFromRequest(final HttpServletRequest request) { String requestURIWithoutContextPath = request.getRequestURI().substring(request.getContextPath().length()).substring(1); return requestURIWithoutContextPath.split("/")[0]; } @Override public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception { String requestVersion = extractVersionFromRequest(request); if (CloudifyConstants.SERVICE_CONTROLLER_URL.equals(requestVersion)) { // if this is a request to service/** than this is not the right interceptor. // (for example service/templates request will arrive here because of the 'templates' suffix // but it is not a template controller and it will be processed in the VersionValidateInterceptor) return; } if (logger.isLoggable(Level.FINEST)) { logCurrentStatus(request, modelAndView); } Object model = filterModel(modelAndView, handler); modelAndView.clear(); response.setContentType(CloudifyConstants.MIME_TYPE_APPLICATION_JSON); if (model instanceof Response<?>) { String responseBodyStr = new ObjectMapper().writeValueAsString(model); response.getOutputStream().write(responseBodyStr.getBytes()); response.getOutputStream().close(); } else { Response<Object> responseBodyObj = new Response<Object>(); responseBodyObj.setResponse(model); responseBodyObj.setStatus("Success"); responseBodyObj.setMessage(messageSource.getMessage(CloudifyMessageKeys.OPERATION_SUCCESSFULL.getName(), new Object[] {}, Locale.US)); responseBodyObj.setMessageId(CloudifyMessageKeys.OPERATION_SUCCESSFULL.getName()); String responseBodyStr = new ObjectMapper().writeValueAsString(responseBodyObj); response.getOutputStream().write(responseBodyStr.getBytes()); response.getOutputStream().close(); } } private void logCurrentStatus(final HttpServletRequest request, final ModelAndView modelAndView) { String requestUri = request.getRequestURI(); Map<String, Object> model = modelAndView.getModel(); View view = modelAndView.getView(); StringBuilder message = new StringBuilder("post handle request"); if (requestUri == null) { message.append(", requestUri is null"); } else { message.append(" from " + request.getRequestURI()); } if (model == null) { message.append(", model is null"); } else { message.append(" with model " + model.toString()); } if (view == null) { message.append(", view is null"); } else { message.append(" and view " + view.toString()); } logger.finest(message.toString()); } /** * Filters the modelAndView object and retrieves the actual object returned by the controller. * This implementation assumes the model consists of just one returned object and a BindingResult. * If the model is empty, the supported return types are String (the view name) or void. */ private Object filterModel(final ModelAndView modelAndView, final Object handler) throws RestErrorException { Object methodReturnObject = null; Map<String, Object> model = modelAndView.getModel(); if (model != null && !model.isEmpty()) { // the model is not empty. The return value is the first value that is not a BindingResult for (Map.Entry<String, Object> entry : model.entrySet()) { Object value = entry.getValue(); if (!(value instanceof BindingResult)) { methodReturnObject = value; break; } } } else { // the model is empty, this means the return type is String or void if (handler instanceof HandlerMethod) { Class<?> returnType = ((HandlerMethod) handler).getMethod().getReturnType(); if (returnType == Void.TYPE) { methodReturnObject = null; } else if (returnType == String.class) { String viewName = modelAndView.getViewName(); methodReturnObject = viewName; } else { logger.warning("return type not supported: " + returnType); throw new RestErrorException("return type not supported: " + returnType); } } else { logger.warning("handler object is not a HandlerMethod: " + handler); throw new RestErrorException("handler object is not a HandlerMethod: " + handler); } } return methodReturnObject; } }