private void writeHttpError(String message, OutputStream entityStream) {
    try {
      ObjectMapper mapper = new ObjectMapper();
      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      Template error;
      Template masterTemplate = null;
      try {
        masterTemplate =
            themeFileResolver.getIndexTemplate(webContext.getRequest().getBreakpoint());
      } catch (TemplateNotFoundException e) {
        // Nothing doing
      }
      try {
        error = themeFileResolver.getTemplate("500.html", webContext.getRequest().getBreakpoint());
      } catch (TemplateNotFoundException notFound) {
        // Fallback on the classpath hosted error 500 file
        error =
            new Template(
                "500",
                Resources.toString(Resources.getResource("templates/500.html"), Charsets.UTF_8));
      }
      Map<String, Object> errorContext = Maps.newHashMap();
      errorContext.put("error", message);

      engine.get().register(error);

      String rendered;

      if (masterTemplate != null) {
        errorContext.put("templateContent", error.getId());
        errorContext.put("template", "500");
        engine.get().register(masterTemplate);
        rendered =
            engine.get().render(masterTemplate.getId(), mapper.writeValueAsString(errorContext));
      } else {
        rendered = engine.get().render(error.getId(), mapper.writeValueAsString(errorContext));
      }

      entityStream.write(rendered.getBytes());
    } catch (Exception e1) {
      throw new RuntimeException(e1);
    }
  }
  private void writeDeveloperError(WebView webView, Exception e, OutputStream entityStream) {
    try {
      // Note:
      // This could be seen as a "server error", but we don't set the Status header to 500 because
      // we want to be
      // able to distinguish between actual server errors (internal Mayocat Shop server error) and
      // theme
      // developers errors (which this is).
      // This is comes at play when setting up monitoring with alerts on a number of 5xx response
      // above a
      // certain threshold.

      // Re-serialize the context as json with indentation for better debugging
      ObjectMapper mapper = new ObjectMapper();
      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      Map<String, Object> context = webView.data();
      String jsonContext = mapper.writeValueAsString(context);
      Template error =
          new Template(
              "developerError",
              Resources.toString(
                  Resources.getResource("templates/developerError.html"), Charsets.UTF_8));
      Map<String, Object> errorContext = Maps.newHashMap();
      errorContext.put(
          "error", StringEscapeUtils.escapeXml(cleanErrorMessageForDisplay(e.getMessage())));
      errorContext.put("stackTrace", StringEscapeUtils.escapeXml(ExceptionUtils.getStackTrace(e)));
      errorContext.put("context", StringEscapeUtils.escapeXml(jsonContext).trim());
      errorContext.put("rawContext", jsonContext);
      errorContext.put("template", webView.template().toString());

      engine.get().register(error);
      String rendered = engine.get().render(error.getId(), mapper.writeValueAsString(errorContext));
      entityStream.write(rendered.getBytes());
    } catch (Exception e1) {
      throw new RuntimeException(e1);
    }
  }
  @Override
  public void writeTo(
      WebView webView,
      Class<?> type,
      Type genericType,
      Annotation[] annotations,
      MediaType mediaType,
      MultivaluedMap<String, Object> httpHeaders,
      OutputStream entityStream)
      throws IOException, WebApplicationException {
    try {

      if (!mediaType.equals(MediaType.APPLICATION_JSON_TYPE)
          && webContext.getTheme() != null
          && !webContext.getTheme().isValidDefinition()) {
        // Fail fast with invalid theme error page, so that the developer knows ASAP and can correct
        // it.
        writeHttpError("Invalid theme definition", entityStream);
        return;
      }

      Template masterTemplate = null;
      try {
        masterTemplate =
            themeFileResolver.getIndexTemplate(webContext.getRequest().getBreakpoint());
      } catch (TemplateNotFoundException e) {
        if (!mediaType.equals(MediaType.APPLICATION_JSON_TYPE)) {
          // For JSON API calls, we don't care if the template is found or not.
          // For other calls, raise the exception
          throw e;
        }
      }

      Template template = null;
      String jsonContext = null;

      if (!mediaType.equals(MediaType.APPLICATION_JSON_TYPE)) {
        if (webView.model().isPresent()) {
          // Check for a model

          Optional<String> path = themeFileResolver.resolveModelPath(webView.model().get());
          if (path.isPresent()) {
            try {
              template =
                  themeFileResolver.getTemplate(
                      path.get(), webContext.getRequest().getBreakpoint());
            } catch (TemplateNotFoundException e) {
              // Keep going
            }
          }
          // else just fallback on the default model
        }

        if (template == null) {
          try {
            template =
                themeFileResolver.getTemplate(
                    webView.template().toString(), webContext.getRequest().getBreakpoint());
          } catch (TemplateNotFoundException e) {
            if (webView.hasOption(WebView.Option.FALLBACK_ON_DEFAULT_THEME)) {
              try {
                template =
                    themeFileResolver.getTemplate(
                        themeManager.getDefaultTheme(),
                        webView.template().toString(),
                        webContext.getRequest().getBreakpoint());
              } catch (TemplateNotFoundException e1) {
                // continue
              }
            }
            if (template == null
                && webView.hasOption(WebView.Option.FALLBACK_ON_GLOBAL_TEMPLATES)) {
              template =
                  themeFileResolver.getGlobalTemplate(
                      webView.template().toString(), webContext.getRequest().getBreakpoint());
            }
          }
        }
      }

      if (!mediaType.equals(MediaType.APPLICATION_JSON_TYPE)
          || httpHeaders.containsKey("X-Mayocat-Full-Context")) {
        if (template != null) {
          webView.data().put("templateContent", template.getId());
          webView.data().put("template", FilenameUtils.getBaseName(webView.template().toString()));
        }

        for (WebDataSupplier supplier : dataSuppliers.values()) {
          supplier.supply(webView.data());
        }
      }

      try {
        ObjectMapper mapper = new ObjectMapper();

        if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE)) {
          mapper.writeValue(entityStream, webView.data());
          return;
        }

        if (template == null) {
          throw new TemplateNotFoundException();
        }

        jsonContext = mapper.writeValueAsString(webView.data());
        engine.get().register(template);
        engine.get().register(masterTemplate);
        String rendered = engine.get().render(masterTemplate.getId(), jsonContext);
        entityStream.write(rendered.getBytes());
      } catch (JsonMappingException e) {
        this.logger.warn("Failed to serialize JSON context", e);
        writeDeveloperError(webView, e, entityStream);
      } catch (TemplateEngineException e) {
        writeDeveloperError(webView, e, entityStream);
      }
    } catch (TemplateNotFoundException e) {
      throw new WebApplicationException(
          Response.status(Response.Status.INTERNAL_SERVER_ERROR)
              .entity("Template not found : " + webView.template().toString())
              .build());
    }
  }