Skip to content

Commit b773784

Browse files
Decouple TestkitState from CommandProcessor. (#993)
This commit remoces the command processor from the TestKit state. The processor is supposed to use that state, not to be part of it. However, some requests, like `NewDriver` need the processor to trigger further state. Therefor the processor is now provided as injectable value via Jackson, so that any request can indicate that it needs a processor via a constructor argument.
1 parent c93587e commit b773784

File tree

12 files changed

+324
-281
lines changed

12 files changed

+324
-281
lines changed

testkit-backend/src/main/java/neo4j/org/testkit/backend/BackendServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private void handleClient( Socket clientSocket )
5050
System.out.println( "Handling connection from: " + clientSocket.getRemoteSocketAddress() );
5151
BufferedReader in = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() ) );
5252
BufferedWriter out = new BufferedWriter( new OutputStreamWriter( clientSocket.getOutputStream() ) );
53-
CommandProcessor commandProcessor = new CommandProcessor( in, out );
53+
CommandProcessor commandProcessor = new DefaultCommandProcessor( in, out );
5454

5555
boolean cont = true;
5656
while ( cont )

testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java

Lines changed: 28 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -18,227 +18,42 @@
1818
*/
1919
package neo4j.org.testkit.backend;
2020

21-
import com.fasterxml.jackson.core.JsonProcessingException;
2221
import com.fasterxml.jackson.databind.DeserializationFeature;
22+
import com.fasterxml.jackson.databind.InjectableValues;
2323
import com.fasterxml.jackson.databind.ObjectMapper;
2424
import neo4j.org.testkit.backend.messages.TestkitModule;
25-
import neo4j.org.testkit.backend.messages.requests.TestkitRequest;
26-
import neo4j.org.testkit.backend.messages.responses.BackendError;
27-
import neo4j.org.testkit.backend.messages.responses.DriverError;
28-
import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
2925

30-
import java.io.BufferedReader;
31-
import java.io.BufferedWriter;
32-
import java.io.IOException;
33-
import java.io.UncheckedIOException;
26+
import java.util.Collections;
3427

35-
import org.neo4j.driver.exceptions.Neo4jException;
36-
import org.neo4j.driver.exceptions.UntrustedServerException;
37-
import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl;
38-
39-
public class CommandProcessor
28+
public interface CommandProcessor
4029
{
41-
private final TestkitState testkitState;
42-
43-
private final ObjectMapper objectMapper = new ObjectMapper();
30+
/**
31+
* Used in ObjectMapper's injectable values.
32+
*/
33+
String COMMAND_PROCESSOR_ID = "commandProcessor";
34+
35+
/**
36+
* Reads one request and writes the response. Returns false when not able to read anymore.
37+
*
38+
* @return False when there's nothing to read anymore.
39+
*/
40+
boolean process();
41+
42+
/**
43+
* Create a new {@link ObjectMapper} configured with the appropriate testkit module and an injectable {@link CommandProcessor}.
44+
* @param processor The processor supposed to be injectable
45+
* @return A reusable object mapper instance
46+
*/
47+
static ObjectMapper newObjectMapperFor(CommandProcessor processor) {
48+
49+
final ObjectMapper objectMapper = new ObjectMapper();
4450

45-
private final BufferedReader in;
46-
private final BufferedWriter out;
47-
48-
public CommandProcessor( BufferedReader in, BufferedWriter out )
49-
{
50-
this.in = in;
51-
this.out = out;
52-
configureObjectMapper();
53-
this.testkitState = new TestkitState( this::writeResponse, this::process );
54-
}
55-
56-
private void configureObjectMapper()
57-
{
5851
TestkitModule testkitModule = new TestkitModule();
59-
this.objectMapper.registerModule( testkitModule );
60-
this.objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
61-
}
62-
63-
private String readLine()
64-
{
65-
try
66-
{
67-
return this.in.readLine();
68-
}
69-
catch ( IOException e )
70-
{
71-
throw new UncheckedIOException( e );
72-
}
73-
}
74-
75-
private void write( String s )
76-
{
77-
try
78-
{
79-
this.out.write( s );
80-
}
81-
catch ( IOException e )
82-
{
83-
throw new UncheckedIOException( e );
84-
}
85-
}
86-
87-
// Logs to frontend
88-
private void log( String s )
89-
{
90-
try
91-
{
92-
this.out.write( s + "\n" );
93-
this.out.flush();
94-
}
95-
catch ( IOException e )
96-
{
97-
}
98-
System.out.println( s );
99-
}
100-
101-
private void flush()
102-
{
103-
try
104-
{
105-
this.out.flush();
106-
}
107-
catch ( IOException e )
108-
{
109-
throw new UncheckedIOException( e );
110-
}
111-
}
112-
113-
// Reads one request and writes the response. Returns false when not able to read anymore.
114-
public boolean process()
115-
{
116-
boolean inRequest = false;
117-
StringBuilder request = new StringBuilder();
118-
119-
log( "Waiting for request" );
120-
121-
while ( true )
122-
{
123-
String currentLine = readLine();
124-
// End of stream
125-
if ( currentLine == null )
126-
{
127-
return false;
128-
}
129-
130-
if ( currentLine.equals( "#request begin" ) )
131-
{
132-
inRequest = true;
133-
}
134-
else if ( currentLine.equals( "#request end" ) )
135-
{
136-
if ( !inRequest )
137-
{
138-
throw new RuntimeException( "Request end not expected" );
139-
}
140-
try
141-
{
142-
processRequest( request.toString() );
143-
}
144-
catch ( Exception e )
145-
{
146-
if ( e instanceof Neo4jException )
147-
{
148-
// Error to track
149-
String id = testkitState.newId();
150-
testkitState.getErrors().put( id, (Neo4jException) e );
151-
writeResponse( driverError( id, (Neo4jException) e ) );
152-
System.out.println( "Neo4jException: " + e );
153-
}
154-
else if ( isConnectionPoolClosedException( e ) || e instanceof UntrustedServerException )
155-
{
156-
String id = testkitState.newId();
157-
DriverError driverError = DriverError.builder()
158-
.data(
159-
DriverError.DriverErrorBody.builder()
160-
.id( id )
161-
.errorType( e.getClass().getName() )
162-
.msg( e.getMessage() )
163-
.build()
164-
)
165-
.build();
166-
writeResponse( driverError );
167-
}
168-
else
169-
{
170-
// Unknown error, interpret this as a backend error.
171-
// Report to frontend and rethrow, note that if socket been
172-
// closed the writing will throw itself...
173-
writeResponse( BackendError.builder().data( BackendError.BackendErrorBody.builder().msg( e.toString() ).build() ).build() );
174-
// This won't print if there was an IO exception since line above will rethrow
175-
e.printStackTrace();
176-
throw e;
177-
}
178-
}
179-
return true;
180-
}
181-
else
182-
{
183-
if ( !inRequest )
184-
{
185-
throw new RuntimeException( "Command Received whilst not in request" );
186-
}
187-
request.append( currentLine );
188-
}
189-
}
190-
}
191-
192-
private DriverError driverError( String id, Neo4jException e )
193-
{
194-
return DriverError.builder().data(
195-
DriverError.DriverErrorBody.builder()
196-
.id( id )
197-
.errorType( e.getClass().getName() )
198-
.code( e.code() )
199-
.msg( e.getMessage() )
200-
.build() )
201-
.build();
202-
}
203-
204-
public void processRequest( String request )
205-
{
206-
System.out.println( "request = " + request + ", in = " + in + ", out = " + out );
207-
try
208-
{
209-
TestkitRequest testkitMessage = objectMapper.readValue( request, TestkitRequest.class );
210-
TestkitResponse response = testkitMessage.process( testkitState );
211-
if ( response != null )
212-
{
213-
writeResponse( response );
214-
}
215-
}
216-
catch ( IOException e )
217-
{
218-
throw new UncheckedIOException( e );
219-
}
220-
}
221-
222-
private void writeResponse( TestkitResponse response )
223-
{
224-
try
225-
{
226-
String responseStr = objectMapper.writeValueAsString( response );
227-
System.out.println("response = " + responseStr + ", in = " + in + ", out = " + out);
228-
write( "#response begin\n" );
229-
write( responseStr + "\n" );
230-
write( "#response end\n" );
231-
flush();
232-
}
233-
catch ( JsonProcessingException ex )
234-
{
235-
throw new RuntimeException( "Error writing response", ex );
236-
}
237-
}
52+
objectMapper.registerModule( testkitModule );
53+
objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
23854

239-
private boolean isConnectionPoolClosedException( Exception e )
240-
{
241-
return e instanceof IllegalStateException && e.getMessage() != null &&
242-
e.getMessage().equals( ConnectionPoolImpl.CONNECTION_POOL_CLOSED_ERROR_MESSAGE );
55+
InjectableValues injectableValues = new InjectableValues.Std( Collections.singletonMap( COMMAND_PROCESSOR_ID, processor ) );
56+
objectMapper.setInjectableValues( injectableValues );
57+
return objectMapper;
24358
}
24459
}

0 commit comments

Comments
 (0)