/* * Copyright 2016 KairosDB Authors * * 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.kairosdb.core.http.rest; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.google.gson.stream.MalformedJsonException; import com.google.inject.Inject; import com.google.inject.name.Named; import org.kairosdb.core.DataPointSet; import org.kairosdb.core.KairosDataPointFactory; import org.kairosdb.core.aggregator.AggregatorFactory; import org.kairosdb.core.aggregator.AggregatorMetadata; import org.kairosdb.core.datapoints.LongDataPointFactory; import org.kairosdb.core.datapoints.LongDataPointFactoryImpl; import org.kairosdb.core.datapoints.StringDataPointFactory; import org.kairosdb.core.datastore.DataPointGroup; import org.kairosdb.core.datastore.DatastoreQuery; import org.kairosdb.core.datastore.KairosDatastore; import org.kairosdb.core.datastore.QueryMetric; import org.kairosdb.core.formatter.DataFormatter; import org.kairosdb.core.formatter.FormatterException; import org.kairosdb.core.formatter.JsonFormatter; import org.kairosdb.core.formatter.JsonResponse; import org.kairosdb.core.http.rest.json.DataPointsParser; import org.kairosdb.core.http.rest.json.ErrorResponse; import org.kairosdb.core.http.rest.json.JsonResponseBuilder; import org.kairosdb.core.http.rest.json.QueryParser; import org.kairosdb.core.http.rest.json.ValidationErrors; import org.kairosdb.core.reporting.KairosMetricReporter; import org.kairosdb.core.reporting.ThreadReporter; import org.kairosdb.util.MemoryMonitorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.GZIPInputStream; import static com.google.common.base.Preconditions.checkNotNull; import static javax.ws.rs.core.Response.ResponseBuilder; enum NameType { METRIC_NAMES, TAG_KEYS, TAG_VALUES } @Path("/api/v1") public class MetricsResource implements KairosMetricReporter { public static final Logger logger = LoggerFactory.getLogger(MetricsResource.class); public static final String QUERY_TIME = "kairosdb.http.query_time"; public static final String REQUEST_TIME = "kairosdb.http.request_time"; public static final String INGEST_COUNT = "kairosdb.http.ingest_count"; public static final String INGEST_TIME = "kairosdb.http.ingest_time"; public static final String QUERY_URL = "/datapoints/query"; private final KairosDatastore datastore; private final Map<String, DataFormatter> formatters = new HashMap<>(); private final QueryParser queryParser; private final AggregatorFactory aggregatorFactory; //Used for parsing incoming metrics private final Gson gson; //These two are used to track rate of ingestion private final AtomicInteger m_ingestedDataPoints = new AtomicInteger(); private final AtomicInteger m_ingestTime = new AtomicInteger(); private final KairosDataPointFactory m_kairosDataPointFactory; @Inject private LongDataPointFactory m_longDataPointFactory = new LongDataPointFactoryImpl(); @Inject private StringDataPointFactory m_stringDataPointFactory = new StringDataPointFactory(); @Inject(optional=true) @Named("kairosdb.log.queries.enable") private boolean m_logQueries = false; @Inject(optional=true) @Named("kairosdb.log.queries.ttl") private int m_logQueriesTtl = 86400; @Inject(optional=true) @Named("kairosdb.log.queries.greater_than") private int m_logQueriesLongerThan = 60; @Inject @Named("HOSTNAME") private String hostName = "localhost"; @Inject public MetricsResource(KairosDatastore datastore, QueryParser queryParser, KairosDataPointFactory dataPointFactory, AggregatorFactory aggregatorFactory) { this.datastore = checkNotNull(datastore); this.queryParser = checkNotNull(queryParser); this.aggregatorFactory = checkNotNull(aggregatorFactory); m_kairosDataPointFactory = dataPointFactory; formatters.put("json", new JsonFormatter()); GsonBuilder builder = new GsonBuilder(); gson = builder.create(); } public static ResponseBuilder setHeaders(ResponseBuilder responseBuilder) { responseBuilder.header("Access-Control-Allow-Origin", "*"); responseBuilder.header("Pragma", "no-cache"); responseBuilder.header("Cache-Control", "no-cache"); responseBuilder.header("Expires", 0); return (responseBuilder); } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/version") public Response corsPreflightVersion(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/version") public Response getVersion() { Package thisPackage = getClass().getPackage(); String versionString = thisPackage.getImplementationTitle() + " " + thisPackage.getImplementationVersion(); ResponseBuilder responseBuilder = Response.status(Response.Status.OK).entity("{\"version\": \"" + versionString + "\"}"); setHeaders(responseBuilder); return responseBuilder.build(); } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/metricnames") public Response corsPreflightMetricNames(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/metricnames") public Response getMetricNames() { return executeNameQuery(NameType.METRIC_NAMES); } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/tagnames") public Response corsPreflightTagNames(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/tagnames") public Response getTagNames() { return executeNameQuery(NameType.TAG_KEYS); } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/tagvalues") public Response corsPreflightTagValues(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/tagvalues") public Response getTagValues() { return executeNameQuery(NameType.TAG_VALUES); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/aggregators") public Response getAggregators() { ImmutableList<AggregatorMetadata> aggregatorMetadata = aggregatorFactory.getAggregatorMetadata(); ResponseBuilder responseBuilder = Response.status(Response.Status.OK).entity(gson.toJson(aggregatorMetadata)); setHeaders(responseBuilder); return responseBuilder.build(); } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints") public Response corsPreflightDataPoints(@HeaderParam("Access-Control-Request-Headers") String requestHeaders, @HeaderParam("Access-Control-Request-Method") String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @POST @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Consumes("application/gzip") @Path("/datapoints") public Response addGzip(InputStream gzip) { GZIPInputStream gzipInputStream; try { gzipInputStream = new GZIPInputStream(gzip); } catch (IOException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } return (add(gzipInputStream)); } @POST @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints") public Response add(InputStream json) { try { DataPointsParser parser = new DataPointsParser(datastore, new InputStreamReader(json, "UTF-8"), gson, m_kairosDataPointFactory); ValidationErrors validationErrors = parser.parse(); m_ingestedDataPoints.addAndGet(parser.getDataPointCount()); m_ingestTime.addAndGet(parser.getIngestTime()); if (!validationErrors.hasErrors()) return setHeaders(Response.status(Response.Status.NO_CONTENT)).build(); else { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); for (String errorMessage : validationErrors.getErrors()) { builder.addError(errorMessage); } return builder.build(); } } catch (JsonIOException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (JsonSyntaxException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (MalformedJsonException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (Exception e) { logger.error("Failed to add metric.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (OutOfMemoryError e) { logger.error("Out of memory error.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints/query/tags") public Response corsPreflightQueryTags(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @POST @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints/query/tags") public Response getMeta(String json) { checkNotNull(json); logger.debug(json); try { File respFile = File.createTempFile("kairos", ".json", new File(datastore.getCacheDir())); BufferedWriter writer = new BufferedWriter(new FileWriter(respFile)); JsonResponse jsonResponse = new JsonResponse(writer); jsonResponse.begin(); List<QueryMetric> queries = queryParser.parseQueryMetric(json); for (QueryMetric query : queries) { List<DataPointGroup> result = datastore.queryTags(query); try { jsonResponse.formatQuery(result, false, -1); } finally { for (DataPointGroup dataPointGroup : result) { dataPointGroup.close(); } } } jsonResponse.end(); writer.flush(); writer.close(); ResponseBuilder responseBuilder = Response.status(Response.Status.OK).entity( new FileStreamingOutput(respFile)); setHeaders(responseBuilder); return responseBuilder.build(); } catch (JsonSyntaxException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (QueryException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (BeanValidationException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addErrors(e.getErrorMessages()).build(); } catch (MemoryMonitorException e) { logger.error("Query failed.", e); System.gc(); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (Exception e) { logger.error("Query failed.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (OutOfMemoryError e) { logger.error("Out of memory error.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } } /** Information for this endpoint was taken from https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS. <p/> <p/>Response to a cors preflight request to access data. */ @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path(QUERY_URL) public Response corsPreflightQuery(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @GET @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path(QUERY_URL) public Response getQuery(@QueryParam("query") String json, @Context HttpServletRequest request) throws Exception { return runQuery(json, request.getRemoteAddr()); } @POST @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path(QUERY_URL) public Response postQuery(String json, @Context HttpServletRequest request) throws Exception { return runQuery(json, request.getRemoteAddr()); } public Response runQuery(String json, String remoteAddr) throws Exception { logger.debug(json); ThreadReporter.setReportTime(System.currentTimeMillis()); ThreadReporter.addTag("host", hostName); try { if (json == null) throw new BeanValidationException(new QueryParser.SimpleConstraintViolation("query json", "must not be null or empty"), ""); File respFile = File.createTempFile("kairos", ".json", new File(datastore.getCacheDir())); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(respFile), "UTF-8")); JsonResponse jsonResponse = new JsonResponse(writer); jsonResponse.begin(); List<QueryMetric> queries = queryParser.parseQueryMetric(json); int queryCount = 0; for (QueryMetric query : queries) { queryCount++; ThreadReporter.addTag("metric_name", query.getName()); ThreadReporter.addTag("query_index", String.valueOf(queryCount)); DatastoreQuery dq = datastore.createQuery(query); long startQuery = System.currentTimeMillis(); try { List<DataPointGroup> results = dq.execute(); jsonResponse.formatQuery(results, query.isExcludeTags(), dq.getSampleSize()); ThreadReporter.addDataPoint(QUERY_TIME, System.currentTimeMillis() - startQuery); } finally { dq.close(); } } jsonResponse.end(); writer.flush(); writer.close(); ThreadReporter.clearTags(); ThreadReporter.addTag("host", hostName); //write metrics for query logging long queryTime = System.currentTimeMillis() - ThreadReporter.getReportTime(); if (m_logQueries && ((queryTime/1000) >= m_logQueriesLongerThan)) { ThreadReporter.addDataPoint("kairosdb.log.query.remote_address", remoteAddr, m_logQueriesTtl); ThreadReporter.addDataPoint("kairosdb.log.query.json", json, m_logQueriesTtl); } ThreadReporter.addTag("request", QUERY_URL); ThreadReporter.addDataPoint(REQUEST_TIME, queryTime); ThreadReporter.submitData(m_longDataPointFactory, m_stringDataPointFactory, datastore); ResponseBuilder responseBuilder = Response.status(Response.Status.OK).entity( new FileStreamingOutput(respFile)); setHeaders(responseBuilder); return responseBuilder.build(); } catch (JsonSyntaxException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (QueryException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (BeanValidationException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addErrors(e.getErrorMessages()).build(); } catch (MemoryMonitorException e) { logger.error("Query failed.", e); Thread.sleep(1000); System.gc(); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (IOException e) { logger.error("Failed to open temp folder " + datastore.getCacheDir(), e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (Exception e) { logger.error("Query failed.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (OutOfMemoryError e) { logger.error("Out of memory error.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } finally { ThreadReporter.clear(); } } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints/delete") public Response corsPreflightDelete(@HeaderParam("Access-Control-Request-Headers") final String requestHeaders, @HeaderParam("Access-Control-Request-Method") final String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @POST @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/datapoints/delete") public Response delete(String json) throws Exception { checkNotNull(json); logger.debug(json); try { List<QueryMetric> queries = queryParser.parseQueryMetric(json); for (QueryMetric query : queries) { datastore.delete(query); } return setHeaders(Response.status(Response.Status.NO_CONTENT)).build(); } catch (JsonSyntaxException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (QueryException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addError(e.getMessage()).build(); } catch (BeanValidationException e) { JsonResponseBuilder builder = new JsonResponseBuilder(Response.Status.BAD_REQUEST); return builder.addErrors(e.getErrorMessages()).build(); } catch (MemoryMonitorException e) { logger.error("Query failed.", e); System.gc(); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (Exception e) { logger.error("Delete failed.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } catch (OutOfMemoryError e) { logger.error("Out of memory error.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } } public static ResponseBuilder getCorsPreflightResponseBuilder(final String requestHeaders, final String requestMethod) { ResponseBuilder responseBuilder = Response.status(Response.Status.OK); responseBuilder.header("Access-Control-Allow-Origin", "*"); responseBuilder.header("Access-Control-Allow-Headers", requestHeaders); responseBuilder.header("Access-Control-Max-Age", "86400"); // Cache for one day if (requestMethod != null) { responseBuilder.header("Access-Control-Allow_Method", requestMethod); } return responseBuilder; } @OPTIONS @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/metric/{metricName}") public Response corsPreflightMetricDelete(@HeaderParam("Access-Control-Request-Headers") String requestHeaders, @HeaderParam("Access-Control-Request-Method") String requestMethod) { ResponseBuilder responseBuilder = getCorsPreflightResponseBuilder(requestHeaders, requestMethod); return (responseBuilder.build()); } @DELETE @Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8") @Path("/metric/{metricName}") public Response metricDelete(@PathParam("metricName") String metricName) throws Exception { try { QueryMetric query = new QueryMetric(Long.MIN_VALUE, Long.MAX_VALUE, 0, metricName); datastore.delete(query); return setHeaders(Response.status(Response.Status.NO_CONTENT)).build(); } catch (Exception e) { logger.error("Delete failed.", e); return setHeaders(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new ErrorResponse(e.getMessage()))).build(); } } private Response executeNameQuery(NameType type) { try { Iterable<String> values = null; switch (type) { case METRIC_NAMES: values = datastore.getMetricNames(); break; case TAG_KEYS: values = datastore.getTagNames(); break; case TAG_VALUES: values = datastore.getTagValues(); break; } DataFormatter formatter = formatters.get("json"); ResponseBuilder responseBuilder = Response.status(Response.Status.OK).entity( new ValuesStreamingOutput(formatter, values)); setHeaders(responseBuilder); return responseBuilder.build(); } catch (Exception e) { logger.error("Failed to get " + type, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( new ErrorResponse(e.getMessage())).build(); } } @Override public List<DataPointSet> getMetrics(long now) { int time = m_ingestTime.getAndSet(0); int count = m_ingestedDataPoints.getAndSet(0); if (count == 0) return Collections.emptyList(); DataPointSet dpsCount = new DataPointSet(INGEST_COUNT); DataPointSet dpsTime = new DataPointSet(INGEST_TIME); dpsCount.addTag("host", hostName); dpsTime.addTag("host", hostName); dpsCount.addDataPoint(m_longDataPointFactory.createDataPoint(now, count)); dpsTime.addDataPoint(m_longDataPointFactory.createDataPoint(now, time)); List<DataPointSet> ret = new ArrayList<>(); ret.add(dpsCount); ret.add(dpsTime); return ret; } public static class ValuesStreamingOutput implements StreamingOutput { private DataFormatter m_formatter; private Iterable<String> m_values; public ValuesStreamingOutput(DataFormatter formatter, Iterable<String> values) { m_formatter = formatter; m_values = values; } @SuppressWarnings("ResultOfMethodCallIgnored") public void write(OutputStream output) throws IOException, WebApplicationException { Writer writer = new OutputStreamWriter(output, "UTF-8"); try { m_formatter.format(writer, m_values); } catch (FormatterException e) { logger.error("Description of what failed:", e); } writer.flush(); } } public static class FileStreamingOutput implements StreamingOutput { private File m_responseFile; public FileStreamingOutput(File responseFile) { m_responseFile = responseFile; } @SuppressWarnings("ResultOfMethodCallIgnored") @Override public void write(OutputStream output) throws IOException, WebApplicationException { try { InputStream reader = new FileInputStream(m_responseFile); byte[] buffer = new byte[1024]; int size; while ((size = reader.read(buffer)) != -1) { output.write(buffer, 0, size); } reader.close(); output.flush(); } finally { m_responseFile.delete(); } } } }