/** * Return a {@link JAXBContext} for the given class. * @param clazz the class to return the context for * @return the {@code JAXBContext} * @throws HttpMessageConversionException in case of JAXB errors */ protected final JAXBContext getJaxbContext(Class<?> clazz) { Assert.notNull(clazz, "'clazz' must not be null"); JAXBContext jaxbContext = this.jaxbContexts.get(clazz); if (jaxbContext == null) { try { jaxbContext = JAXBContext.newInstance(clazz); this.jaxbContexts.putIfAbsent(clazz, jaxbContext); } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } return jaxbContext; }
@Override @SuppressWarnings("unchecked") protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream body = inputMessage.getBody(); if (DOMSource.class.equals(clazz)) { return (T) readDOMSource(body); } else if (SAXSource.class.equals(clazz)) { return (T) readSAXSource(body); } else if (StAXSource.class.equals(clazz)) { return (T) readStAXSource(body); } else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { return (T) readStreamSource(body); } else { throw new HttpMessageConversionException("Could not read class [" + clazz + "]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported."); } }
@Override @SuppressWarnings("unchecked") protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream body = inputMessage.getBody(); if (DOMSource.class == clazz) { return (T) readDOMSource(body); } else if (SAXSource.class == clazz) { return (T) readSAXSource(body); } else if (StAXSource.class == clazz) { return (T) readStAXSource(body); } else if (StreamSource.class == clazz || Source.class == clazz) { return (T) readStreamSource(body); } else { throw new HttpMessageConversionException("Could not read class [" + clazz + "]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported."); } }
protected boolean isMissingExpectedContentCase(HttpMessageConversionException ex) { if (ex instanceof HttpMessageNotReadableException) { // Different versions of Spring Web MVC can have different ways of expressing missing content. // More common case if (ex.getMessage().startsWith("Required request body is missing")) { return true; } // An older/more unusual case. Unfortunately there's a lot of manual digging that we have to do to determine // that we've reached this case. if (ex.getCause() != null && ex.getCause() instanceof JsonMappingException && ex.getCause().getMessage() != null && ex.getCause().getMessage() .contains("No content to map due to end-of-input")) { return true; } } return false; }
@Override public void execute(PostDownloadExecution execution) { PushbulletRequest body = new PushbulletRequest(); body.body = execution.fileName; body.title = "A new file has been downloaded"; body.type = "note"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.add("Authorization", "Bearer " + apiKey); try { LOGGER.info("Sending notification to Pushbullet for {}", execution.fileName); LOGGER.debug("API Key: {}", apiKey); HttpEntity<PushbulletRequest> httpEntity = new HttpEntity<PushbulletRequest>(body, headers); LOGGER.debug("Sending message to Pushbullet: {}", httpEntity); restTemplate.exchange("https://api.pushbullet.com/v2/pushes", HttpMethod.POST, httpEntity, Object.class); } catch (RestClientException | HttpMessageConversionException e ) { LOGGER.debug("Full stacktrace", e); LOGGER.error("Unable to complete notification to Pushbullet. Given error: {}", e.getMessage()); } }
@Override public void execute(PostDownloadExecution execution) { try { HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", contentType); LOGGER.info("Sending message to generic API for {}", execution.fileName); HttpEntity<String> httpEntity = new HttpEntity<String>(resolveFilename(body, execution.fileName), headers); LOGGER.debug("Sending {} message {} to generic API: {}", method, httpEntity, url); restTemplate.exchange(resolveFilename(url, execution.fileName), method, httpEntity, Object.class); } catch (RestClientException | HttpMessageConversionException e) { LOGGER.debug("Full stacktrace", e); LOGGER.error("Unable to complete message to generic API. Given error: {}", e.getMessage()); } }
@Override public Version retrieveRemoteVersion() { try { String gitHubURL = "https://raw.githubusercontent.com/linuxserver/davos/LatestRelease/version.txt"; LOGGER.debug("Calling out to GitHub to check for new version ({})", gitHubURL); ResponseEntity<String> response = restTemplate.exchange(gitHubURL, HttpMethod.GET, new HttpEntity<String>(new HttpHeaders()), String.class); String body = response.getBody(); LOGGER.debug("GitHub responded with a {}, and body of {}", response.getStatusCode(), body); return new Version(body); } catch (RestClientException | HttpMessageConversionException e) { LOGGER.error("Unable to get version from GitHub: {}", e.getMessage(), e); LOGGER.debug("Defaulting remote version to zero"); return new Version(0, 0, 0); } }
/** * Return a {@link JAXBContext} for the given class. * @param clazz the class to return the context for * @return the {@code JAXBContext} * @throws HttpMessageConversionException in case of JAXB errors */ protected final JAXBContext getJaxbContext(Class clazz) { Assert.notNull(clazz, "'clazz' must not be null"); JAXBContext jaxbContext = this.jaxbContexts.get(clazz); if (jaxbContext == null) { try { jaxbContext = JAXBContext.newInstance(clazz); this.jaxbContexts.putIfAbsent(clazz, jaxbContext); } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } return jaxbContext; }
@Override @SuppressWarnings("unchecked") protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream body = inputMessage.getBody(); if (DOMSource.class.equals(clazz)) { return (T) readDOMSource(body); } else if (SAXSource.class.equals(clazz)) { return (T) readSAXSource(body); } else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { return (T) readStreamSource(body); } else { throw new HttpMessageConversionException("Could not read class [" + clazz + "]. Only DOMSource, SAXSource, and StreamSource are supported."); } }
protected static JAXBContext getJaxbContext(Class clazz) { Assert.notNull(clazz, "'clazz' must not be null"); JAXBContext jaxbContext = jaxbContexts.get(clazz); if (jaxbContext == null) { try { jaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class); jaxbContexts.putIfAbsent(clazz, jaxbContext); } catch (JAXBException ex) { throw new HttpMessageConversionException("Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } return jaxbContext; }
/** * Create a new {@link Marshaller} for the given class. * @param clazz the class to create the marshaller for * @return the {@code Marshaller} * @throws HttpMessageConversionException in case of JAXB errors */ protected final Marshaller createMarshaller(Class<?> clazz) { try { JAXBContext jaxbContext = getJaxbContext(clazz); Marshaller marshaller = jaxbContext.createMarshaller(); customizeMarshaller(marshaller); return marshaller; } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
/** * Create a new {@link Unmarshaller} for the given class. * @param clazz the class to create the unmarshaller for * @return the {@code Unmarshaller} * @throws HttpMessageConversionException in case of JAXB errors */ protected final Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException { try { JAXBContext jaxbContext = getJaxbContext(clazz); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); customizeUnmarshaller(unmarshaller); return unmarshaller; } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
@ResponseStatus(HttpStatus.BAD_REQUEST) // 400 @ExceptionHandler(HttpMessageConversionException.class) @ResponseBody public ErrorInfo handleBadMessageConversion(HttpMessageConversionException e) { LOGGER.error("Bad request", e); return new ErrorInfo("Bad request", e); }
protected static JAXBContext getJaxbContext(Class clazz) { Assert.notNull(clazz, "'clazz' must not be null"); JAXBContext jaxbContext = jaxbContexts.get(clazz); if (jaxbContext == null) { try { jaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class); jaxbContexts.putIfAbsent(clazz, jaxbContext); } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } return jaxbContext; }
/** * This delagates actual execution to {@link #runWithUrl(String)} and handles any * Spring Rest exceptions, wrapping them as necessary. Default behavior is to throw * a {@link RetryableApiCommandException} for 5xx errors and timeouts, and {@link * NonRetryableApiCommandException} for all others. If you need different behavior * your best bet is to write your own {@link RemoteServiceCallback} and/or use * the various flavors of {@link org.springframework.web.client.RestTemplate} that * take let you provide {@link ResponseErrorHandler}. * @param url The URL of the remote service. * @return Result of the remote call, or potentially throws an exception if an * error occurs calling the remote service. */ @Override public T run(String url) { T response = null; try { LOG.debug("Running command {} with URL {}", getContext().getCommandName(), url); response = runWithUrl(url); } catch (HttpStatusCodeException hsce) { if (hsce.getStatusCode() == HttpStatus.REQUEST_TIMEOUT || hsce.getStatusCode().value() >= 500) { throw new RetryableApiCommandException("Remote server error: " + hsce.getMessage(), hsce); } else { throw new NonRetryableApiCommandException("Local client error: " + hsce.getMessage(), hsce); } } catch(HttpMessageConversionException e) { throw new NonRetryableApiCommandException("Invalid response from server: " + e.getMessage(), e); } LOG.debug("Returning response {}", response); return response; }
@Test public void ifRestTemplateFailsBecauseMessageIsUnreadbleThenDoNothing() { PostDownloadExecution execution = new PostDownloadExecution(); execution.fileName = "filename"; when(mockRestTemplate.exchange(eq("http://url"), eq(HttpMethod.POST), any(HttpEntity.class), eq(Object.class))) .thenThrow(new HttpMessageConversionException("")); httpAPICallAction.execute(execution); }
@Test public void ifRestTemplateFailsBecauseMessageIsUnreadbleThenDoNothing() { when(mockRestTemplate.exchange(eq("https://api.pushbullet.com/v2/pushes"), eq(HttpMethod.POST), any(HttpEntity.class), eq(Object.class))).thenThrow(new HttpMessageConversionException("")); pushbulletNotifyAction.execute(new PostDownloadExecution()); }
@ExceptionHandler(HttpMessageConversionException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public ErrorResponse processHttpMessageConversionError(final HttpMessageConversionException ex, final HttpServletResponse response) throws IOException { return processExceptionResponse(ex, response, org.apache.http.HttpStatus.SC_BAD_REQUEST); }
@ExceptionHandler({ ServletRequestBindingException.class, HttpMessageConversionException.class, ConversionException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public ErrorBean handleMissingParameterException(Exception exception, Locale locale) { return createErrorBean(extractExceptionName(exception), locale, exception); }
/** * Creates a new {@link Marshaller} for the given class. * * @param clazz * the class to create the marshaller for * @return the {@code Marshaller} * @throws HttpMessageConversionException * in case of JAXB errors */ protected final Marshaller createMarshaller(Class<?> clazz) { try { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createMarshaller(); } catch (JAXBException ex) { throw new HttpMessageConversionException("Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
/** * Creates a new {@link Unmarshaller} for the given class. * * @param clazz * the class to create the unmarshaller for * @return the {@code Unmarshaller} * @throws HttpMessageConversionException * in case of JAXB errors */ protected final Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException { try { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createUnmarshaller(); } catch (JAXBException ex) { throw new HttpMessageConversionException("Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
/** * Returns a {@link JAXBContext} for the given class. * * @param clazz * the class to return the context for * @return the {@code JAXBContext} * @throws HttpMessageConversionException * in case of JAXB errors */ protected final JAXBContext getJaxbContext(Class<?> clazz) { Assert.notNull(clazz, "'clazz' must not be null"); JAXBContext result = null; if (defaultJaxbContext != null) { result = defaultJaxbContext; } else { result = jaxbContexts.get(clazz); if (result == null) { try { result = JAXBContext.newInstance(clazz); jaxbContexts.putIfAbsent(clazz, result); } catch (JAXBException ex) { throw new HttpMessageConversionException("Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } } } return result; }
/** * Create a new {@link Marshaller} for the given class. * @param clazz the class to create the marshaller for * @return the {@code Marshaller} * @throws HttpMessageConversionException in case of JAXB errors */ protected final Marshaller createMarshaller(Class clazz) { try { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createMarshaller(); } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
/** * Create a new {@link Unmarshaller} for the given class. * @param clazz the class to create the unmarshaller for * @return the {@code Unmarshaller} * @throws HttpMessageConversionException in case of JAXB errors */ protected final Unmarshaller createUnmarshaller(Class clazz) throws JAXBException { try { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createUnmarshaller(); } catch (JAXBException ex) { throw new HttpMessageConversionException( "Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex); } }
/** * Validate param using param specific validator and validate all strings using a blacklist validator * @param arg * @param argName */ private void validateArg(Object arg, String argName) { BindingResult result = new BeanPropertyBindingResult(arg, argName); ValidationUtils.invokeValidator(getValidator(), arg, result); // force string validation for bad chars if (arg instanceof String) { ValidationUtils.invokeValidator(getValidator(), new DefaultStringValidatable((String) arg), result); } if (result.hasErrors()) { throw new HttpMessageConversionException("Invalid input parameter " + argName, new BindException(result)); } }
@Test public void testBadSave() throws Exception { Map<String, Config> mapOfConfigs = new HashMap<String, Config>(); Config bad = new Config("+++", null, null, Type.FIELD, null, null, null, null, null); mapOfConfigs.put("something", bad); ConfigMap map = new ConfigMap(); map.setConfig(mapOfConfigs); try { configController.saveConfig(map); Assert.fail("Should not be able to save"); } catch (HttpMessageConversionException e) { Assert.assertEquals("Invalid input parameter configMap", e.getMessage().substring(0, 33)); } }
@Override public ApiExceptionHandlerListenerResult shouldHandleException(Throwable ex) { SortedApiErrorSet handledErrors = null; List<Pair<String, String>> extraDetailsForLogging = new ArrayList<>(); if (ex instanceof NoHandlerFoundException) { handledErrors = singletonSortedSetOf(projectApiErrors.getNotFoundApiError()); } if (ex instanceof TypeMismatchException) { TypeMismatchException tme = (TypeMismatchException) ex; Map<String, Object> metadata = new LinkedHashMap<>(); utils.addBaseExceptionMessageToExtraDetailsForLogging(ex, extraDetailsForLogging); String badPropName = extractPropertyName(tme); String badPropValue = (tme.getValue() == null) ? null : String.valueOf(tme.getValue()); String requiredTypeNoInfoLeak = extractRequiredTypeNoInfoLeak(tme); extraDetailsForLogging.add(Pair.of("bad_property_name", badPropName)); if (badPropName != null) { metadata.put("bad_property_name", badPropName); } extraDetailsForLogging.add(Pair.of("bad_property_value", String.valueOf(tme.getValue()))); if (badPropValue != null) { metadata.put("bad_property_value", badPropValue); } extraDetailsForLogging.add(Pair.of("required_type", String.valueOf(tme.getRequiredType()))); if (requiredTypeNoInfoLeak != null) { metadata.put("required_type", requiredTypeNoInfoLeak); } handledErrors = singletonSortedSetOf( new ApiErrorWithMetadata(projectApiErrors.getTypeConversionApiError(), metadata) ); } if (ex instanceof ServletRequestBindingException) { // Malformed requests can be difficult to track down - add the exception's message to our logging details utils.addBaseExceptionMessageToExtraDetailsForLogging(ex, extraDetailsForLogging); handledErrors = singletonSortedSetOf(projectApiErrors.getMalformedRequestApiError()); } if (ex instanceof HttpMessageConversionException) { // Malformed requests can be difficult to track down - add the exception's message to our logging details utils.addBaseExceptionMessageToExtraDetailsForLogging(ex, extraDetailsForLogging); if (isMissingExpectedContentCase((HttpMessageConversionException) ex)) { handledErrors = singletonSortedSetOf(projectApiErrors.getMissingExpectedContentApiError()); } else { // NOTE: If this was a HttpMessageNotReadableException with a cause of // com.fasterxml.jackson.databind.exc.InvalidFormatException then we *could* theoretically map // to projectApiErrors.getTypeConversionApiError(). If we ever decide to implement this, then // InvalidFormatException does contain reference to the field that failed to convert - we can // get to it via getPath(), iterating over each path object, and building the full path by // concatenating them with '.'. For now we'll just turn all errors in this category into // projectApiErrors.getMalformedRequestApiError(). handledErrors = singletonSortedSetOf(projectApiErrors.getMalformedRequestApiError()); } } if (ex instanceof HttpMediaTypeNotAcceptableException) { handledErrors = singletonSortedSetOf(projectApiErrors.getNoAcceptableRepresentationApiError()); } if (ex instanceof HttpMediaTypeNotSupportedException) { handledErrors = singletonSortedSetOf(projectApiErrors.getUnsupportedMediaTypeApiError()); } if (ex instanceof HttpRequestMethodNotSupportedException) { handledErrors = singletonSortedSetOf(projectApiErrors.getMethodNotAllowedApiError()); } if (handledErrors != null) { return ApiExceptionHandlerListenerResult.handleResponse(handledErrors, extraDetailsForLogging); } return ApiExceptionHandlerListenerResult.ignoreResponse(); }
@Test public void shouldReturnMALFORMED_REQUESTForHttpMessageConversionException() { HttpMessageConversionException ex = new HttpMessageConversionException("foobar"); ApiExceptionHandlerListenerResult result = listener.shouldHandleException(ex); validateResponse(result, true, Collections.singletonList(testProjectApiErrors.getMalformedRequestApiError())); }
@ResponseStatus(HttpStatus.BAD_REQUEST) // 400 @ExceptionHandler(HttpMessageConversionException.class) @ResponseBody public ErrorInfo handleBadMessageConversion(HttpMessageConversionException e) { return new ErrorInfo("Bad request", e); }
@ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) public GeneralError handleException(Exception exception) { if (exception instanceof MethodArgumentNotValidException) { BindingResult bindingResult = ((MethodArgumentNotValidException) exception).getBindingResult(); return ErrorBuilder.buildValidationError(bindingResult); } else if (exception instanceof DataAccessException) { return ErrorBuilder.buildDatabasePersistenceError(exception); } else if (exception instanceof HttpMessageConversionException) { return ErrorBuilder.buildJsonMappingException(exception); } return ErrorBuilder.buildUncaughtException(exception); }
@ExceptionHandler(Exception.class) public ResponseEntity<GeneralErrorDTO> handleExceptions(Exception e, HttpServletRequest request) { // idea by http://blog.sizovs.net/spring-rest-exception-handler/ ResponseStatus annotation = AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class); HttpStatus responseStatus = INTERNAL_SERVER_ERROR; String message = e.getLocalizedMessage(); if (annotation != null) { if(annotation.value() != null) { responseStatus = annotation.value(); } if(annotation.reason() != null) { message = annotation.reason(); } } else if (e instanceof AuthenticationException) { responseStatus = UNAUTHORIZED; } else if (e instanceof AccessDeniedException) { responseStatus = FORBIDDEN; } else if (e instanceof MethodArgumentNotValidException || e instanceof HttpMessageConversionException || e instanceof ServletRequestBindingException || e instanceof TypeMismatchException) { responseStatus = BAD_REQUEST; } else if (e instanceof HttpRequestMethodNotSupportedException) { responseStatus = METHOD_NOT_ALLOWED; } else if (e instanceof HttpMediaTypeException) { responseStatus = UNSUPPORTED_MEDIA_TYPE; } GeneralErrorDTO result = new GeneralErrorDTO(); result.setTimestamp(new Date()); result.setStatus(responseStatus.value()); result.setError(responseStatus.getReasonPhrase()); result.setMethod(request.getMethod()); result.setPath(request.getRequestURI()); // mask the reason behind the exception on !DEV if(asList(env.getActiveProfiles()).contains(PROFILE_DEV)) { result.setException(e.getClass().getSimpleName()); result.setMessage(message); } return new ResponseEntity<>(result, responseStatus); }
@ExceptionHandler(HttpMessageConversionException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String badRequest(HttpMessageConversionException e) { return "bad request"; }