1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package de.kaiserpfalzedv.services.dnb.marcxml;
28
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.stream.Collectors;
36
37 import org.apache.commons.io.IOUtils;
38 import org.springframework.stereotype.Service;
39
40 import com.fasterxml.jackson.databind.ObjectMapper;
41 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
42
43 import de.kaiserpfalzedv.services.dnb.marcxml.model.DataField;
44 import de.kaiserpfalzedv.services.dnb.marcxml.model.Record;
45 import de.kaiserpfalzedv.services.dnb.marcxml.model.SearchRetrieveResponse;
46 import de.kaiserpfalzedv.services.dnb.marcxml.model.SubField;
47 import de.kaiserpfalzedv.services.dnb.model.Book;
48 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
49 import jakarta.inject.Inject;
50 import jakarta.validation.constraints.NotBlank;
51 import lombok.RequiredArgsConstructor;
52 import lombok.extern.slf4j.Slf4j;
53
54
55
56
57
58
59
60
61 @Service
62 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "it*s a lombok generated constructor")
63 @RequiredArgsConstructor(onConstructor = @__(@Inject))
64 @Slf4j
65 public class MarcConverter {
66 private final XmlMapper mapper = new XmlMapper();
67 private final ObjectMapper jsonMapper;
68
69 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Nothing get's stored here.")
70 public List<Book> convert(final SearchRetrieveResponse response) {
71 log.info("Converting response. query='{}', count={}",
72 response.getEchoedSearchRetrieveRequest().getQuery(), response.getNumberOfRecords());
73
74 return response.getRecords().stream()
75 .map(this::mapRecordToBook)
76 .toList();
77 }
78
79 @SuppressWarnings({ "rawtypes", "unchecked" })
80 private Book mapRecordToBook(final Record record) {
81 final Book.BookBuilder result = Book.builder();
82
83 log.debug("Converting record. position={}, schema='{}', packing='{}', datafields={}",
84 record.getRecordPosition(), record.getRecordSchema(), record.getRecordPacking(),
85 record.getRecordData().getRecord().getDatafields());
86
87 final List<DataField> data = record.getRecordData().getRecord().getDatafields();
88
89 final List<String> isbns = new ArrayList<>();
90 final List<String> authors = new ArrayList<>();
91 for (final DataField d : data) {
92 log.trace("Working on data field. tag='{}', ind1='{}', ind2='{}' subfields={}",
93 d.getTag(), d.getInd1(), d.getInd2(), d.getSubfield());
94
95 switch(d.getTag()) {
96 case "024":
97 result.ean(this.getSubField(d, "a"));
98 break;
99
100 case "020":
101 isbns.add(this.getSubField(d, "a"));
102 isbns.add(this.getSubField(d, "9"));
103 break;
104
105 case "245":
106 result.title(this.getSubField(d, "a"));
107 result.subTitle(this.getSubField(d, "b"));
108 break;
109
110 case "490":
111 result.series(this.getSubField(d, "a"));
112 break;
113
114 case "100":
115 case "700":
116 authors.add(this.getSubField(d, "a"));
117 break;
118
119 case "710":
120 result.publisher(this.getSubField(d, "a"));
121 break;
122
123 case "264":
124 result.publisher(this.getSubField(d, "b"));
125 result.placeOfPublication(this.getSubField(d, "a"));
126 break;
127
128 default:
129 continue;
130 }
131 }
132
133 result.authors(authors.stream().filter(f -> !f.isBlank()).collect(Collectors.toList()));
134 result.isbns(isbns.stream().filter(f -> !f.isBlank()).collect(Collectors.toList()));
135
136 return result.build();
137 }
138
139 private String getSubField(final DataField data, @NotBlank final String code) {
140 final List<String> result = data.getSubfield().stream()
141 .map(f -> this.retrieveSubField(f, code))
142 .filter(s -> !s.isBlank())
143 .toList();
144
145 if (! result.isEmpty()) {
146 return result.get(0);
147 }
148
149 return "";
150 }
151
152 private String retrieveSubField(final SubField field, final String code) {
153 if (code.equals(field.getCode())) {
154 return field.getContent();
155 }
156
157 return "";
158 }
159
160
161 public List<Book> convert(final InputStream is) {
162 try {
163 return this.convert(this.mapper.readValue(is, SearchRetrieveResponse.class));
164 } catch (final IOException e) {
165 throw new LibraryLookupMarc21MappingException(e);
166 }
167 }
168
169 public InputStream convertXml(final List<Book> books) {
170 try {
171 return IOUtils.toInputStream(this.mapper.writeValueAsString(books), Charset.defaultCharset());
172 } catch (final IOException e) {
173 throw new LibraryLookupMarc21MappingException(e);
174 }
175 }
176
177 public InputStream convert(final List<Book> books) {
178 try {
179 return IOUtils.toInputStream(new String(this.jsonMapper.writeValueAsBytes(books), StandardCharsets.UTF_8), StandardCharsets.UTF_8);
180 } catch (final IOException e) {
181 throw new LibraryLookupMarc21MappingException(e);
182 }
183 }
184 }