jenkins merging release branch into master (strategy: theirs)
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / cdm / dataInserter / RegistrationRequiredDataInserter.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.dataInserter;
10
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.EnumSet;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.UUID;
21
22 import org.apache.log4j.Logger;
23 import org.joda.time.DateTime;
24 import org.joda.time.DateTimeFieldType;
25 import org.joda.time.Partial;
26 import org.joda.time.format.DateTimeFormatter;
27 import org.springframework.context.event.ContextRefreshedEvent;
28 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
29 import org.springframework.security.core.GrantedAuthority;
30 import org.springframework.transaction.TransactionStatus;
31 import org.springframework.transaction.annotation.Transactional;
32
33 import com.fasterxml.jackson.core.JsonParseException;
34 import com.fasterxml.jackson.databind.JsonMappingException;
35 import com.fasterxml.jackson.databind.ObjectMapper;
36
37 import eu.etaxonomy.cdm.api.application.AbstractDataInserter;
38 import eu.etaxonomy.cdm.api.application.CdmRepository;
39 import eu.etaxonomy.cdm.api.service.pager.Pager;
40 import eu.etaxonomy.cdm.model.agent.AgentBase;
41 import eu.etaxonomy.cdm.model.agent.Institution;
42 import eu.etaxonomy.cdm.model.common.ExtensionType;
43 import eu.etaxonomy.cdm.model.common.GrantedAuthorityImpl;
44 import eu.etaxonomy.cdm.model.common.Group;
45 import eu.etaxonomy.cdm.model.common.TimePeriod;
46 import eu.etaxonomy.cdm.model.name.Registration;
47 import eu.etaxonomy.cdm.model.name.RegistrationStatus;
48 import eu.etaxonomy.cdm.model.name.TaxonName;
49 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
50 import eu.etaxonomy.cdm.model.reference.Reference;
51 import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
52 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmAuthority;
53 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionClass;
54 import eu.etaxonomy.cdm.persistence.hibernate.permission.Role;
55 import eu.etaxonomy.cdm.persistence.query.MatchMode;
56 import eu.etaxonomy.cdm.vaadin.model.registration.KindOfUnitTerms;
57 import eu.etaxonomy.cdm.vaadin.security.RolesAndPermissions;
58
59 /**
60 *
61 * Can create missing registrations for names which have Extensions of the Type <code>IAPTRegdata.json</code>.
62 * See https://dev.e-taxonomy.eu/redmine/issues/6621 for further details.
63 * This feature can be activated by by supplying one of the following jvm command line arguments:
64 * <ul>
65 * <li><code>-DregistrationCreate=iapt</code>: create all iapt Registrations if missing</li>
66 * <li><code>-DregistrationWipeout=iapt</code>: remove all iapt Registrations</li>
67 * <li><code>-DregistrationWipeout=all</code>: remove all Registrations</li>
68 * </ul>
69 * The <code>-DregistrationWipeout</code> commands are executed before the <code>-DregistrationCreate</code> and will not change the name and type designations.
70 *
71 * @author a.kohlbecker
72 * @since May 9, 2017
73 *
74 */
75 public class RegistrationRequiredDataInserter extends AbstractDataInserter {
76
77 protected static final String PARAM_NAME_CREATE = "registrationCreate";
78
79 protected static final String PARAM_NAME_WIPEOUT = "registrationWipeout";
80
81 protected static final UUID GROUP_SUBMITTER_UUID = UUID.fromString("c468c6a7-b96c-4206-849d-5a825f806d3e");
82
83 protected static final UUID GROUP_CURATOR_UUID = UUID.fromString("135210d3-3db7-4a81-ab36-240444637d45");
84
85 private static final EnumSet<CRUD> CREATE_READ = EnumSet.of(CRUD.CREATE, CRUD.READ);
86 private static final EnumSet<CRUD> CREATE_READ_UPDATE_DELETE = EnumSet.of(CRUD.CREATE, CRUD.READ, CRUD.UPDATE, CRUD.DELETE);
87
88 private static final Logger logger = Logger.getLogger(RegistrationRequiredDataInserter.class);
89
90 private ExtensionType extensionTypeIAPTRegData;
91
92 Map<String, Institution> instituteMap = new HashMap<>();
93
94 public static boolean commandsExecuted = false;
95
96 private CdmRepository repo;
97
98 private boolean hasRun = false;
99
100 public void setCdmRepository(CdmRepository repo){
101 this.repo = repo;
102 }
103
104
105 // ==================== Registration creation ======================= //
106
107 /**
108 * {@inheritDoc}
109 */
110 @Override
111 public void onApplicationEvent(ContextRefreshedEvent event) {
112
113 if(hasRun){
114 return;
115 }
116
117 runAsAuthentication(Role.ROLE_ADMIN);
118
119 insertRequiredData();
120 executeSuppliedCommands();
121
122 restoreAuthentication();
123
124 hasRun = true;
125 }
126
127 /**
128 *
129 */
130 @Transactional
131 private void insertRequiredData() {
132
133 TransactionStatus txStatus = repo.startTransaction(false);
134
135 Role roleCuration = RolesAndPermissions.ROLE_CURATION;
136 if(repo.getGrantedAuthorityService().find(roleCuration.getUuid()) == null){
137 repo.getGrantedAuthorityService().saveOrUpdate(roleCuration.asNewGrantedAuthority());
138 }
139
140 Group groupCurator = repo.getGroupService().load(GROUP_CURATOR_UUID, Arrays.asList("grantedAuthorities"));
141 if(groupCurator == null){
142 groupCurator = Group.NewInstance();
143 groupCurator.setUuid(GROUP_CURATOR_UUID);
144 groupCurator.setName("Curator");
145 }
146 assureGroupHas(groupCurator, new CdmAuthority(CdmPermissionClass.REGISTRATION, CREATE_READ_UPDATE_DELETE).toString());
147 repo.getGroupService().saveOrUpdate(groupCurator);
148
149 Group groupSubmitter = repo.getGroupService().load(GROUP_SUBMITTER_UUID, Arrays.asList("grantedAuthorities"));
150 if(groupSubmitter == null){
151 groupSubmitter = Group.NewInstance();
152 groupSubmitter.setUuid(GROUP_SUBMITTER_UUID);
153 groupSubmitter.setName("Submitter");
154 }
155 assureGroupHas(groupSubmitter, new CdmAuthority(CdmPermissionClass.TAXONNAME, CREATE_READ).toString());
156 assureGroupHas(groupSubmitter, new CdmAuthority(CdmPermissionClass.TEAMORPERSONBASE, CREATE_READ).toString());
157 assureGroupHas(groupSubmitter, new CdmAuthority(CdmPermissionClass.REGISTRATION, CREATE_READ).toString());
158 assureGroupHas(groupSubmitter, new CdmAuthority(CdmPermissionClass.REFERENCE, CREATE_READ).toString());
159 assureGroupHas(groupSubmitter, new CdmAuthority(CdmPermissionClass.SPECIMENOROBSERVATIONBASE, CREATE_READ).toString());
160 repo.getGroupService().saveOrUpdate(groupSubmitter);
161
162 if(repo.getTermService().find(KindOfUnitTerms.SPECIMEN().getUuid()) == null){
163 repo.getTermService().save(KindOfUnitTerms.SPECIMEN());
164 }
165 if(repo.getTermService().find(KindOfUnitTerms.PUBLISHED_IMAGE().getUuid()) == null){
166 repo.getTermService().save(KindOfUnitTerms.PUBLISHED_IMAGE());
167 }
168 if(repo.getTermService().find(KindOfUnitTerms.UNPUBLISHED_IMAGE().getUuid()) == null){
169 repo.getTermService().save(KindOfUnitTerms.UNPUBLISHED_IMAGE());
170 }
171 if(repo.getTermService().find(KindOfUnitTerms.CULTURE_METABOLIC_INACTIVE().getUuid()) == null){
172 repo.getTermService().save(KindOfUnitTerms.CULTURE_METABOLIC_INACTIVE());
173 }
174
175 // --- remove after release 4.12.0 ------------------------------------------------------
176 // delete old DerivationEventTypes terms which are no longer used, see #7059
177 // UUID_PUBLISHED_IMAGE = UUID.fromString("b8cba359-4202-4741-8ed8-4f17ae94b3e3");
178 // UUID UUID_UNPUBLISHED_IMAGE = UUID.fromString("6cd5681f-0918-4ed6-89a8-bda1480dc890");
179 // UUID UUID_CULTURE_METABOLIC_INACTIVE = UUID.fromString("eaf1c853-ba8d-4c40-aa0a-56beac96b0d2");
180 for(UUID uuid : new UUID[]{
181 UUID.fromString("b8cba359-4202-4741-8ed8-4f17ae94b3e3"),
182 UUID.fromString("6cd5681f-0918-4ed6-89a8-bda1480dc890"),
183 UUID.fromString("eaf1c853-ba8d-4c40-aa0a-56beac96b0d2")}){
184 if(repo.getTermService().find(uuid) != null){
185 repo.getTermService().delete(uuid);
186 }
187 }
188 // --------------------------------------------------------------------------------------
189 txStatus.flush();
190 repo.commitTransaction(txStatus);
191
192 }
193
194 private void assureGroupHas(Group group, String authorityString){
195 boolean authorityExists = false;
196
197 for(GrantedAuthority ga : group.getGrantedAuthorities()){
198 if((authorityExists = ga.getAuthority().equals(authorityString)) == true){
199 break;
200 }
201 }
202 if(!authorityExists){
203 group.addGrantedAuthority(findGrantedAuthority(authorityString));
204 }
205 }
206
207 private GrantedAuthorityImpl findGrantedAuthority(String authorityString){
208 GrantedAuthorityImpl ga = null;
209 try{
210 ga = repo.getGrantedAuthorityService().findAuthorityString(authorityString);
211 } catch (AuthenticationCredentialsNotFoundException e){
212 e.printStackTrace();
213 }
214 if(ga == null){
215 ga = GrantedAuthorityImpl.NewInstance(authorityString);
216 repo.getGrantedAuthorityService().save(ga);
217 }
218 return ga;
219 }
220
221 /**
222 *
223 */
224
225 private void executeSuppliedCommands() {
226
227 if(commandsExecuted){
228 // do not run twice
229 // a second run could take place during initialization of the web context
230 return;
231 }
232 commandsExecuted = true;
233
234 String wipeoutCmd = System.getProperty(PARAM_NAME_WIPEOUT);
235 String createCmd = System.getProperty(PARAM_NAME_CREATE);
236
237 // ============ DELETE
238 if(wipeoutCmd != null && wipeoutCmd.matches("iapt|all")){
239
240 boolean onlyIapt = wipeoutCmd.equals("iapt");
241 Set<UUID> deleteCandidates = new HashSet<UUID>();
242
243 TransactionStatus tx = repo.startTransaction(true);
244 List<Registration> allRegs = repo.getRegistrationService().list(null, null, null, null, null);
245 for(Registration reg : allRegs){
246 if(onlyIapt){
247 try {
248 @SuppressWarnings("unchecked")
249 Set<String> extensions = reg.getName().getExtensions(getExtensionTypeIAPTRegData());
250 if(reg.getUuid() != null){
251 deleteCandidates.add(reg.getUuid());
252 }
253 } catch(NullPointerException e){
254 // IGNORE
255 }
256 } else {
257 if(reg.getUuid() != null){
258 deleteCandidates.add(reg.getUuid());
259 }
260 }
261 }
262 repo.commitTransaction(tx);
263 if(!deleteCandidates.isEmpty()){
264 try {
265 repo.getRegistrationService().delete(deleteCandidates);
266 } catch (Exception e) {
267 // MySQLIntegrityConstraintViolationException happens here every second run !!!
268 logger.error(e);
269 }
270 }
271 }
272
273 // ============ CREATE
274 int pageIndex = 0;
275 if(createCmd != null && createCmd.equals("iapt")){
276
277 DateTimeFormatter dateFormat1 = org.joda.time.format.DateTimeFormat.forPattern("dd.MM.yy").withPivotYear(1950);
278 DateTimeFormatter dateFormat2 = org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd").withPivotYear(1950);
279
280 TransactionStatus tx = repo.startTransaction(false);
281 while(true) {
282 Pager<TaxonName> pager = repo.getNameService().page(null, 1000, pageIndex, null, null);
283 if(pager.getRecords().isEmpty()){
284 break;
285 }
286 List<Registration> newRegs = new ArrayList<>(pager.getRecords().size());
287 for(TaxonName name : pager.getRecords()){
288
289
290
291 Set<String> extensionValues = name.getExtensions(getExtensionTypeIAPTRegData());
292
293 // there is for sure only one
294 if(extensionValues.isEmpty()){
295 continue;
296 }
297
298 logger.debug("IAPT Registration for " + name.getTitleCache() + " ...");
299
300 String iaptJson = extensionValues.iterator().next();
301 try {
302
303 IAPTRegData iaptData = new ObjectMapper().readValue(iaptJson, IAPTRegData.class);
304
305 if(iaptData.getRegId() == null){
306 continue;
307 }
308
309 DateTime regDate = null;
310 if(iaptData.getDate() != null){
311 DateTimeFormatter dateFormat;
312 if(iaptData.getDate().matches("\\d{4}-\\d{2}-\\d{2}")){
313 dateFormat = dateFormat2;
314 } else {
315 dateFormat = dateFormat1;
316 }
317 try {
318 regDate = dateFormat.parseDateTime(iaptData.getDate());
319 regDate.getYear();
320 } catch (Exception e) {
321 logger.error("Error parsing date : " + iaptData.getDate(), e);
322 continue;
323 }
324 }
325
326 Registration reg = Registration.NewInstance();
327 reg.setStatus(RegistrationStatus.PUBLISHED);
328 reg.setIdentifier("http://phycobank.org/" + iaptData.getRegId());
329 reg.setSpecificIdentifier(iaptData.getRegId().toString());
330 reg.setInstitution(getInstitution(iaptData.getOffice()));
331
332 boolean isPhycobankID = Integer.valueOf(reg.getSpecificIdentifier()) >= 100000;
333
334 Partial youngestDate = null;
335 Reference youngestPub = null;
336
337 // find youngest publication
338
339 // NOTE:
340 // data imported from IAPT does not have typedesignation citations and sometimes no nomref
341
342 if(isPhycobankID){
343 youngestPub = (Reference) name.getNomenclaturalReference();
344 youngestDate = partial(youngestPub.getDatePublished());
345
346 if(name.getTypeDesignations() != null && !name.getTypeDesignations().isEmpty()){
347 for(TypeDesignationBase td : name.getTypeDesignations()){
348 if(td.getCitation() == null){
349 continue;
350 }
351 Partial pubdate = partial(td.getCitation().getDatePublished());
352 if(pubdate != null){
353 if(youngestDate== null || comparePartials(youngestDate, pubdate)){
354 youngestDate = pubdate;
355 youngestPub = td.getCitation();
356 }
357 }
358 }
359 }
360 }
361
362 if((isPhycobankID && youngestPub == name.getNomenclaturalReference()) || !isPhycobankID) {
363 reg.setName(name);
364 } else {
365 logger.debug("skipping name published in older referece");
366 }
367 if(name.getTypeDesignations() != null && !name.getTypeDesignations().isEmpty()){
368 // do not add the collection directly to avoid "Found shared references to a collection" problem
369 HashSet<TypeDesignationBase> typeDesignations = new HashSet<>(name.getTypeDesignations().size());
370 for(TypeDesignationBase td : name.getTypeDesignations()){
371 if(td.getCitation() == null && isPhycobankID){
372 logger.error("Missing TypeDesignation Citation in Phycobank data");
373 continue;
374 }
375 if((isPhycobankID && youngestPub == td.getCitation()) || !isPhycobankID){
376 typeDesignations.add(td);
377 } else {
378 logger.debug("skipping typedesignation published in older reference");
379 }
380 }
381 reg.setTypeDesignations(typeDesignations);
382 }
383 reg.setRegistrationDate(regDate);
384 newRegs.add(reg);
385
386 } catch (JsonParseException e) {
387 logger.error("Error parsing IAPTRegData from extension", e);
388 } catch (JsonMappingException e) {
389 logger.error("Error mapping json from extension to IAPTRegData", e);
390 } catch (IOException e) {
391 logger.error(e);
392 }
393
394 }
395 repo.getRegistrationService().save(newRegs);
396 tx.flush();
397 logger.debug("Registrations saved");
398 pageIndex++;
399 }
400 repo.commitTransaction(tx);
401 }
402 }
403
404
405 /**
406 * @param youngestDate
407 * @param pubdate
408 * @return
409 */
410 protected boolean comparePartials(Partial youngestDate, Partial pubdate) {
411
412 if(youngestDate.size() == pubdate.size()) {
413 return youngestDate.compareTo(pubdate) < 0;
414 }
415 youngestDate = youngestDate.without(DateTimeFieldType.dayOfMonth());
416 pubdate = pubdate.without(DateTimeFieldType.dayOfMonth());
417 if(youngestDate.size() == pubdate.size()) {
418 return youngestDate.compareTo(pubdate) < 0;
419 }
420 youngestDate = youngestDate.without(DateTimeFieldType.monthOfYear());
421 pubdate = pubdate.without(DateTimeFieldType.monthOfYear());
422 return youngestDate.compareTo(pubdate) < 0;
423
424 }
425
426
427 /**
428 * @param datePublished
429 * @return
430 */
431 private Partial partial(TimePeriod datePublished) {
432 if(datePublished != null){
433 if(datePublished.getEnd() != null){
434 return datePublished.getEnd();
435 } else {
436 return datePublished.getStart();
437 }
438 }
439 return null;
440 }
441
442
443 /**
444 * @param office
445 * @return
446 */
447 private Institution getInstitution(String office) {
448 Institution institution;
449 if(instituteMap.containsKey(office)){
450 institution = instituteMap.get(office);
451 } else {
452
453 Pager<AgentBase> pager = repo.getAgentService().findByTitle(Institution.class, office, MatchMode.EXACT, null, null, null, null, null);
454 if(!pager.getRecords().isEmpty()){
455 institution = (Institution) pager.getRecords().get(0);
456 } else {
457 Institution institute = (Institution) repo.getAgentService().save(Institution.NewNamedInstance(office));
458 institution = institute;
459 }
460 instituteMap.put(office, institution);
461 }
462 return institution;
463 }
464
465
466 private ExtensionType getExtensionTypeIAPTRegData() {
467 if(extensionTypeIAPTRegData == null){
468 extensionTypeIAPTRegData = (ExtensionType) repo.getTermService().load(UUID.fromString("9be1bfe3-6ba0-4560-af15-86971ab96e09"));
469 }
470 return extensionTypeIAPTRegData;
471 }
472
473
474
475 }