Skip to content

Commit ae1fb79

Browse files
committed
Merge remote-tracking branch 'origin/issue-338-else-nested-preserve-same-entity' into oersi
2 parents 97d9e8c + 89a3b26 commit ae1fb79

File tree

17 files changed

+1166
-599
lines changed

17 files changed

+1166
-599
lines changed

metafacture-biblio/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ dependencies {
2222
implementation project(':metafacture-commons')
2323
implementation project(':metafacture-flowcontrol')
2424
implementation 'org.dspace:oclc-harvester2:0.1.12'
25-
implementation 'xalan:xalan:2.7.1'
25+
implementation ('xalan:xalan:2.7.0') {
26+
exclude group: 'xalan', module: 'serializer'
27+
exclude group: 'xercesImpl', module: 'xercesImpl'
28+
exclude group: 'xml-apis', module: 'xml-apis'
29+
}
30+
implementation 'log4j:log4j:1.2.12'
2631
testImplementation 'junit:junit:4.12'
2732
testImplementation 'org.mockito:mockito-core:2.5.5'
2833
}

metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ public void literal(final String name, final String value) {
175175
if (currentEntity.equals("")) {
176176
prettyPrintIndentation();
177177
writeRaw(String.format(CONTROLFIELD_OPEN_TEMPLATE, name));
178-
writeEscaped(value.trim());
178+
if (value != null)
179+
writeEscaped(value.trim());
179180
writeRaw(CONTROLFIELD_CLOSE);
180181
prettyPrintNewLine();
181182
} else if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) {

metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,17 @@ public void sendAndClearDataWhenOnResetStream() {
204204
String actual = resultCollector.toString();
205205
assertEquals(expected, actual);
206206
}
207+
208+
@Test
209+
public void shouldIgnoreNullValueOfLiteral() {
210+
encoder.startRecord(RECORD_ID);
211+
encoder.literal("type", null);
212+
encoder.endRecord();
213+
encoder.closeStream();
214+
String expected = XML_DECLARATION + XML_ROOT_OPEN
215+
+ "<marc:record><marc:controlfield tag=\"type\"></marc:controlfield></marc:record>"
216+
+ XML_MARC_COLLECTION_END_TAG;
217+
String actual = resultCollector.toString();
218+
assertEquals(expected, actual);
219+
}
207220
}

metamorph/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ dependencies {
2424
implementation project(':metafacture-flowcontrol')
2525
implementation project(':metafacture-mangling')
2626
implementation project(':metafacture-javaintegration')
27+
implementation 'org.slf4j:slf4j-api:1.7.21'
2728
testImplementation 'junit:junit:4.12'
2829
testImplementation 'org.mockito:mockito-core:2.5.5'
30+
testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.21'
2931
}
3032

3133
sourceSets {
@@ -37,3 +39,10 @@ sourceSets {
3739
output.resourcesDir = sourceSets.test.java.outputDir
3840
}
3941
}
42+
43+
test {
44+
testLogging {
45+
showStandardStreams = true
46+
exceptionFormat = 'full'
47+
}
48+
}

metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.LinkedList;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.regex.Pattern;
3132

3233
import org.metafacture.commons.ResourceUtil;
3334
import org.metafacture.framework.FluxCommand;
@@ -48,6 +49,8 @@
4849
import org.metafacture.metamorph.api.NamedValueReceiver;
4950
import org.metafacture.metamorph.api.NamedValueSource;
5051
import org.metafacture.metamorph.api.SourceLocation;
52+
import org.slf4j.Logger;
53+
import org.slf4j.LoggerFactory;
5154
import org.xml.sax.InputSource;
5255

5356
/**
@@ -64,7 +67,9 @@
6467
@FluxCommand("morph")
6568
public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePipe, Maps {
6669

70+
private static final String ELSE_NESTED_KEYWORD = "_elseNested";
6771
public static final String ELSE_KEYWORD = "_else";
72+
public static final String ELSE_FLATTENED_KEYWORD = "_elseFlattened";
6873
public static final char FEEDBACK_CHAR = '@';
6974
public static final char ESCAPE_CHAR = '\\';
7075
public static final String METADATA = "__meta";
@@ -77,8 +82,7 @@ public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePi
7782
private static final InterceptorFactory NULL_INTERCEPTOR_FACTORY = new NullInterceptorFactory();
7883
private static final Map<String, String> NO_VARS = Collections.emptyMap();
7984

80-
private final Registry<NamedValueReceiver> dataRegistry =
81-
new WildcardRegistry<>();
85+
private final Registry<NamedValueReceiver> dataRegistry = new WildcardRegistry<>();
8286
private final List<NamedValueReceiver> elseSources = new ArrayList<>();
8387

8488
private final Map<String, Map<String, String>> maps = new HashMap<>();
@@ -94,6 +98,10 @@ public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePi
9498
private MorphErrorHandler errorHandler = new DefaultErrorHandler();
9599
private int recordCount;
96100
private final List<FlushListener> recordEndListener = new ArrayList<>();
101+
private boolean elseNested;
102+
private boolean elseNestedEntityStarted;
103+
private String currentLiteralName;
104+
private static final Logger LOG = LoggerFactory.getLogger(Metamorph.class);
97105

98106
protected Metamorph() {
99107
// package private
@@ -114,7 +122,6 @@ public Metamorph(final String morphDef, final InterceptorFactory interceptorFact
114122

115123
public Metamorph(final String morphDef, final Map<String, String> vars,
116124
final InterceptorFactory interceptorFactory) {
117-
118125
this(getInputSource(morphDef), vars, interceptorFactory);
119126
}
120127

@@ -132,7 +139,6 @@ public Metamorph(final Reader morphDef, final InterceptorFactory interceptorFact
132139

133140
public Metamorph(final Reader morphDef, final Map<String, String> vars,
134141
final InterceptorFactory interceptorFactory) {
135-
136142
this(new InputSource(morphDef), vars, interceptorFactory);
137143
}
138144

@@ -150,7 +156,6 @@ public Metamorph(final InputStream morphDef, final InterceptorFactory intercepto
150156

151157
public Metamorph(final InputStream morphDef, final Map<String, String> vars,
152158
final InterceptorFactory interceptorFactory) {
153-
154159
this(new InputSource(morphDef), vars, interceptorFactory);
155160
}
156161

@@ -197,7 +202,7 @@ private void init() {
197202
flattener.setReceiver(new DefaultStreamReceiver() {
198203
@Override
199204
public void literal(final String name, final String value) {
200-
dispatch(name, value, getElseSources());
205+
dispatch(name, value, getElseSources(), false);
201206
}
202207
});
203208
}
@@ -215,8 +220,17 @@ public void setErrorHandler(final MorphErrorHandler errorHandler) {
215220
}
216221

217222
protected void registerNamedValueReceiver(final String source, final NamedValueReceiver data) {
218-
if (ELSE_KEYWORD.equals(source)) {
219-
elseSources.add(data);
223+
if (ELSE_NESTED_KEYWORD.equals(source)) {
224+
elseNested = true;
225+
}
226+
227+
if (ELSE_KEYWORD.equals(source) || ELSE_FLATTENED_KEYWORD.equals(source) || elseNested) {
228+
if (elseSources.isEmpty()) {
229+
elseSources.add(data);
230+
}
231+
else {
232+
LOG.warn("Only one of '_else', '_elseFlattened' and '_elseNested' is allowed. Ignoring the superflous ones.");
233+
}
220234
} else {
221235
dataRegistry.register(source, data);
222236
}
@@ -238,12 +252,11 @@ public void startRecord(final String identifier) {
238252
final String identifierFinal = identifier;
239253

240254
outputStreamReceiver.startRecord(identifierFinal);
241-
dispatch(StandardEventNames.ID, identifierFinal, null);
255+
dispatch(StandardEventNames.ID, identifierFinal, null, false);
242256
}
243257

244258
@Override
245259
public void endRecord() {
246-
247260
for(final FlushListener listener: recordEndListener){
248261
listener.flush(recordCount, currentEntityCount);
249262
}
@@ -268,24 +281,20 @@ public void startEntity(final String name) {
268281
entityCountStack.push(Integer.valueOf(entityCount));
269282

270283
flattener.startEntity(name);
271-
272-
273-
274284
}
275285

276286
@Override
277287
public void endEntity() {
278-
dispatch(flattener.getCurrentPath(), "", null);
288+
dispatch(flattener.getCurrentPath(), "", getElseSources(), true);
279289
currentEntityCount = entityCountStack.pop().intValue();
280290
flattener.endEntity();
281-
282291
}
283292

284293

285294
@Override
286295
public void literal(final String name, final String value) {
296+
currentLiteralName = name;
287297
flattener.literal(name, value);
288-
289298
}
290299

291300
@Override
@@ -306,25 +315,48 @@ public void closeStream() {
306315
outputStreamReceiver.closeStream();
307316
}
308317

309-
protected void dispatch(final String path, final String value, final List<NamedValueReceiver> fallback) {
310-
final List<NamedValueReceiver> matchingData = findMatchingData(path, fallback);
311-
if (null != matchingData) {
318+
private void dispatch(final String path, final String value, final List<NamedValueReceiver> fallbackReceiver, final boolean endEntity) {
319+
final List<NamedValueReceiver> matchingData = getData(path);
320+
321+
if (matchingData != null) {
312322
send(path, value, matchingData);
313323
}
324+
else if (fallbackReceiver != null) {
325+
if (endEntity) {
326+
if (elseNestedEntityStarted) {
327+
outputStreamReceiver.endEntity();
328+
elseNestedEntityStarted = false;
329+
}
330+
}
331+
else {
332+
final String entityName = elseNested ? flattener.getCurrentEntityName() : null;
333+
334+
if (entityName != null) {
335+
if (getData(entityName) == null) {
336+
if (!elseNestedEntityStarted) {
337+
outputStreamReceiver.startEntity(entityName);
338+
elseNestedEntityStarted = true;
339+
}
340+
341+
send(currentLiteralName, value, fallbackReceiver);
342+
}
343+
}
344+
else {
345+
send(path, value, fallbackReceiver);
346+
}
347+
}
348+
}
314349
}
315350

316-
private List<NamedValueReceiver> findMatchingData(final String path, final List<NamedValueReceiver> fallback) {
351+
private List<NamedValueReceiver> getData(final String path) {
317352
final List<NamedValueReceiver> matchingData = dataRegistry.get(path);
318-
if (matchingData == null || matchingData.isEmpty()) {
319-
return fallback;
320-
}
321-
return matchingData;
353+
return matchingData != null && !matchingData.isEmpty() ? matchingData : null;
322354
}
323355

324-
private void send(final String key, final String value, final List<NamedValueReceiver> dataList) {
356+
private void send(final String path, final String value, final List<NamedValueReceiver> dataList) {
325357
for (final NamedValueReceiver data : dataList) {
326358
try {
327-
data.receive(key, value, null, recordCount, currentEntityCount);
359+
data.receive(path, value, null, recordCount, currentEntityCount);
328360
} catch (final RuntimeException e) {
329361
errorHandler.error(e);
330362
}
@@ -357,7 +389,7 @@ public void receive(final String name, final String value, final NamedValueSourc
357389
}
358390

359391
if (name.length() != 0 && name.charAt(0) == FEEDBACK_CHAR) {
360-
dispatch(name, value, null);
392+
dispatch(name, value, null, false);
361393
return;
362394
}
363395

metamorph/src/main/java/org/metafacture/metamorph/functions/Regexp.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public final class Regexp extends AbstractFunction {
3939
public void receive(final String name, final String value,
4040
final NamedValueSource source, final int recordCount,
4141
final int entityCount) {
42+
if (null == value) {
43+
return;
44+
}
4245
matcher.reset(value);
4346
if (null == format) {
4447
while (matcher.find()) {

metamorph/src/test/java/org/metafacture/metamorph/MetamorphTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public final class MetamorphTest {
5252
@Mock
5353
private NamedValueReceiver namedValueReceiver;
5454

55+
@Mock
56+
private DefaultStreamReceiver receiver = new DefaultStreamReceiver();
57+
5558
private Metamorph metamorph;
5659

5760
@Before
@@ -60,6 +63,7 @@ public void createSystemUnderTest() {
6063
metamorph.setReceiver(new DefaultStreamReceiver());
6164
}
6265

66+
6367
@Test
6468
public void shouldMapMatchingPath() {
6569
setupSimpleMappingMorph();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020 hbz NRW
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+
package org.metafacture.metamorph;
17+
18+
import org.metafacture.framework.StreamReceiver;
19+
import org.mockito.InOrder;
20+
import org.mockito.Mockito;
21+
import org.mockito.exceptions.base.MockitoAssertionError;
22+
23+
import java.util.function.BiConsumer;
24+
import java.util.function.Consumer;
25+
import java.util.function.IntFunction;
26+
import java.util.function.Supplier;
27+
28+
/**
29+
* Helper functions for Metamorph tests.
30+
*
31+
* @author Jens Wille
32+
*/
33+
public final class TestHelpers {
34+
35+
public static void assertMorph(final StreamReceiver receiver, final String morphDef, final Consumer<Metamorph> in, final Consumer<Supplier<StreamReceiver>> out) {
36+
assertMorph(receiver, morphDef, in, (s, f) -> out.accept(s));
37+
}
38+
39+
public static void assertMorph(final StreamReceiver receiver, final String morphDef, final Consumer<Metamorph> in, final BiConsumer<Supplier<StreamReceiver>, IntFunction<StreamReceiver>> out) {
40+
final Metamorph metamorph = InlineMorph.in(TestHelpers.class).with(morphDef).createConnectedTo(receiver);
41+
final InOrder ordered = Mockito.inOrder(receiver);
42+
43+
try {
44+
in.accept(metamorph);
45+
out.accept(() -> ordered.verify(receiver), i -> ordered.verify(receiver, Mockito.times(i)));
46+
47+
ordered.verifyNoMoreInteractions();
48+
Mockito.verifyNoMoreInteractions(receiver);
49+
}
50+
catch (final MockitoAssertionError e) {
51+
System.out.println(Mockito.mockingDetails(receiver).printInvocations());
52+
throw e;
53+
}
54+
}
55+
56+
}

0 commit comments

Comments
 (0)