Skip to content

Commit 26227d3

Browse files
aclementghillert
authored andcommitted
Add tools endpoint for invoking taskdsl-graph conversion
- Convert to Resource responses - Make DSLMessages consistent - lower case start char and no trailing period - Enable TaskValidationProblem to format themselves for client transmission - Introduce `ToolsControllerTests` which tests tools endpoints
1 parent d102f2c commit 26227d3

File tree

15 files changed

+478
-50
lines changed

15 files changed

+478
-50
lines changed

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/DSLMessage.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,25 +93,25 @@ public enum DSLMessage {
9393
EXPECTED_WHITESPACE_AFTER_LABEL_COLON(ERROR, 147, "whitespace is expected after an app label"), //
9494
EXPECTED_STREAM_NAME_AFTER_LABEL_COLON(ERROR, 148, "stream name is expected after an app label"), //
9595
// These relate specifically to the composed task DSL
96-
TASK_DOUBLE_AND_REQUIRED(ERROR, 150, "task dsl flow requires a pair of &, not just one."), //
97-
TASK_DOUBLE_OR_REQUIRED(ERROR, 151, "task dsl requires a pair of '|'."), //
96+
TASK_DOUBLE_AND_REQUIRED(ERROR, 150, "task dsl flow requires a pair of &, not just one"), //
97+
TASK_DOUBLE_OR_REQUIRED(ERROR, 151, "task dsl requires a pair of '|'"), //
9898
TASK_HYPHEN_EXPECTED_USE_FOR_TRANSITION(ERROR, 152, "expected '->' and not just the hyphen"), //
9999
TASK_MISSING_TRANSITION_ARROW(ERROR, 153, "expected '->' to follow state when specifying transition"), //
100100
TASK_UNQUOTED_TRANSITION_CHECK_MUST_BE_NUMBER(ERROR, 154, "transition conditions must be quoted literals, numerics or '*' but ''{0}'' is not"), //
101101
TASK_NO_LABELS_ON_PARENS(ERROR, 155, "labels cannot be specified on parenthesized groups"), //
102-
TASK_NO_DOUBLE_LABELS(ERROR, 156, "Double labels are not supported"), //
103-
TASK_ARROW_SHOULD_BE_PRECEDED_BY_CODE(ERROR, 157, "Transition arrow must be preceeded by the exit code that should drive the transition"), //
104-
TASK_VALIDATION_SECONDARY_SEQUENCES_MUST_BE_NAMED(ERROR, 158, "Secondary sequences must have labels or are unreachable"), //
105-
TASK_VALIDATION_DUPLICATE_LABEL(ERROR, 159, "This label has already been defined"), //
106-
TASK_VALIDATION_TRANSITION_TARGET_LABEL_UNDEFINED(ERROR, 160, "Transition specifies an undefined label"), //
107-
TASK_ELEMENT_IN_COMPOSED_DEFINITION_DOES_NOT_EXIST(ERROR, 161, "Task in composed task definition does not exist"), //
108-
TASK_UNEXPECTED_DATA(ERROR, 162, "Unexpected data in task definition ''{0}''"), //
109-
TASK_MORE_INPUT(ERROR, 163, "After parsing a valid task, there is still more data: ''{0}''"), //
110-
TASK_VALIDATION_LABEL_CLASHES_WITH_TASKAPP_NAME(ERROR, 164, "The label clashes with an existing unlabeled task application name"), //
111-
TASK_VALIDATION_APP_NAME_CLASHES_WITH_LABEL(ERROR, 165, "The app name clashes with an existing label"), //
112-
TASK_VALIDATION_APP_NAME_ALREADY_IN_USE(ERROR, 166, "Duplicate app name. Use a label to ensure uniqueness"), //
113-
TASK_VALIDATION_SPLIT_WITH_ONE_FLOW(ERROR, 167, "Unnecessary use of split construct when only one flow to execute in parallel"), //
114-
TASK_ARGUMENTS_NOT_ALLOWED_UNLESS_IN_APP_MODE(ERROR, 168, "Arguments not allowed unless parser is in app mode"), //
102+
TASK_NO_DOUBLE_LABELS(ERROR, 156, "double labels are not supported"), //
103+
TASK_ARROW_SHOULD_BE_PRECEDED_BY_CODE(ERROR, 157, "transition arrow must be preceeded by the exit code that should drive the transition"), //
104+
TASK_VALIDATION_SECONDARY_SEQUENCES_MUST_BE_NAMED(ERROR, 158, "secondary sequences must have labels or are unreachable"), //
105+
TASK_VALIDATION_DUPLICATE_LABEL(ERROR, 159, "this label has already been defined"), //
106+
TASK_VALIDATION_TRANSITION_TARGET_LABEL_UNDEFINED(ERROR, 160, "transition specifies an undefined label"), //
107+
TASK_ELEMENT_IN_COMPOSED_DEFINITION_DOES_NOT_EXIST(ERROR, 161, "task in composed task definition does not exist"), //
108+
TASK_UNEXPECTED_DATA(ERROR, 162, "unexpected data in task definition ''{0}''"), //
109+
TASK_MORE_INPUT(ERROR, 163, "after parsing a valid task, there is still more data: ''{0}''"), //
110+
TASK_VALIDATION_LABEL_CLASHES_WITH_TASKAPP_NAME(ERROR, 164, "the label clashes with an existing unlabeled task application name"), //
111+
TASK_VALIDATION_APP_NAME_CLASHES_WITH_LABEL(ERROR, 165, "the app name clashes with an existing label"), //
112+
TASK_VALIDATION_APP_NAME_ALREADY_IN_USE(ERROR, 166, "duplicate app name. Use a label to ensure uniqueness"), //
113+
TASK_VALIDATION_SPLIT_WITH_ONE_FLOW(ERROR, 167, "unnecessary use of split construct when only one flow to execute in parallel"), //
114+
TASK_ARGUMENTS_NOT_ALLOWED_UNLESS_IN_APP_MODE(ERROR, 168, "arguments not allowed unless parser is in app mode"), //
115115
;
116116

117117
private Kind kind;

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/ParseException.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.cloud.dataflow.core.dsl;
1818

19+
import java.util.HashMap;
20+
import java.util.Map;
21+
1922
/**
2023
* Root exception for DSL parsing related exceptions. Rather than holding a
2124
* hard coded string indicating the problem, it records a message key and
@@ -95,4 +98,23 @@ public final int getPosition() {
9598
return position;
9699
}
97100

101+
/**
102+
* Produce a simple map of information about the exception that
103+
* can be sent to the client for display.
104+
* @return map of simple information including message and position
105+
*/
106+
public Map<String, Object> toExceptionDescriptor() {
107+
Map<String, Object> descriptor = new HashMap<>();
108+
String text = null;
109+
if (message != null) {
110+
text = message.formatMessage(position, inserts);
111+
}
112+
else {
113+
text = super.getMessage();
114+
}
115+
descriptor.put("message", text);
116+
descriptor.put("position", position);
117+
return descriptor;
118+
}
119+
98120
}

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/TaskValidationProblem.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.cloud.dataflow.core.dsl;
1818

19+
import java.util.HashMap;
20+
import java.util.Map;
21+
1922
/**
2023
* After parsing a task definition from a DSL string, the validation visitor may optionally run.
2124
* Even though it parses successfully there may be issues with how the definition is constructed. The
@@ -74,4 +77,17 @@ private int getStartOfLine(int position) {
7477
return 0;
7578
}
7679

80+
/**
81+
* Produce a simple map of information about the exception that
82+
* can be sent to the client for display.
83+
* @return map of simple information including message and position
84+
*/
85+
public Map<String, Object> toExceptionDescriptor() {
86+
Map<String, Object> descriptor = new HashMap<>();
87+
String text = message.formatMessage(offset);
88+
descriptor.put("message", text);
89+
descriptor.put("position", offset);
90+
return descriptor;
91+
}
92+
7793
}

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/graph/Graph.java

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -310,34 +310,17 @@ private int countLinksWithoutTransitions(List<Link> links) {
310310

311311
private void printNode(StringBuilder graphText, Node node, List<Node> unvisitedNodes) {
312312
unvisitedNodes.remove(node);
313-
// What to generate depends on whether it is a job definition or reference
314-
// if (node.metadata != null && node.metadata.containsKey(Node.METADATAKEY_JOBMODULENAME)) {
315-
// graphText.append(node.metadata.get(Node.METADATAKEY_JOBMODULENAME)).append(" ");
316-
// graphText.append(node.name).append(" ");
317-
//// if (node.properties != null) {
318-
//// int count = 0;
319-
//// for (Map.Entry<String, String> entry : node.properties.entrySet()) {
320-
//// if (count > 0) {
321-
//// graphText.append(" ");
322-
//// }
323-
//// graphText.append("--").append(entry.getKey()).append("=").append(entry.getValue());
324-
//// count++;
325-
//// }
326-
//// }
327-
// }
328-
// else {
329-
String nameInDSL = node.name;
330-
if (node.getLabel()!=null) {
331-
graphText.append(node.getLabel()).append(": ");
313+
String nameInDSL = node.name;
314+
if (node.getLabel() != null) {
315+
graphText.append(node.getLabel()).append(": ");
316+
}
317+
graphText.append(nameInDSL);
318+
if (node.properties != null) {
319+
for (Map.Entry<String, String> entry : node.properties.entrySet()) {
320+
graphText.append(" ");
321+
graphText.append("--").append(entry.getKey()).append("=").append(entry.getValue());
332322
}
333-
graphText.append(nameInDSL);
334-
// if (node.properties != null) {
335-
// for (Map.Entry<String, String> entry : node.properties.entrySet()) {
336-
// graphText.append(" ");
337-
// graphText.append("--").append(entry.getKey()).append("=").append(entry.getValue());
338-
// }
339-
// }
340-
// }
323+
}
341324
}
342325

343326
private void followLink(StringBuilder graphText, Link link, Node nodeToFinishFollowingAt,

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/graph/Link.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121

2222
import com.fasterxml.jackson.annotation.JsonIgnore;
2323
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
24+
import com.fasterxml.jackson.annotation.JsonInclude;
25+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
2426

2527
/**
2628
* Represents a link in a {@link Graph} object that Flo will display as a link.
2729
*
2830
* @author Andy Clement
2931
*/
3032
@JsonIgnoreProperties(ignoreUnknown = true)
33+
@JsonInclude(Include.NON_EMPTY)
3134
public class Link {
3235

3336
public final static String PROPERTY_TRANSITION_NAME = "transitionName";

spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/graph/Node.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@
2121

2222
import com.fasterxml.jackson.annotation.JsonIgnore;
2323
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
24+
import com.fasterxml.jackson.annotation.JsonInclude;
25+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
2426

2527
/**
2628
* Represents a node in a {@link Graph} object that Flo will display as a block.
2729
*
2830
* @author Andy Clement
2931
*/
3032
@JsonIgnoreProperties(ignoreUnknown = true)
33+
@JsonInclude(Include.NON_EMPTY)
3134
public class Node {
3235

33-
public static final String METADATA_LABEL = "composedtask.label";
36+
public static final String METADATA_LABEL = "label";
3437

3538
public String id;
3639

@@ -88,13 +91,15 @@ public boolean isSync() {
8891
return name.equals("SYNC");
8992
}
9093

94+
@JsonIgnore
9195
public void setLabel(String label) {
9296
if (metadata == null) {
9397
metadata = new HashMap<>();
9498
}
9599
metadata.put(METADATA_LABEL, label);
96100
}
97101

102+
@JsonIgnore
98103
public String getLabel() {
99104
if (metadata == null) {
100105
return null;

spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,8 @@ public void validator() {
784784
assertEquals(1,problems.size());
785785
assertEquals(DSLMessage.TASK_VALIDATION_SECONDARY_SEQUENCES_MUST_BE_NAMED,problems.get(0).getMessage());
786786
assertEquals(5,problems.get(0).getOffset());
787-
assertEquals("158E:(pos 5): Secondary sequences must have labels or are unreachable",problems.get(0).toString());
788-
assertEquals("158E:(pos 5): Secondary sequences must have labels or are unreachable\nappA;appB\n ^\n",problems.get(0).toStringWithContext());
787+
assertEquals("158E:(pos 5): secondary sequences must have labels or are unreachable",problems.get(0).toString());
788+
assertEquals("158E:(pos 5): secondary sequences must have labels or are unreachable\nappA;appB\n ^\n",problems.get(0).toStringWithContext());
789789

790790
validator.reset();
791791
ctn = parse("appA;foo: appB");
@@ -799,8 +799,8 @@ public void validator() {
799799
assertEquals(1,problems.size());
800800
assertEquals(DSLMessage.TASK_VALIDATION_SECONDARY_SEQUENCES_MUST_BE_NAMED,problems.get(0).getMessage());
801801
assertEquals(15,problems.get(0).getOffset());
802-
assertEquals("158E:(pos 15): Secondary sequences must have labels or are unreachable",problems.get(0).toString());
803-
assertEquals("158E:(pos 15): Secondary sequences must have labels or are unreachable\nappC\n^\n",problems.get(0).toStringWithContext());
802+
assertEquals("158E:(pos 15): secondary sequences must have labels or are unreachable",problems.get(0).toString());
803+
assertEquals("158E:(pos 15): secondary sequences must have labels or are unreachable\nappC\n^\n",problems.get(0).toStringWithContext());
804804

805805
validator.reset();
806806
ctn = parse("<appA>",false);
@@ -809,7 +809,7 @@ public void validator() {
809809
assertEquals(1,problems.size());
810810
assertEquals(DSLMessage.TASK_VALIDATION_SPLIT_WITH_ONE_FLOW,problems.get(0).getMessage());
811811
assertEquals(0,problems.get(0).getOffset());
812-
assertEquals("167E:(pos 0): Unnecessary use of split construct when only one flow to execute in parallel",problems.get(0).toString());
812+
assertEquals("167E:(pos 0): unnecessary use of split construct when only one flow to execute in parallel",problems.get(0).toString());
813813
}
814814

815815
@Test

spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,13 @@ public void index() throws Exception {
106106
linkWithRel("aggregate-counters").description("Provides the resource for dealing with aggregate counters"),
107107
linkWithRel("aggregate-counters/counter").description("Handle a specific aggregate counter"),
108108
linkWithRel("field-value-counters").description("Provides the resource for dealing with field-value-counters"),
109-
linkWithRel("field-value-counters/counter").description("Handle a specific field-value-counter")),
109+
linkWithRel("field-value-counters/counter").description("Handle a specific field-value-counter"),
110+
linkWithRel("tools/parseTaskTextToGraph").description("Parse a task definition into a graph structure"),
111+
linkWithRel("tools/convertTaskGraphToText").description("Convert a graph format into DSL text format")),
110112
responseFields(
111113
fieldWithPath("_links").description("Links to other resources"),
112114
fieldWithPath("['"+Version.REVISION_KEY+"']").description("Incremented each time a change is implemented in this REST API")
115+
113116
)
114117
));
115118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.dataflow.rest.resource;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import org.springframework.cloud.dataflow.core.dsl.graph.Graph;
23+
import org.springframework.hateoas.ResourceSupport;
24+
25+
/**
26+
* Represents a response from the tools endpoint. Depending on whether the request was to
27+
* parse a DSL string into a graph or convert a graph to DSL, the different fields of the
28+
* response will be filled in.
29+
*
30+
* @author Andy Clement
31+
*/
32+
public class TaskToolsResource extends ResourceSupport {
33+
34+
private Graph graph;
35+
36+
private String dsl;
37+
38+
// A list of errors, each entry is a map with keys for position and message text
39+
private List<Map<String, Object>> errors;
40+
41+
public TaskToolsResource() {
42+
}
43+
44+
public TaskToolsResource(Graph graph, List<Map<String, Object>> errors) {
45+
this.graph = graph;
46+
this.dsl = null;
47+
this.errors = errors;
48+
}
49+
50+
public TaskToolsResource(String dsl, List<Map<String,Object>> errors) {
51+
this.graph = null;
52+
this.dsl = dsl;
53+
this.errors = errors;
54+
}
55+
56+
public Graph getGraph() {
57+
return graph;
58+
}
59+
60+
public String getDsl() {
61+
return dsl;
62+
}
63+
64+
public List<Map<String,Object>> getErrors() {
65+
return errors;
66+
}
67+
68+
}

spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.springframework.cloud.dataflow.server.controller.StreamDeploymentController;
5959
import org.springframework.cloud.dataflow.server.controller.TaskDefinitionController;
6060
import org.springframework.cloud.dataflow.server.controller.TaskExecutionController;
61+
import org.springframework.cloud.dataflow.server.controller.ToolsController;
6162
import org.springframework.cloud.dataflow.server.controller.UiController;
6263
import org.springframework.cloud.dataflow.server.controller.security.LoginController;
6364
import org.springframework.cloud.dataflow.server.controller.security.SecurityController;
@@ -227,6 +228,11 @@ public CompletionController completionController(StreamCompletionProvider comple
227228
return new CompletionController(completionProvider, taskCompletionProvider);
228229
}
229230

231+
@Bean
232+
public ToolsController toolsController() {
233+
return new ToolsController();
234+
}
235+
230236
@Bean
231237
@ConditionalOnMissingBean(name = "appRegistryFJPFB")
232238
public ForkJoinPoolFactoryBean appRegistryFJPFB() {

0 commit comments

Comments
 (0)