View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.project.exporters.eag;
21  
22  import com.google.common.base.Splitter;
23  import com.google.common.collect.ImmutableList;
24  import com.google.common.collect.ImmutableMap;
25  import com.google.common.collect.Lists;
26  import com.google.common.collect.Sets;
27  import eu.ehri.project.api.Api;
28  import eu.ehri.project.definitions.ContactInfo;
29  import eu.ehri.project.definitions.Isdiah;
30  import eu.ehri.project.exporters.xml.AbstractStreamingXmlExporter;
31  import eu.ehri.project.models.Address;
32  import eu.ehri.project.models.Country;
33  import eu.ehri.project.models.MaintenanceEvent;
34  import eu.ehri.project.models.MaintenanceEventAgentType;
35  import eu.ehri.project.models.MaintenanceEventType;
36  import eu.ehri.project.models.Repository;
37  import eu.ehri.project.models.RepositoryDescription;
38  import eu.ehri.project.models.base.Actioner;
39  import eu.ehri.project.models.base.Described;
40  import eu.ehri.project.models.base.Description;
41  import eu.ehri.project.models.events.SystemEvent;
42  import eu.ehri.project.utils.LanguageHelpers;
43  import org.joda.time.DateTime;
44  import org.joda.time.format.DateTimeFormat;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import javax.xml.stream.XMLStreamWriter;
49  import java.util.List;
50  import java.util.Map;
51  import java.util.Optional;
52  import java.util.Set;
53  import java.util.stream.Collectors;
54  
55  /**
56   * Export EAG 2012 XML.
57   */
58  public final class Eag2012Exporter extends AbstractStreamingXmlExporter<Repository> implements EagExporter {
59      private static final Logger logger = LoggerFactory.getLogger(Eag2012Exporter.class);
60      private static final String DEFAULT_NAMESPACE = "http://www.archivesportaleurope.net/Portal/profiles/eag_2012/";
61  
62      private final Api api;
63  
64      private static final Map<Isdiah, String> descriptiveTextMappings = ImmutableMap.<Isdiah, String>builder()
65              .put(Isdiah.buildings, "buildinginfo/building")
66              .put(Isdiah.holdings, "holdings")
67              .put(Isdiah.conditions, "termsOfUse") // added to mandatory access
68              .put(Isdiah.researchServices, "services/searchroom/researchServices")
69              .build();
70  
71      private static final List<Isdiah> historyElements = ImmutableList.of(
72              Isdiah.history, Isdiah.geoculturalContext, Isdiah.mandates);
73  
74      private static final Map<String, String> NAMESPACES = namespaces(
75              "xlink", "http://www.w3.org/1999/xlink",
76              "ape", "http://www.archivesportaleurope.eu/functions",
77              "xsi", "http://www.w3.org/2001/XMLSchema-instance");
78  
79      private static final Map<String, String> ATTRS = attrs(
80              "audience", "external"
81      );
82  
83      public Eag2012Exporter(final Api api) {
84          this.api = api;
85      }
86  
87      @Override
88      public void export(XMLStreamWriter sw, Repository repository, String langCode) {
89  
90          comment(sw, resourceAsString("export-boilerplate.txt"));
91  
92          Country country = repository.getCountry();
93  
94          root(sw, "eag", DEFAULT_NAMESPACE, ATTRS, NAMESPACES, () -> {
95  
96              attribute(sw, "http://www.w3.org/2001/XMLSchema-instance", "schemaLocation",
97                      DEFAULT_NAMESPACE + " http://schemas.archivesportaleurope.net/profiles/eag.xsd");
98  
99              LanguageHelpers.getBestDescription(repository, Optional.empty(), langCode).ifPresent(desc -> {
100 
101                 addControlSection(sw, repository, country, desc);
102 
103                 tag(sw, "archguide", () -> {
104                     addIdentitySection(sw, repository, desc);
105 
106                     tag(sw, ImmutableList.of("desc", "repositories", "repository"), () -> {
107                         tag(sw, "geogarea", LanguageHelpers.countryCodeToContinent(country.getCode()).orElse("Europe"));
108 
109                         List<Address> addresses = Lists.newArrayList(desc.as(RepositoryDescription.class).getAddresses());
110                         if (addresses.isEmpty()) {
111                             // NB: A location tag is always needed ¯\_(ツ)_/¯
112                             tag(sw, "location", attrs("localType", "postal address"), () -> {
113                                 tag(sw, "country", LanguageHelpers.countryCodeToName(country.getCode()));
114                                 tag(sw, "municipalityPostalcode", (String) null);
115                                 tag(sw, "street", (String) null);
116                             });
117                         } else {
118                             for (Address address : addresses) {
119                                 tag(sw, "location", attrs("localType", "postal address"), () -> {
120                                     String cc = Optional.ofNullable(((String) address.getProperty(ContactInfo.countryCode)))
121                                             .orElse(country.getCode());
122                                     tag(sw, "country", LanguageHelpers.countryCodeToName(cc));
123                                     tag(sw, "municipalityPostalcode", address.<String>getProperty(ContactInfo.postalCode));
124                                     tag(sw, "street", address.<String>getProperty(ContactInfo.street));
125                                 });
126                             }
127                         }
128 
129                         for (ContactInfo contact : new ContactInfo[]{ContactInfo.telephone, ContactInfo.fax}) {
130                             for (Address address : (desc.as(RepositoryDescription.class)).getAddresses()) {
131                                 for (Object value : coerceList(address.getProperty(contact))) {
132                                     tag(sw, contact.name(), value.toString());
133                                 }
134                             }
135                         }
136                         for (ContactInfo ref : new ContactInfo[]{ContactInfo.email, ContactInfo.webpage}) {
137                             for (Address address : (desc.as(RepositoryDescription.class)).getAddresses()) {
138                                 for (Object value : coerceList(address.getProperty(ref))) {
139                                     tag(sw, ref.name(), value.toString(), attrs("href", value.toString()));
140                                 }
141                             }
142                         }
143 
144                         List<String> elems = historyElements
145                                 .stream().<String>map(desc::getProperty)
146                                 .filter(v -> v != null).collect(Collectors.toList());
147                         if (!elems.isEmpty()) {
148                             tag(sw, ImmutableList.of("repositorhist", "descriptiveNote"), () -> {
149                                 for (String e : elems) {
150                                     tag(sw, "p",
151                                             attrs("xml:lang", desc.getLanguageOfDescription()), () -> cData(sw, e));
152                                 }
153                             });
154                         }
155 
156                         addTextElements(sw, desc, Isdiah.buildings, Isdiah.holdings);
157 
158                         tag(sw, ImmutableList.of("timetable", "opening"), desc.<String>getProperty(Isdiah.openingTimes));
159 
160                         tag(sw, "access", attrs("question", "yes"), () ->
161                                 Optional.ofNullable(desc.<String>getProperty(Isdiah.conditions)).ifPresent(terms ->
162                                         tag(sw, "termsOfUse", terms)
163                                 )
164                         );
165 
166                         tag(sw, "accessibility", desc.getProperty(Isdiah.accessibility), attrs("question", "yes"));
167                         addTextElements(sw, desc, Isdiah.researchServices);
168                     });
169 
170                 });
171             });
172         });
173     }
174 
175     private void addControlSection(XMLStreamWriter sw, Repository repository, Country country, Description desc) {
176         tag(sw, "control", () -> {
177             tag(sw, "recordId", String.format("%s-%s", country.getCode().toUpperCase(),
178                     repository.getIdentifier()));
179 
180             tag(sw, "otherRecordId", repository.getId(), attrs("localType", "yes"));
181 
182             tag(sw, "maintenanceAgency", () -> {
183                 tag(sw, "agencyCode", "EHRI");
184                 tag(sw, "agencyName", "The EHRI Consortium");
185             });
186             tag(sw, "maintenanceStatus", "revised");
187 
188             addRevisionDesc(sw, repository, desc);
189         });
190     }
191 
192     private void addIdentitySection(XMLStreamWriter sw, Repository repository, Description desc) {
193 
194         tag(sw, "identity", () -> {
195             tag(sw, "repositorid", null,
196                     attrs("countrycode", repository.getCountry().getCode().toUpperCase()));
197             tag(sw, "autform", desc.getName(), attrs("xml:lang", desc.getLanguageOfDescription()));
198 
199             Optional.ofNullable(desc.getProperty(Isdiah.parallelFormsOfName)).ifPresent(parNames -> {
200                 List values = parNames instanceof List ? (List) parNames : ImmutableList.of(parNames);
201                 for (Object value : values) {
202                     tag(sw, "parform", value.toString());
203                 }
204             });
205             Optional.ofNullable(desc.getProperty(Isdiah.otherFormsOfName)).ifPresent(parNames -> {
206                 List values = parNames instanceof List ? (List) parNames : ImmutableList.of(parNames);
207                 for (Object value : values) {
208                     tag(sw, "parform", value.toString());
209                 }
210             });
211         });
212     }
213 
214     private void addRevisionDesc(XMLStreamWriter sw, Described entity, Description desc) {
215         tag(sw, "maintenanceHistory", () -> {
216 
217             List<MaintenanceEvent> maintenanceEvents = ImmutableList.copyOf(desc.getMaintenanceEvents());
218             for (MaintenanceEvent event : maintenanceEvents) {
219                 tag(sw, "maintenanceEvent", () -> {
220                     tag(sw, "agent", "EHRI");
221                     tag(sw, "agentType", MaintenanceEventAgentType.human.name());
222                     tag(sw, "eventDateTime", event.<String>getProperty("date"));
223                     tag(sw, "eventType", event.getEventType().name());
224                 });
225             }
226 
227             List<List<SystemEvent>> systemEvents = ImmutableList.copyOf(api.events().aggregateForItem(entity));
228             for (List<SystemEvent> agg : Lists.reverse(systemEvents)) {
229                 SystemEvent event = agg.get(0);
230 
231                 tag(sw, "maintenanceEvent", () -> {
232                     tag(sw, "agent", Optional.ofNullable(event.getActioner())
233                             .map(Actioner::getName).orElse(null));
234                     tag(sw, "agentType", MaintenanceEventAgentType.human.name());
235                     DateTime dateTime = new DateTime(event.getTimestamp());
236                     tag(sw, "eventDateTime", DateTimeFormat.longDateTime().print(dateTime), attrs(
237                             "standardDateTime", dateTime.toString()));
238                     tag(sw, "eventType", MaintenanceEventType
239                             .fromSystemEventType(event.getEventType()).name());
240                 });
241             }
242 
243             // We must provide a default event
244             if (maintenanceEvents.isEmpty() && systemEvents.isEmpty()) {
245                 logger.debug("No events found for element {}, using fallback", entity.getId());
246 
247                 tag(sw, "maintenanceEvent", () -> {
248                     tag(sw, "agent", entity.getId());
249                     tag(sw, "agentType", MaintenanceEventAgentType.machine.name());
250                     DateTime dateTime = DateTime.now();
251                     tag(sw, "eventDateTime", DateTimeFormat.longDateTime().print(dateTime), attrs(
252                             "standardDateTime", dateTime.toString()));
253                     tag(sw, "eventType", MaintenanceEventType.created.name());
254                 });
255             }
256         });
257     }
258 
259     private void addTextElements(XMLStreamWriter sw, Description desc, Isdiah... toAdd) {
260         Set<Isdiah> adding = Sets.newHashSet(toAdd);
261         final Map<String, String> paraAttrs = attrs("xml:lang", desc.getLanguageOfDescription());
262         for (Map.Entry<Isdiah, String> entry : descriptiveTextMappings.entrySet()) {
263             if (adding.contains(entry.getKey())) {
264                 Optional.ofNullable(desc.<String>getProperty(entry.getKey())).ifPresent(prop -> {
265                     List<String> tags = Splitter.on("/").splitToList(entry.getValue());
266                     tag(sw, tags, () ->
267                             tag(sw, ImmutableList.of("descriptiveNote", "p"), paraAttrs, () -> cData(sw, prop)));
268                 });
269             }
270         }
271     }
272 }