All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.server.rest.transactional.ExecutionResultSerializer Maven / Gradle / Ivy

There is a newer version: 5.26.1
Show newest version
/*
 * Copyright (c) 2002-2016 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.server.rest.transactional;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;

import org.neo4j.graphdb.ExecutionPlanDescription;
import org.neo4j.graphdb.InputPosition;
import org.neo4j.graphdb.Notification;
import org.neo4j.graphdb.QueryStatistics;
import org.neo4j.graphdb.Result;
import org.neo4j.helpers.Exceptions;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;
import org.neo4j.server.rest.repr.util.RFC1123;
import org.neo4j.server.rest.transactional.error.Neo4jError;

import static org.neo4j.server.rest.domain.JsonHelper.writeValue;

/**
 * Writes directly to an output stream, therefore implicitly stateful. Methods must be invoked in the correct
 * order, as follows:
 * 
    *
  • {@link #transactionCommitUri(URI) transactionId}{@code ?}
  • *
  • {@link #statementResult(org.neo4j.graphdb.Result, boolean, ResultDataContent...) statementResult}{@code *}
  • *
  • {@link #errors(Iterable) errors}{@code ?}
  • *
  • {@link #transactionStatus(long expiryDate)}{@code ?}
  • *
  • {@link #finish() finish}
  • *
*

* Where {@code ?} means invoke at most once, and {@code *} means invoke zero or more times. */ public class ExecutionResultSerializer { public ExecutionResultSerializer( OutputStream output, URI baseUri, LogProvider logProvider, TransitionalPeriodTransactionMessContainer container ) { this.baseUri = baseUri; this.log = logProvider.getLog( getClass() ); this.container = container; JSON_FACTORY.setCodec( new Neo4jJsonCodec( container ) ); JsonGenerator generator = null; try { generator = JSON_FACTORY.createJsonGenerator( output ); } catch ( IOException e ) { loggedIOException( e ); } this.out = generator; } /** * Will always get called at most once, and is the first method to get called. This method is not allowed * to throw exceptions. If there are network errors or similar, the handler should take appropriate action, * but never fail this method. */ public void transactionCommitUri( URI commitUri ) { try { ensureDocumentOpen(); out.writeStringField( "commit", commitUri.toString() ); } catch ( IOException e ) { loggedIOException( e ); } } /** * Will get called at most once per statement. Throws IOException so that upstream executor can decide whether * to execute further statements. */ public void statementResult( Result result, boolean includeStats, ResultDataContent... resultDataContents ) throws IOException { try { ensureResultsFieldOpen(); out.writeStartObject(); try { Iterable columns = result.columns(); writeColumns( columns ); writeRows( columns, result, configureWriters( resultDataContents ) ); if ( includeStats ) { writeStats( result.getQueryStatistics() ); } if ( result.getQueryExecutionType().requestedExecutionPlanDescription() ) { writeRootPlanDescription( result.getExecutionPlanDescription() ); } } finally { out.writeEndObject(); // } } catch ( IOException e ) { throw loggedIOException( e ); } } public void notifications( Iterable notifications ) throws IOException { //don't add anything if notifications are empty if ( !notifications.iterator().hasNext() ) return; try { ensureResultsFieldClosed(); out.writeArrayFieldStart( "notifications" ); try { for ( Notification notification : notifications ) { out.writeStartObject(); try { out.writeStringField( "code", notification.getCode() ); out.writeStringField( "severity", notification.getSeverity().toString() ); out.writeStringField( "title", notification.getTitle() ); out.writeStringField( "description", notification.getDescription() ); writePosition( notification.getPosition() ); } finally { out.writeEndObject(); } } } finally { out.writeEndArray(); } } catch ( IOException e ) { throw loggedIOException( e ); } } private void writePosition( InputPosition position ) throws IOException { //do not add position if empty if ( position == InputPosition.empty ) return; out.writeObjectFieldStart( "position" ); try { out.writeNumberField( "offset", position.getOffset() ); out.writeNumberField( "line", position.getLine() ); out.writeNumberField( "column", position.getColumn() ); } finally { out.writeEndObject(); } } private void writeStats( QueryStatistics stats ) throws IOException { out.writeObjectFieldStart( "stats" ); try { out.writeBooleanField( "contains_updates", stats.containsUpdates() ); out.writeNumberField( "nodes_created", stats.getNodesCreated() ); out.writeNumberField( "nodes_deleted", stats.getNodesDeleted() ); out.writeNumberField( "properties_set", stats.getPropertiesSet() ); out.writeNumberField( "relationships_created", stats.getRelationshipsCreated() ); out.writeNumberField( "relationship_deleted", stats.getRelationshipsDeleted() ); out.writeNumberField( "labels_added", stats.getLabelsAdded() ); out.writeNumberField( "labels_removed", stats.getLabelsRemoved() ); out.writeNumberField( "indexes_added", stats.getIndexesAdded() ); out.writeNumberField( "indexes_removed", stats.getIndexesRemoved() ); out.writeNumberField( "constraints_added", stats.getConstraintsAdded() ); out.writeNumberField( "constraints_removed", stats.getConstraintsRemoved() ); } finally { out.writeEndObject(); } } private void writeRootPlanDescription( ExecutionPlanDescription planDescription ) throws IOException { out.writeObjectFieldStart( "plan" ); try { out.writeObjectFieldStart( "root" ); try { writePlanDescriptionObjectBody( planDescription ); } finally { out.writeEndObject(); } } finally { out.writeEndObject(); } } private void writePlanDescriptionObjectBody( ExecutionPlanDescription planDescription ) throws IOException { out.writeStringField( "operatorType", planDescription.getName() ); writePlanArgs( planDescription ); writePlanIdentifiers( planDescription ); List children = planDescription.getChildren(); out.writeArrayFieldStart( "children" ); try { for (ExecutionPlanDescription child : children ) { out.writeStartObject(); try { writePlanDescriptionObjectBody( child ); } finally { out.writeEndObject(); } } } finally { out.writeEndArray(); } } private void writePlanArgs( ExecutionPlanDescription planDescription ) throws IOException { for ( Map.Entry entry : planDescription.getArguments().entrySet() ) { String fieldName = entry.getKey(); Object fieldValue = entry.getValue(); out.writeFieldName( fieldName ); writeValue( out, fieldValue ); } } private void writePlanIdentifiers( ExecutionPlanDescription planDescription ) throws IOException { out.writeArrayFieldStart( "identifiers" ); for ( String id : planDescription.getIdentifiers() ) { out.writeString( id ); } out.writeEndArray(); } /** * Will get called once if any errors occurred, after {@link #statementResult(org.neo4j.graphdb.Result, boolean, ResultDataContent...)} statementResults} * has been called This method is not allowed to throw exceptions. If there are network errors or similar, the * handler should take appropriate action, but never fail this method. * @param errors the errors to write */ public void errors( Iterable errors ) { try { ensureDocumentOpen(); ensureResultsFieldClosed(); out.writeArrayFieldStart( "errors" ); try { for ( Neo4jError error : errors ) { try { out.writeStartObject(); out.writeObjectField( "code", error.status().code().serialize() ); out.writeObjectField( "message", error.getMessage() ); if ( error.shouldSerializeStackTrace() ) { out.writeObjectField( "stackTrace", error.getStackTraceAsString() ); } } finally { out.writeEndObject(); } } } finally { out.writeEndArray(); currentState = State.ERRORS_WRITTEN; } } catch ( IOException e ) { loggedIOException( e ); } } public void transactionStatus( long expiryDate ) { try { ensureDocumentOpen(); ensureResultsFieldClosed(); out.writeObjectFieldStart( "transaction" ); out.writeStringField( "expires", RFC1123.formatDate( new Date( expiryDate ) ) ); out.writeEndObject(); } catch ( IOException e ) { loggedIOException( e ); } } /** * This method must be called exactly once, and no method must be called after calling this method. * This method may not fail. */ public void finish() { try { ensureDocumentOpen(); if ( currentState != State.ERRORS_WRITTEN ) { errors( Collections.emptyList() ); } out.writeEndObject(); out.flush(); } catch ( IOException e ) { loggedIOException( e ); } } private ResultDataContentWriter configureWriters( ResultDataContent[] specifiers ) { if ( specifiers == null || specifiers.length == 0 ) { return ResultDataContent.row.writer( baseUri ); // default } if ( specifiers.length == 1 ) { return specifiers[0].writer( baseUri ); } ResultDataContentWriter[] writers = new ResultDataContentWriter[specifiers.length]; for ( int i = 0; i < specifiers.length; i++ ) { writers[i] = specifiers[i].writer( baseUri ); } return new AggregatingWriter( writers ); } private enum State { EMPTY, DOCUMENT_OPEN, RESULTS_OPEN, RESULTS_CLOSED, ERRORS_WRITTEN } private State currentState = State.EMPTY; private static final JsonFactory JSON_FACTORY = new JsonFactory().disable( JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM ); private final JsonGenerator out; private final URI baseUri; private final Log log; private final TransitionalPeriodTransactionMessContainer container; private void ensureDocumentOpen() throws IOException { if ( currentState == State.EMPTY ) { out.writeStartObject(); currentState = State.DOCUMENT_OPEN; } } private void ensureResultsFieldOpen() throws IOException { ensureDocumentOpen(); if ( currentState == State.DOCUMENT_OPEN ) { out.writeArrayFieldStart( "results" ); currentState = State.RESULTS_OPEN; } } private void ensureResultsFieldClosed() throws IOException { ensureResultsFieldOpen(); if ( currentState == State.RESULTS_OPEN ) { out.writeEndArray(); currentState = State.RESULTS_CLOSED; } } private void writeRows( final Iterable columns, Result data, final ResultDataContentWriter writer ) throws IOException { out.writeArrayFieldStart( "data" ); try { data.accept( row -> { out.writeStartObject(); try { writer.write( out, columns, row, TransactionStateChecker.create( container ) ); } finally { out.writeEndObject(); } return true; } ); } finally { out.writeEndArray(); // } } private void writeColumns( Iterable columns ) throws IOException { try { out.writeArrayFieldStart( "columns" ); for ( String key : columns ) { out.writeString( key ); } } finally { out.writeEndArray(); // } } private IOException loggedIOException( IOException exception ) { if(Exceptions.contains(exception, "Broken pipe", IOException.class )) { log.error( "Unable to reply to request, because the client has closed the connection (Broken pipe)." ); } else { log.error( "Failed to generate JSON output.", exception ); } return exception; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy