Skip to content

Commit d8a09b5

Browse files
committed
MODLD-1029: LightWork DTO introduced and added to WorkResponse DTO
1 parent 6dbb5dc commit d8a09b5

7 files changed

Lines changed: 249 additions & 1 deletion

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.folio.linked.data.mapper.dto.resource.common.work.sub;
2+
3+
import static org.folio.ld.dictionary.PredicateDictionary.IS_PART_OF;
4+
import static org.folio.ld.dictionary.PredicateDictionary.OTHER_EDITION;
5+
import static org.folio.ld.dictionary.PredicateDictionary.OTHER_VERSION;
6+
import static org.folio.ld.dictionary.PredicateDictionary.RELATED_WORK;
7+
import static org.folio.ld.dictionary.PropertyDictionary.LABEL;
8+
import static org.folio.ld.dictionary.ResourceTypeDictionary.LIGHT_RESOURCE;
9+
import static org.folio.ld.dictionary.ResourceTypeDictionary.SERIES;
10+
import static org.folio.linked.data.util.ResourceUtils.getFirstPropertyValue;
11+
12+
import java.util.Set;
13+
import lombok.RequiredArgsConstructor;
14+
import org.folio.linked.data.domain.dto.LightWork;
15+
import org.folio.linked.data.domain.dto.WorkResponse;
16+
import org.folio.linked.data.exception.RequestProcessingExceptionBuilder;
17+
import org.folio.linked.data.mapper.dto.resource.base.CoreMapper;
18+
import org.folio.linked.data.mapper.dto.resource.base.MapperUnit;
19+
import org.folio.linked.data.mapper.dto.resource.base.SingleResourceMapperUnit;
20+
import org.folio.linked.data.model.entity.Resource;
21+
import org.springframework.stereotype.Component;
22+
23+
@Component
24+
@RequiredArgsConstructor
25+
@MapperUnit(type = LIGHT_RESOURCE, predicate = {IS_PART_OF, OTHER_EDITION, OTHER_VERSION, RELATED_WORK},
26+
requestDto = LightWork.class)
27+
public class LightWorkMapperUnit implements SingleResourceMapperUnit {
28+
29+
private static final Set<Class<?>> SUPPORTED_PARENTS = Set.of(
30+
WorkResponse.class
31+
);
32+
private final CoreMapper coreMapper;
33+
private final RequestProcessingExceptionBuilder exceptionBuilder;
34+
35+
@Override
36+
public Set<Class<?>> supportedParents() {
37+
return SUPPORTED_PARENTS;
38+
}
39+
40+
@Override
41+
public <P> P toDto(Resource resourceToConvert, P parentDto, ResourceMappingContext context) {
42+
if (resourceToConvert.isOfType(SERIES)) {
43+
return parentDto;
44+
}
45+
if (parentDto instanceof WorkResponse workResponse) {
46+
var lightWork = coreMapper.toDtoWithEdges(resourceToConvert, LightWork.class, false);
47+
lightWork.setId(String.valueOf(resourceToConvert.getId()));
48+
lightWork.setLabel(getFirstPropertyValue(resourceToConvert, LABEL));
49+
lightWork.setRelation(context.predicate().getUri());
50+
workResponse.addAnalyticalEntryItem(lightWork);
51+
}
52+
return parentDto;
53+
}
54+
55+
@Override
56+
public Resource toEntity(Object dto, Resource parentEntity) {
57+
throw exceptionBuilder.notSupportedException(LIGHT_RESOURCE.name(), "Create or update");
58+
}
59+
60+
}

src/main/java/org/folio/linked/data/util/ResourceUtils.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ public static List<String> getTypeUris(Resource resource) {
142142
.toList();
143143
}
144144

145+
public static String getFirstPropertyValue(Resource resource, PropertyDictionary property) {
146+
return getPropertyValues(resource, property)
147+
.getFirst();
148+
}
149+
145150
public static List<String> getPropertyValues(Resource resource, PropertyDictionary property) {
146151
return ofNullable(resource.getDoc())
147152
.map(doc -> doc.get(property.getValue()))

src/main/java/org/folio/linked/data/validation/entity/PrimaryTitleEntityValidator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
55
import static org.folio.ld.dictionary.PropertyDictionary.MAIN_TITLE;
66
import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE;
7+
import static org.folio.ld.dictionary.ResourceTypeDictionary.LIGHT_RESOURCE;
78
import static org.folio.ld.dictionary.ResourceTypeDictionary.SERIES;
89
import static org.folio.ld.dictionary.ResourceTypeDictionary.TITLE;
910
import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK;
@@ -20,7 +21,7 @@ public class PrimaryTitleEntityValidator implements ConstraintValidator<PrimaryT
2021

2122
@Override
2223
public boolean isValid(Resource resource, ConstraintValidatorContext context) {
23-
if (isNotWorkOrInstance(resource) || isSeries(resource)) {
24+
if (isNotWorkOrInstance(resource) || isSeries(resource) || isLightResource(resource)) {
2425
return true;
2526
}
2627
if (isEmpty(resource.getOutgoingEdges())) {
@@ -45,4 +46,8 @@ private boolean isNotWorkOrInstance(Resource resource) {
4546
private boolean isSeries(Resource resource) {
4647
return resource.isOfType(SERIES);
4748
}
49+
50+
private boolean isLightResource(Resource resource) {
51+
return resource.isOfType(LIGHT_RESOURCE);
52+
}
4853
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"description": "Light Work",
4+
"allOf": [
5+
{
6+
"$ref": "IdField.json"
7+
},
8+
{
9+
"type": "object",
10+
"properties": {
11+
"label": {
12+
"type": "string"
13+
},
14+
"_relation": {
15+
"type": "string"
16+
}
17+
}
18+
}
19+
]
20+
}
21+

src/main/resources/swagger.api/schema/resource/response/WorkResponse.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@
187187
"type": "object",
188188
"$ref": "../common/HubReferenceWithType.json"
189189
}
190+
},
191+
"_analyticalEntry ": {
192+
"type": "array",
193+
"items": {
194+
"type": "object",
195+
"$ref": "../common/LightWork.json"
196+
}
190197
}
191198
}
192199
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package org.folio.linked.data.e2e.mappings.work.lightwork;
2+
3+
import static org.folio.ld.dictionary.PredicateDictionary.IS_PART_OF;
4+
import static org.folio.ld.dictionary.PredicateDictionary.OTHER_EDITION;
5+
import static org.folio.ld.dictionary.PredicateDictionary.OTHER_VERSION;
6+
import static org.folio.ld.dictionary.PredicateDictionary.RELATED_WORK;
7+
import static org.folio.ld.dictionary.PropertyDictionary.LABEL;
8+
import static org.folio.ld.dictionary.ResourceTypeDictionary.LIGHT_RESOURCE;
9+
import static org.folio.ld.dictionary.ResourceTypeDictionary.SERIES;
10+
import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK;
11+
import static org.folio.linked.data.test.TestUtil.STANDALONE_TEST_PROFILE;
12+
import static org.folio.linked.data.test.TestUtil.TEST_JSON_MAPPER;
13+
import static org.folio.linked.data.test.TestUtil.defaultHeaders;
14+
import static org.folio.linked.data.util.Constants.STANDALONE_PROFILE;
15+
import static org.hamcrest.Matchers.containsInAnyOrder;
16+
import static org.hamcrest.Matchers.hasSize;
17+
import static org.springframework.http.MediaType.APPLICATION_JSON;
18+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
19+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
20+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21+
22+
import lombok.SneakyThrows;
23+
import org.folio.ld.dictionary.PredicateDictionary;
24+
import org.folio.linked.data.e2e.base.ITBase;
25+
import org.folio.linked.data.e2e.base.IntegrationTest;
26+
import org.folio.linked.data.model.entity.Resource;
27+
import org.folio.linked.data.model.entity.ResourceEdge;
28+
import org.folio.linked.data.test.MonographTestUtil;
29+
import org.junit.jupiter.api.Test;
30+
import org.springframework.test.context.ActiveProfiles;
31+
32+
@IntegrationTest
33+
@ActiveProfiles({STANDALONE_PROFILE, STANDALONE_TEST_PROFILE})
34+
class LightWorkIT extends ITBase {
35+
36+
private static final String RESOURCE_URL = "/linked-data/resource";
37+
private static final Long IS_PART_OF_ID = 200L;
38+
private static final Long OTHER_EDITION_ID = 201L;
39+
private static final Long OTHER_VERSION_ID = 202L;
40+
private static final Long RELATED_WORK_ID = 203L;
41+
42+
@Test
43+
@SneakyThrows
44+
void getWork_withLightWorkEdges_shouldReturnAnalyticalEntryForEachRelation() {
45+
// given
46+
var work = buildWorkWithLightWorkEdges();
47+
resourceTestService.saveGraph(work);
48+
var getRequest = get(RESOURCE_URL + "/" + work.getId())
49+
.contentType(APPLICATION_JSON)
50+
.headers(defaultHeaders(env));
51+
52+
// when
53+
var response = mockMvc.perform(getRequest);
54+
55+
// then
56+
var analyticalEntryPath = "$.resource['http://bibfra.me/vocab/lite/Work']['_analyticalEntry ']";
57+
response
58+
.andExpect(status().isOk())
59+
.andExpect(jsonPath(analyticalEntryPath, hasSize(4)))
60+
.andExpect(jsonPath(analyticalEntryPath + "[*]['id']", containsInAnyOrder(
61+
IS_PART_OF_ID.toString(),
62+
OTHER_EDITION_ID.toString(),
63+
OTHER_VERSION_ID.toString(),
64+
RELATED_WORK_ID.toString()
65+
)))
66+
.andExpect(jsonPath(analyticalEntryPath + "[*]['_relation']", containsInAnyOrder(
67+
IS_PART_OF.getUri(),
68+
OTHER_EDITION.getUri(),
69+
OTHER_VERSION.getUri(),
70+
RELATED_WORK.getUri()
71+
)));
72+
}
73+
74+
@Test
75+
@SneakyThrows
76+
void getWork_withLightWorkEdgesAndPartOfSeries_shouldReturnCorrectAnalyticalEntries() {
77+
// given
78+
var work = buildWorkWithLightWorkEdges();
79+
var series = new Resource().setLabel("Series").addTypes(WORK, SERIES, LIGHT_RESOURCE).setIdAndRefreshEdges(333L);
80+
work.addOutgoingEdge(new ResourceEdge(work, series, IS_PART_OF));
81+
resourceTestService.saveGraph(work);
82+
var getRequest = get(RESOURCE_URL + "/" + work.getId())
83+
.contentType(APPLICATION_JSON)
84+
.headers(defaultHeaders(env));
85+
86+
// when
87+
var response = mockMvc.perform(getRequest);
88+
89+
// then
90+
var analyticalEntryPath = "$.resource['http://bibfra.me/vocab/lite/Work']['_analyticalEntry ']";
91+
response
92+
.andExpect(status().isOk())
93+
.andExpect(jsonPath(analyticalEntryPath, hasSize(4)))
94+
.andExpect(jsonPath(analyticalEntryPath + "[*]['id']", containsInAnyOrder(
95+
IS_PART_OF_ID.toString(),
96+
OTHER_EDITION_ID.toString(),
97+
OTHER_VERSION_ID.toString(),
98+
RELATED_WORK_ID.toString()
99+
)))
100+
.andExpect(jsonPath(analyticalEntryPath + "[*]['_relation']", containsInAnyOrder(
101+
IS_PART_OF.getUri(),
102+
OTHER_EDITION.getUri(),
103+
OTHER_VERSION.getUri(),
104+
RELATED_WORK.getUri()
105+
)));
106+
}
107+
108+
@SneakyThrows
109+
private Resource buildWorkWithLightWorkEdges() {
110+
var work = MonographTestUtil.getWork("work", hashService);
111+
addLightWorkEdge(work, IS_PART_OF_ID, IS_PART_OF);
112+
addLightWorkEdge(work, OTHER_EDITION_ID, OTHER_EDITION);
113+
addLightWorkEdge(work, OTHER_VERSION_ID, OTHER_VERSION);
114+
addLightWorkEdge(work, RELATED_WORK_ID, RELATED_WORK);
115+
return work;
116+
}
117+
118+
@SneakyThrows
119+
private void addLightWorkEdge(Resource work, Long lightWorkId, PredicateDictionary predicate) {
120+
var doc = TEST_JSON_MAPPER.readTree("""
121+
{"%s": ["%s"]}""".formatted(LABEL.getValue(), labelFor(lightWorkId)));
122+
var lightWork = new Resource()
123+
.addTypes(LIGHT_RESOURCE, WORK)
124+
.setDoc(doc)
125+
.setLabel(labelFor(lightWorkId))
126+
.setIdAndRefreshEdges(lightWorkId);
127+
work.addOutgoingEdge(new ResourceEdge(work, lightWork, predicate));
128+
}
129+
130+
private String labelFor(Long id) {
131+
return "light work label " + id;
132+
}
133+
}
134+

src/test/java/org/folio/linked/data/validation/entity/PrimaryTitleEntityValidatorTest.java

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

33
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
44
import static org.folio.ld.dictionary.ResourceTypeDictionary.INSTANCE;
5+
import static org.folio.ld.dictionary.ResourceTypeDictionary.LIGHT_RESOURCE;
56
import static org.folio.ld.dictionary.ResourceTypeDictionary.SERIES;
67
import static org.folio.ld.dictionary.ResourceTypeDictionary.TITLE;
78
import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK;
@@ -27,6 +28,21 @@ void shouldReturnTrue_ifGivenResourceIsNotInstanceOrWork() {
2728
// when
2829
boolean result = validator.isValid(resource, null);
2930

31+
// then
32+
// then
33+
assertThat(result).isTrue();
34+
}
35+
36+
@Test
37+
void shouldReturnTrue_ifGivenResourceIsWorkAndLightResourceWithEmptyOutgoingEdges() {
38+
// given
39+
var resource = new Resource()
40+
.addTypes(WORK, LIGHT_RESOURCE)
41+
.setOutgoingEdges(new HashSet<>());
42+
43+
// when
44+
var result = validator.isValid(resource, null);
45+
3046
// then
3147
assertThat(result).isTrue();
3248
}

0 commit comments

Comments
 (0)