Skip to content

Commit a381c6a

Browse files
committed
Implement YAML module.
Based off of JSON module. Fixes #399.
1 parent fb527e4 commit a381c6a

File tree

7 files changed

+1205
-0
lines changed

7 files changed

+1205
-0
lines changed

metafacture-yaml/build.gradle

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2017 Christoph Böhme
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+
ext.mavenName = 'Metafacture YAML'
18+
description = 'Modules for processing YAML data in Metafacture'
19+
20+
dependencies {
21+
api project(':metafacture-framework')
22+
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.0'
23+
testImplementation 'junit:junit:4.12'
24+
testImplementation 'org.mockito:mockito-core:2.5.5'
25+
}
26+
27+
test {
28+
testLogging {
29+
showStandardStreams = true
30+
exceptionFormat = 'full'
31+
}
32+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright 2017, 2021 hbz
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.metafacture.yaml;
18+
19+
import org.metafacture.framework.FluxCommand;
20+
import org.metafacture.framework.MetafactureException;
21+
import org.metafacture.framework.StreamReceiver;
22+
import org.metafacture.framework.annotations.Description;
23+
import org.metafacture.framework.annotations.In;
24+
import org.metafacture.framework.annotations.Out;
25+
import org.metafacture.framework.helpers.DefaultObjectPipe;
26+
27+
import com.fasterxml.jackson.core.JsonProcessingException;
28+
import com.fasterxml.jackson.core.JsonToken;
29+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
30+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
31+
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
32+
33+
import java.io.IOException;
34+
import java.util.Arrays;
35+
import java.util.List;
36+
import java.util.stream.Stream;
37+
38+
/**
39+
* Decodes a record in YAML format.
40+
*
41+
* @author Jens Wille
42+
*
43+
*/
44+
@Description("Decodes YAML to metadata events.")
45+
@In(String.class)
46+
@Out(StreamReceiver.class)
47+
@FluxCommand("decode-yaml")
48+
public final class YamlDecoder extends DefaultObjectPipe<String, StreamReceiver> {
49+
50+
public static final String DEFAULT_ARRAY_MARKER = YamlEncoder.ARRAY_MARKER;
51+
52+
public static final String DEFAULT_ARRAY_NAME = "%d";
53+
54+
public static final String DEFAULT_RECORD_ID = "%d";
55+
56+
private final YAMLFactory yamlFactory = new YAMLFactory();
57+
58+
private YAMLParser yamlParser;
59+
private String arrayMarker;
60+
private String arrayName;
61+
private String recordId;
62+
private int recordCount;
63+
64+
public YamlDecoder() {
65+
setArrayMarker(DEFAULT_ARRAY_MARKER);
66+
setArrayName(DEFAULT_ARRAY_NAME);
67+
setRecordId(DEFAULT_RECORD_ID);
68+
69+
resetRecordCount();
70+
}
71+
72+
public void setArrayMarker(final String arrayMarker) {
73+
this.arrayMarker = arrayMarker;
74+
}
75+
76+
public String getArrayMarker() {
77+
return arrayMarker;
78+
}
79+
80+
public void setArrayName(final String arrayName) {
81+
this.arrayName = arrayName;
82+
}
83+
84+
public String getArrayName() {
85+
return arrayName;
86+
}
87+
88+
public void setRecordId(final String recordId) {
89+
this.recordId = recordId;
90+
}
91+
92+
public String getRecordId() {
93+
return recordId;
94+
}
95+
96+
public void setRecordCount(final int recordCount) {
97+
this.recordCount = recordCount;
98+
}
99+
100+
public int getRecordCount() {
101+
return recordCount;
102+
}
103+
104+
public void resetRecordCount() {
105+
setRecordCount(0);
106+
}
107+
108+
@Override
109+
public void process(final String yaml) {
110+
assert !isClosed();
111+
112+
createParser(yaml);
113+
114+
try {
115+
decode();
116+
}
117+
catch (final IOException e) {
118+
throw new MetafactureException(e);
119+
}
120+
finally {
121+
closeParser();
122+
}
123+
}
124+
125+
private Stream<String> matches(final Object obj) {
126+
final List<?> records = (obj instanceof List<?>) ? ((List<?>) obj) : Arrays.asList(obj);
127+
128+
return records.stream().map(doc -> {
129+
try {
130+
return new YAMLMapper(yamlFactory).writeValueAsString(doc);
131+
}
132+
catch (final JsonProcessingException e) {
133+
e.printStackTrace();
134+
return doc.toString();
135+
}
136+
});
137+
}
138+
139+
@Override
140+
protected void onResetStream() {
141+
resetRecordCount();
142+
}
143+
144+
private void createParser(final String string) {
145+
try {
146+
yamlParser = yamlFactory.createParser(string);
147+
}
148+
catch (final IOException e) {
149+
throw new MetafactureException(e);
150+
}
151+
}
152+
153+
private void closeParser() {
154+
try {
155+
yamlParser.close();
156+
}
157+
catch (final IOException e) {
158+
throw new MetafactureException(e);
159+
}
160+
}
161+
162+
private void decode() throws IOException {
163+
while (yamlParser.nextToken() == JsonToken.START_OBJECT) {
164+
getReceiver().startRecord(String.format(recordId, ++recordCount));
165+
decodeObject();
166+
getReceiver().endRecord();
167+
}
168+
169+
if (yamlParser.currentToken() != null) {
170+
throw new MetafactureException(new StringBuilder()
171+
.append("Unexpected token '")
172+
.append(yamlParser.currentToken())
173+
.append("' at ")
174+
.append(yamlParser.getCurrentLocation())
175+
.toString());
176+
}
177+
}
178+
179+
private void decodeObject() throws IOException {
180+
while (yamlParser.nextToken() == JsonToken.FIELD_NAME) {
181+
decodeValue(yamlParser.getCurrentName(), yamlParser.nextToken());
182+
}
183+
}
184+
185+
private void decodeArray() throws IOException {
186+
int arrayCount = 0;
187+
188+
while (yamlParser.nextToken() != JsonToken.END_ARRAY) {
189+
decodeValue(String.format(arrayName, ++arrayCount), yamlParser.currentToken());
190+
}
191+
}
192+
193+
private void decodeValue(final String name, final JsonToken token) throws IOException {
194+
switch (token) {
195+
case START_OBJECT:
196+
getReceiver().startEntity(name);
197+
decodeObject();
198+
getReceiver().endEntity();
199+
200+
break;
201+
case START_ARRAY:
202+
getReceiver().startEntity(name + arrayMarker);
203+
decodeArray();
204+
getReceiver().endEntity();
205+
206+
break;
207+
case VALUE_NULL:
208+
getReceiver().literal(name, null);
209+
210+
break;
211+
default:
212+
getReceiver().literal(name, yamlParser.getText());
213+
214+
break;
215+
}
216+
}
217+
218+
}

0 commit comments

Comments
 (0)