Skip to content

Commit 1f4cd23

Browse files
authored
Merge pull request #3517 from apache/CAUSEWAY-3989
Causeway 3989
2 parents 339ce10 + 187f9ca commit 1f4cd23

49 files changed

Lines changed: 3029 additions & 163 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/applib/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@
107107
<scope>test</scope>
108108
</dependency>
109109

110+
<dependency>
111+
<groupId>com.approvaltests</groupId>
112+
<artifactId>approvaltests</artifactId>
113+
<scope>test</scope>
114+
</dependency>
115+
<dependency>
116+
<!-- required by com.approvaltests:test (version managed by spring boot) -->
117+
<groupId>com.google.code.gson</groupId>
118+
<artifactId>gson</artifactId>
119+
<scope>test</scope>
120+
</dependency>
121+
110122
</dependencies>
111123

112124
</project>

api/applib/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
requires transitive spring.core;
147147
requires spring.tx;
148148
requires org.apache.logging.log4j.core;
149+
requires com.fasterxml.jackson.annotation;
149150

150151
// JAXB viewmodels
151152
opens org.apache.causeway.applib.annotation;

api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommandDtoUtils.java

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,47 @@
1818
*/
1919
package org.apache.causeway.applib.util.schema;
2020

21+
import java.io.IOException;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.stream.Collectors;
25+
26+
import javax.xml.datatype.DatatypeConstants;
27+
import javax.xml.datatype.DatatypeFactory;
28+
import javax.xml.datatype.XMLGregorianCalendar;
29+
2130
import org.apache.causeway.applib.services.bookmark.Bookmark;
2231
import org.apache.causeway.commons.internal.base._Lazy;
32+
import org.apache.causeway.commons.internal.base._NullSafe;
2333
import org.apache.causeway.commons.internal.base._Strings;
34+
import org.apache.causeway.commons.io.DataSource;
2435
import org.apache.causeway.commons.io.DtoMapper;
2536
import org.apache.causeway.commons.io.JaxbUtils;
37+
import org.apache.causeway.commons.io.JsonUtils;
38+
import org.apache.causeway.commons.io.JsonUtils.JacksonCustomizer;
39+
import org.apache.causeway.commons.io.YamlUtils;
2640
import org.apache.causeway.schema.cmd.v2.ActionDto;
2741
import org.apache.causeway.schema.cmd.v2.CommandDto;
2842
import org.apache.causeway.schema.cmd.v2.MapDto;
43+
import org.apache.causeway.schema.cmd.v2.MemberDto;
2944
import org.apache.causeway.schema.cmd.v2.ParamsDto;
45+
import org.apache.causeway.schema.cmd.v2.PropertyDto;
3046
import org.apache.causeway.schema.common.v2.OidsDto;
3147
import org.apache.causeway.schema.common.v2.PeriodDto;
48+
import org.apache.causeway.schema.common.v2.ValueDto;
49+
50+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
51+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
52+
import com.fasterxml.jackson.core.JsonGenerator;
53+
import com.fasterxml.jackson.core.JsonParser;
54+
import com.fasterxml.jackson.databind.DeserializationContext;
55+
import com.fasterxml.jackson.databind.JsonDeserializer;
56+
import com.fasterxml.jackson.databind.JsonSerializer;
57+
import com.fasterxml.jackson.databind.ObjectMapper;
58+
import com.fasterxml.jackson.databind.SerializerProvider;
59+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
60+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
61+
import com.fasterxml.jackson.databind.jsontype.NamedType;
3262

3363
import lombok.experimental.UtilityClass;
3464

@@ -117,4 +147,230 @@ private MapDto userDataFor(final CommandDto commandDto) {
117147
return userData;
118148
}
119149

150+
// -- YAML SUPPORT
151+
152+
public String toYaml(final Iterable<CommandDto> commandDtos) {
153+
final JsonUtils.JacksonCustomizer customizer = new JacksonCustomizer() {
154+
@Override
155+
public ObjectMapper apply(ObjectMapper mapper) {
156+
JsonUtils.jaxbAnnotationSupport(mapper);
157+
CommandDtoUtils.memberDtoSupport(mapper);
158+
CommandDtoUtils.valueDtoSupport(mapper);
159+
JsonUtils.onlyIncludeNonNull(mapper);
160+
return mapper;
161+
}
162+
};
163+
return YamlUtils.toStringUtf8(
164+
_NullSafe.stream(commandDtos)
165+
.collect(Collectors.toList()),
166+
customizer);
167+
}
168+
169+
public List<CommandDto> fromYaml(final DataSource commandDtosYaml) {
170+
final JsonUtils.JacksonCustomizer customizer = new JacksonCustomizer() {
171+
@Override
172+
public ObjectMapper apply(ObjectMapper mapper) {
173+
JsonUtils.jaxbAnnotationSupport(mapper);
174+
CommandDtoUtils.memberDtoSupport(mapper);
175+
CommandDtoUtils.valueDtoSupport(mapper);
176+
return mapper;
177+
}
178+
};
179+
return YamlUtils.tryReadAsList(CommandDto.class, commandDtosYaml, customizer)
180+
.ifFailureFail()
181+
.getValue()
182+
.orElseGet(Collections::emptyList);
183+
}
184+
185+
// Mix-in to add type metadata to MemberDto
186+
@JsonTypeInfo(
187+
use = JsonTypeInfo.Id.NAME,
188+
include = JsonTypeInfo.As.PROPERTY,
189+
property = "type")
190+
private abstract class AbstractDtoMixIn {}
191+
192+
// Mix-in to ignore unknown properties for ValueDto
193+
@JsonIgnoreProperties(ignoreUnknown = true)
194+
private abstract class AbstractValueDtoMixIn {
195+
@JsonSerialize(using = LocalDateXmlGregorianCalendarSerializer.class)
196+
abstract XMLGregorianCalendar getLocalDate();
197+
198+
@JsonDeserialize(using = LocalDateXmlGregorianCalendarDeserializer.class)
199+
abstract void setLocalDate(XMLGregorianCalendar localDate);
200+
201+
@JsonSerialize(using = LocalDateTimeXmlGregorianCalendarSerializer.class)
202+
abstract XMLGregorianCalendar getLocalDateTime();
203+
204+
@JsonDeserialize(using = LocalDateTimeXmlGregorianCalendarDeserializer.class)
205+
abstract void setLocalDateTime(XMLGregorianCalendar localDateTime);
206+
207+
@JsonSerialize(using = LocalTimeXmlGregorianCalendarSerializer.class)
208+
abstract XMLGregorianCalendar getLocalTime();
209+
210+
@JsonDeserialize(using = LocalTimeXmlGregorianCalendarDeserializer.class)
211+
abstract void setLocalTime(XMLGregorianCalendar localTime);
212+
}
213+
214+
private void valueDtoSupport(final ObjectMapper mb) {
215+
mb.addMixIn(ValueDto.class, AbstractValueDtoMixIn.class);
216+
}
217+
218+
private static final DatatypeFactory DATATYPE_FACTORY = datatypeFactory();
219+
220+
private static DatatypeFactory datatypeFactory() {
221+
try {
222+
return DatatypeFactory.newInstance();
223+
} catch (Exception ex) {
224+
throw new RuntimeException("Failed to initialize DatatypeFactory", ex);
225+
}
226+
}
227+
228+
private static final class LocalDateXmlGregorianCalendarSerializer
229+
extends JsonSerializer<XMLGregorianCalendar> {
230+
231+
@Override
232+
public void serialize(
233+
final XMLGregorianCalendar value,
234+
final JsonGenerator gen,
235+
final SerializerProvider serializers) throws IOException {
236+
if (value == null) {
237+
gen.writeNull();
238+
return;
239+
}
240+
final XMLGregorianCalendar dateOnly = DATATYPE_FACTORY.newXMLGregorianCalendarDate(
241+
value.getYear(),
242+
value.getMonth(),
243+
value.getDay(),
244+
DatatypeConstants.FIELD_UNDEFINED);
245+
gen.writeString(dateOnly.toXMLFormat());
246+
}
247+
}
248+
249+
private static final class LocalDateXmlGregorianCalendarDeserializer
250+
extends JsonDeserializer<XMLGregorianCalendar> {
251+
252+
@Override
253+
public XMLGregorianCalendar deserialize(
254+
final JsonParser p,
255+
final DeserializationContext ctxt) throws IOException {
256+
final String text = p.getValueAsString();
257+
if (_Strings.isNullOrEmpty(text)) {
258+
return null;
259+
}
260+
final XMLGregorianCalendar parsed = DATATYPE_FACTORY.newXMLGregorianCalendar(text);
261+
return DATATYPE_FACTORY.newXMLGregorianCalendarDate(
262+
parsed.getYear(),
263+
parsed.getMonth(),
264+
parsed.getDay(),
265+
DatatypeConstants.FIELD_UNDEFINED);
266+
}
267+
}
268+
269+
private static final class LocalDateTimeXmlGregorianCalendarSerializer
270+
extends JsonSerializer<XMLGregorianCalendar> {
271+
272+
@Override
273+
public void serialize(
274+
final XMLGregorianCalendar value,
275+
final JsonGenerator gen,
276+
final SerializerProvider serializers) throws IOException {
277+
if (value == null) {
278+
gen.writeNull();
279+
return;
280+
}
281+
final XMLGregorianCalendar localDateTime = DATATYPE_FACTORY.newXMLGregorianCalendar(
282+
value.getYear(),
283+
value.getMonth(),
284+
value.getDay(),
285+
value.getHour(),
286+
value.getMinute(),
287+
value.getSecond(),
288+
millisecondsOf(value),
289+
DatatypeConstants.FIELD_UNDEFINED);
290+
gen.writeString(localDateTime.toXMLFormat());
291+
}
292+
}
293+
294+
private static final class LocalDateTimeXmlGregorianCalendarDeserializer
295+
extends JsonDeserializer<XMLGregorianCalendar> {
296+
297+
@Override
298+
public XMLGregorianCalendar deserialize(
299+
final JsonParser p,
300+
final DeserializationContext ctxt) throws IOException {
301+
final String text = p.getValueAsString();
302+
if (_Strings.isNullOrEmpty(text)) {
303+
return null;
304+
}
305+
final XMLGregorianCalendar parsed = DATATYPE_FACTORY.newXMLGregorianCalendar(text);
306+
return DATATYPE_FACTORY.newXMLGregorianCalendar(
307+
parsed.getYear(),
308+
parsed.getMonth(),
309+
parsed.getDay(),
310+
parsed.getHour(),
311+
parsed.getMinute(),
312+
parsed.getSecond(),
313+
millisecondsOf(parsed),
314+
DatatypeConstants.FIELD_UNDEFINED);
315+
}
316+
}
317+
318+
private static final class LocalTimeXmlGregorianCalendarSerializer
319+
extends JsonSerializer<XMLGregorianCalendar> {
320+
321+
@Override
322+
public void serialize(
323+
final XMLGregorianCalendar value,
324+
final JsonGenerator gen,
325+
final SerializerProvider serializers) throws IOException {
326+
if (value == null) {
327+
gen.writeNull();
328+
return;
329+
}
330+
final XMLGregorianCalendar localTime = DATATYPE_FACTORY.newXMLGregorianCalendarTime(
331+
value.getHour(),
332+
value.getMinute(),
333+
value.getSecond(),
334+
millisecondsOf(value),
335+
DatatypeConstants.FIELD_UNDEFINED);
336+
gen.writeString(localTime.toXMLFormat());
337+
}
338+
}
339+
340+
private static final class LocalTimeXmlGregorianCalendarDeserializer
341+
extends JsonDeserializer<XMLGregorianCalendar> {
342+
343+
@Override
344+
public XMLGregorianCalendar deserialize(
345+
final JsonParser p,
346+
final DeserializationContext ctxt) throws IOException {
347+
final String text = p.getValueAsString();
348+
if (_Strings.isNullOrEmpty(text)) {
349+
return null;
350+
}
351+
final XMLGregorianCalendar parsed = DATATYPE_FACTORY.newXMLGregorianCalendar(text);
352+
return DATATYPE_FACTORY.newXMLGregorianCalendarTime(
353+
parsed.getHour(),
354+
parsed.getMinute(),
355+
parsed.getSecond(),
356+
millisecondsOf(parsed),
357+
DatatypeConstants.FIELD_UNDEFINED);
358+
}
359+
}
360+
361+
private static int millisecondsOf(final XMLGregorianCalendar value) {
362+
final int millis = value.getMillisecond();
363+
return millis == DatatypeConstants.FIELD_UNDEFINED
364+
? DatatypeConstants.FIELD_UNDEFINED
365+
: millis;
366+
}
367+
368+
private void memberDtoSupport(final ObjectMapper mb) {
369+
// add mix-in so MemberDto carries @JsonTypeInfo without modifying source
370+
mb.addMixIn(MemberDto.class, AbstractDtoMixIn.class);
371+
// register concrete sub-types with logical names
372+
mb.registerSubtypes(new NamedType(ActionDto.class, "ACT"));
373+
mb.registerSubtypes(new NamedType(PropertyDto.class, "PROP"));
374+
}
375+
120376
}

api/applib/src/test/java/org/apache/causeway/applib/util/schema/CommandDtoUtils_Test.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
*/
1919
package org.apache.causeway.applib.util.schema;
2020

21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
24+
import org.apache.causeway.applib.value.Blob;
25+
import org.apache.causeway.applib.value.NamedWithMimeType;
26+
27+
import org.assertj.core.api.Assertions;
2128
import org.junit.jupiter.api.BeforeEach;
2229
import org.junit.jupiter.api.Test;
2330

@@ -28,6 +35,8 @@
2835
import org.apache.causeway.schema.cmd.v2.CommandDto;
2936
import org.apache.causeway.schema.cmd.v2.MapDto;
3037

38+
import org.springframework.util.StreamUtils;
39+
3140
public class CommandDtoUtils_Test {
3241

3342
CommandDto dto;
@@ -76,4 +85,5 @@ public void clearUserData() {
7685
// then
7786
assertThat(CommandDtoUtils.getUserData(dto, "someKey"), is(nullValue()));
7887
}
88+
7989
}

0 commit comments

Comments
 (0)