class JobDetailsHandler extends AbstractExecutionGraphRequestHandler {

	public static String createJobDetailsJson(AccessExecutionGraph graph, @Nullable MetricFetcher fetcher) throws IOException {
		final StringWriter writer = new StringWriter();
		final JsonGenerator gen = JsonFactory.JACKSON_FACTORY.createGenerator(writer);

		final long now = System.currentTimeMillis();

		gen.writeStartObject();

		// basic info
		gen.writeStringField("jid", graph.getJobID().toString());
		gen.writeStringField("name", graph.getJobName());
		gen.writeBooleanField("isStoppable", graph.isStoppable());
		gen.writeStringField("state", graph.getState().name());

		// times and duration
		final long jobStartTime = graph.getStatusTimestamp(JobStatus.CREATED);
		final long jobEndTime = graph.getState().isGloballyTerminalState() ?
				graph.getStatusTimestamp(graph.getState()) : -1L;
		gen.writeNumberField("start-time", jobStartTime);
		gen.writeNumberField("end-time", jobEndTime);
		gen.writeNumberField("duration", (jobEndTime > 0 ? jobEndTime : now) - jobStartTime);
		gen.writeNumberField("now", now);

		// timestamps
		gen.writeObjectFieldStart("timestamps");
		for (JobStatus status : JobStatus.values()) {
			gen.writeNumberField(status.name(), graph.getStatusTimestamp(status));
		}
		gen.writeEndObject();

		// job vertices
		int[] jobVerticesPerState = new int[ExecutionState.values().length];
		gen.writeArrayFieldStart("vertices");

		for (AccessExecutionJobVertex ejv : graph.getVerticesTopologically()) {
			int[] tasksPerState = new int[ExecutionState.values().length];
			long startTime = Long.MAX_VALUE;
			long endTime = 0;
			boolean allFinished = true;

			for (AccessExecutionVertex vertex : ejv.getTaskVertices()) {
				final ExecutionState state = vertex.getExecutionState();
				tasksPerState[state.ordinal()]++;

				// take the earliest start time
				long started = vertex.getStateTimestamp(ExecutionState.DEPLOYING);
				if (started > 0) {
					startTime = Math.min(startTime, started);
				}

				allFinished &= state.isTerminal();
				endTime = Math.max(endTime, vertex.getStateTimestamp(state));
			}

			long duration;
			if (startTime < Long.MAX_VALUE) {
				if (allFinished) {
					duration = endTime - startTime;
				}
				else {
					endTime = -1L;
					duration = now - startTime;
				}
			}
			else {
				startTime = -1L;
				endTime = -1L;
				duration = -1L;
			}

			ExecutionState jobVertexState =
					ExecutionJobVertex.getAggregateJobVertexState(tasksPerState, ejv.getParallelism());
			jobVerticesPerState[jobVertexState.ordinal()]++;

			gen.writeStartObject();
			gen.writeStringField("id", ejv.getJobVertexId().toString());
			gen.writeStringField("name", ejv.getName());
			gen.writeNumberField("parallelism", ejv.getParallelism());
			gen.writeStringField("status", jobVertexState.name());

			gen.writeNumberField("start-time", startTime);
			gen.writeNumberField("end-time", endTime);
			gen.writeNumberField("duration", duration);

			gen.writeObjectFieldStart("tasks");
			for (ExecutionState state : ExecutionState.values()) {
				gen.writeNumberField(state.name(), tasksPerState[state.ordinal()]);
			}
			gen.writeEndObject();

			MutableIOMetrics counts = new MutableIOMetrics();

			for (AccessExecutionVertex vertex : ejv.getTaskVertices()) {
				counts.addIOMetrics(
					vertex.getCurrentExecutionAttempt(),
					fetcher,
					graph.getJobID().toString(),
					ejv.getJobVertexId().toString());
			}

			counts.writeIOMetricsAsJson(gen);

			gen.writeEndObject();
		}
		gen.writeEndArray();

		gen.writeObjectFieldStart("status-counts");
		for (ExecutionState state : ExecutionState.values()) {
			gen.writeNumberField(state.name(), jobVerticesPerState[state.ordinal()]);
		}
		gen.writeEndObject();

		gen.writeFieldName("plan");
		gen.writeRawValue(graph.getJsonPlan());

		gen.writeEndObject();

		gen.close();
		return writer.toString();
	}

	public  JobDetailsHandler(ExecutionGraphCache executionGraphHolder, Executor executor, MetricFetcher fetcher);

}