1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package fr.cnes.doi.client;
20
21 import static fr.cnes.doi.client.ClientMDS.METADATA_RESOURCE;
22 import fr.cnes.doi.exception.ClientMdsException;
23 import fr.cnes.doi.settings.DoiSettings;
24 import fr.cnes.doi.utils.Utils;
25 import fr.cnes.doi.utils.spec.Requirement;
26 import java.io.IOException;
27 import java.nio.charset.StandardCharsets;
28 import java.text.MessageFormat;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.logging.Level;
36 import javax.xml.XMLConstants;
37 import javax.xml.bind.JAXBException;
38 import javax.xml.bind.ValidationEvent;
39 import javax.xml.bind.ValidationEventHandler;
40 import javax.xml.bind.ValidationException;
41 import javax.xml.validation.SchemaFactory;
42 import org.datacite.schema.kernel_4.Resource;
43 import org.datacite.schema.kernel_4.Resource.Identifier;
44 import org.restlet.data.ChallengeScheme;
45 import org.restlet.data.CharacterSet;
46 import org.restlet.data.Form;
47 import org.restlet.data.Language;
48 import org.restlet.data.MediaType;
49 import org.restlet.data.Parameter;
50 import org.restlet.data.Reference;
51 import org.restlet.data.Status;
52 import org.restlet.ext.jaxb.JaxbRepresentation;
53 import org.restlet.representation.Representation;
54 import org.restlet.representation.StringRepresentation;
55 import org.restlet.resource.ResourceException;
56
57
58
59
60
61
62
63
64 @Requirement(reqId = Requirement.DOI_INTER_010, reqName = Requirement.DOI_INTER_010_NAME)
65 public class ClientMDS extends BaseClient {
66
67
68
69
70 public static final String DATA_CITE_URL = "https://mds.datacite.org";
71
72
73
74
75 public static final String DATA_CITE_MOCK_URL = "http://localhost:" + DATACITE_MOCKSERVER_PORT;
76
77
78
79
80 public static final String DATA_CITE_TEST_URL = "https://mds.test.datacite.org";
81
82
83
84
85 public static final String DOI_RESOURCE = "doi";
86
87
88
89
90 public static final String METADATA_RESOURCE = "metadata";
91
92
93
94
95 public static final String MEDIA_RESOURCE = "media";
96
97
98
99
100 public static final Parameter TEST_MODE = new Parameter("testMode", "true");
101
102
103
104
105 public static final String TEST_DOI_PREFIX = "10.80163";
106
107
108
109
110 public static final String POST_DOI = "doi";
111
112
113
114
115 public static final String POST_URL = "url";
116
117
118
119
120 public static final String SCHEMA_DATACITE = "https://schema.datacite.org/meta/kernel-4-1/metadata.xsd";
121
122
123
124
125 private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(
126 XMLConstants.W3C_XML_SCHEMA_NS_URI
127 );
128
129
130
131
132 private static final DoiSettings DOI_SETTINGS = DoiSettings.getInstance();
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 public static void checkIfAllCharsAreValid(final String test) {
154 if (!test.matches("^[0-9a-zA-Z\\-._+:/\\s]+$")) {
155 throw new IllegalArgumentException("Only these characters are allowed "
156 + "0-9a-zA-Z\\-._+:/ in a DOI name");
157 }
158 }
159
160
161
162 private final Parameter testMode;
163
164
165
166 private final Context context;
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 public ClientMDS(final Context context) throws ClientMdsException {
198 super(context.getDataCiteUrl());
199 this.context = context;
200 this.testMode = this.context.hasTestMode() ? TEST_MODE : null;
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 public ClientMDS(final Context context,
230 final String login,
231 final String pwd) throws ClientMdsException {
232 this(context);
233 this.getLog().debug("Authentication with HTTP_BASIC : {}/{}",
234 login, Utils.transformPasswordToStars(pwd));
235 this.getClient().setChallengeResponse(ChallengeScheme.HTTP_BASIC, login, pwd);
236 }
237
238
239
240
241
242
243
244
245
246 public ClientMDS(final String login,
247 final String pwd) throws ClientMdsException {
248 this(Context.PROD);
249 this.getLog().debug("Authentication with HTTP_BASIC : {}/{}",
250 login, Utils.transformPasswordToStars(pwd));
251 this.getClient().setChallengeResponse(ChallengeScheme.HTTP_BASIC, login, pwd);
252 }
253
254
255
256
257
258
259
260 private Parameter getTestMode() {
261 return this.testMode;
262 }
263
264
265
266
267
268
269
270 private String useTestPrefix(final String doiName) {
271 final String[] split = doiName.split("/");
272 split[0] = TEST_DOI_PREFIX;
273 final String testingPrefix = String.join("/", split);
274 final String message = String.format(
275 "DOI %s has been renamed as %s for testing", doiName, testingPrefix
276 );
277 this.getLog().warn(message);
278 return testingPrefix;
279 }
280
281
282
283
284
285 private void initReference() {
286 this.getClient().setReference(this.context.getDataCiteUrl());
287 }
288
289
290
291
292
293
294
295 private Reference createReference(final String segment) {
296 this.initReference();
297 Reference url = this.getClient().addSegment(segment);
298 if (this.getTestMode() != null) {
299 url = url.addQueryParameter(this.getTestMode());
300 }
301 return url;
302 }
303
304
305
306
307
308
309
310
311 private Reference createReferenceWithDOI(final String segment,
312 final String doiName) {
313 final String requestDOI = getDoiAccorgindToContext(doiName);
314 final Reference ref = createReference(segment);
315 final String[] split = requestDOI.split("/");
316 for (final String segmentUri : split) {
317 ref.addSegment(segmentUri);
318 }
319 return ref;
320 }
321
322
323
324
325
326
327
328
329
330 private String getDoiAccorgindToContext(final String doiName) {
331 return this.context.hasDoiTestPrefix() ? useTestPrefix(doiName) : doiName;
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345 private void checkInputForm(final Form form) throws IllegalArgumentException {
346 final Map<String, String> map = form.getValuesMap();
347 if (map.containsKey(POST_DOI) && map.containsKey(POST_URL)) {
348 final String doiName = form.getFirstValue(POST_DOI);
349 checkIfAllCharsAreValid(doiName);
350 form.set(POST_DOI, getDoiAccorgindToContext(doiName));
351 } else {
352 throw new IllegalArgumentException(MessageFormat.format(
353 "{0} and {1} parameters are required",
354 POST_DOI, POST_URL)
355 );
356 }
357 }
358
359
360
361
362
363
364
365
366
367 private String getText(final Representation rep) throws ClientMdsException {
368 final String result;
369 try {
370 result = rep.getText();
371 } catch (IOException ex) {
372 throw new ClientMdsException(Status.SERVER_ERROR_INTERNAL, ex);
373 }
374 return result;
375 }
376
377
378
379
380
381
382
383
384
385 private List<String> getList(final String segment) throws ClientMdsException {
386 try {
387 final Reference ref = this.createReference(segment);
388 this.getClient().setReference(ref);
389 final Representation rep = this.getClient().get();
390 final Status status = this.getClient().getStatus();
391 if (status.isSuccess()) {
392 final String result = rep.getText();
393 return Arrays.asList(result.split("\n"));
394 } else {
395 throw new ClientMdsException(status, status.getDescription());
396 }
397 } catch (IOException | ResourceException ex) {
398 throw new ClientMdsException(Status.SERVER_ERROR_INTERNAL, ex.getMessage(), ex);
399 } finally {
400 this.getClient().release();
401 }
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 public String getDoi(final String doiName) throws ClientMdsException {
423 final Reference url = createReferenceWithDOI(DOI_RESOURCE, doiName);
424 this.getLog().info("GET {0}", url.toString());
425
426 this.getClient().setReference(url);
427 Representation rep;
428 try {
429 rep = this.getClient().get();
430 return (rep == null) ? "" : this.getText(rep);
431 } catch (ResourceException ex) {
432 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
433 getResponseEntity(), ex);
434 } finally {
435 this.getClient().release();
436 }
437 }
438
439 public List<String> getDois() throws ClientMdsException {
440 return getList(DOI_RESOURCE);
441 }
442
443
444
445
446
447
448
449
450
451
452 public List<String> getDois(final String idProject) throws ClientMdsException {
453 final List<String> doiListFiltered = new ArrayList<>();
454 for (final String doi : this.getDois()) {
455 if (doi.contains(idProject)) {
456 doiListFiltered.add(doi);
457 }
458 }
459 return Collections.unmodifiableList(doiListFiltered);
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486 public String createDoi(final Form form) throws ClientMdsException {
487 try {
488 this.checkInputForm(form);
489 final Reference url = createReference(DOI_RESOURCE+"/"+form.getFirstValue(POST_DOI));
490 this.getLog().debug("PUT {0}", url.toString());
491 final Representation rep = createRequest(url, form);
492 return getText(rep);
493 } catch (ResourceException ex) {
494 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
495 getResponseEntity(), ex);
496 } finally {
497 this.getClient().release();
498 }
499 }
500
501
502
503
504
505
506
507
508 private Representation createRequest(final Reference url, final Form form) {
509 this.getClient().setReference(url);
510 String requestBody = POST_DOI + "=" + form.getFirstValue(POST_DOI) + "\n"
511 + POST_URL + "=" + form.getFirstValue(POST_URL);
512 requestBody = new String(requestBody.getBytes(
513 StandardCharsets.UTF_8),
514 StandardCharsets.UTF_8
515 );
516 final Map<String, Object> requestAttributes = this.getClient().getRequestAttributes();
517 requestAttributes.put("charset", StandardCharsets.UTF_8);
518 requestAttributes.put("Content-Type", "text/plain");
519 this.getLog().info("PUT {} with parameters {}", url, requestBody);
520 return this.getClient().put(requestBody, MediaType.TEXT_PLAIN);
521 }
522
523
524
525
526
527
528
529
530
531 private synchronized Resource parseDataciteResource(final Representation rep) throws
532 ClientMdsException {
533 final JaxbRepresentation<Resource> resource = new JaxbRepresentation<>(rep, Resource.class);
534 try {
535 return resource.getObject();
536 } catch (IOException ex) {
537 throw new ClientMdsException(Status.SERVER_ERROR_INTERNAL, ex);
538 }
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 public Resource getMetadataAsObject(final String doiName) throws ClientMdsException {
560 final Representation rep = getMetadata(doiName);
561 return parseDataciteResource(rep);
562 }
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584 public Representation getMetadata(final String doiName) throws ClientMdsException {
585 final Reference url = createReferenceWithDOI(METADATA_RESOURCE, doiName);
586 this.getLog().debug("GET {}", url.toString());
587 this.getClient().setReference(url);
588 this.getLog().info("GET {}", url);
589 try {
590 return this.getClient().get(MediaType.APPLICATION_XML);
591 } catch (ResourceException ex) {
592 this.getClient().release();
593 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
594 getResponseEntity(), ex);
595 }
596 }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615 public String createMetadata(final Representation entity) throws ClientMdsException {
616 final Resource resource = parseDataciteResource(entity);
617 return this.createMetadata(resource);
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638 public String createMetadata(final Resource entity) throws ClientMdsException {
639 try {
640 final Identifier identifier = entity.getIdentifier();
641 identifier.setValue(getDoiAccorgindToContext(identifier.getValue()));
642 final Reference url = createReference(METADATA_RESOURCE+"/"+identifier.getValue());
643 this.getLog().debug("PUT {}", url.toString());
644 final JaxbRepresentation<Resource> result = new JaxbRepresentation<>(entity);
645 result.setCharacterSet(CharacterSet.UTF_8);
646 result.setMediaType(MediaType.APPLICATION_XML);
647 this.getClient().setReference(url);
648 this.getClient().getRequestAttributes().put("Content-Type", "application/xml");
649 this.getClient().getRequestAttributes().put("charset", "UTF-8");
650 this.getClient().setMethod(null);
651 final Representation response = this.getClient().put(result);
652 return getText(response);
653 } catch (ResourceException ex) {
654 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
655 getResponseEntity(), ex);
656 } finally {
657 this.getClient().release();
658 }
659 }
660
661
662
663
664
665
666
667
668
669
670 public synchronized Resource parseMetadata(final Representation entity) throws
671 ValidationException {
672
673 try {
674 final JaxbRepresentation<Resource> resourceEntity = new JaxbRepresentation<>(entity,
675 Resource.class);
676 final MyValidationEventHandler validationHandler = new MyValidationEventHandler(this.
677 getClient().getLogger());
678 resourceEntity.setValidationEventHandler(validationHandler);
679 final Resource resource = resourceEntity.getObject();
680 if (validationHandler.isValid()) {
681 return resource;
682 } else {
683 throw new ValidationException(validationHandler.getErrorMsg());
684 }
685 } catch (IOException | JAXBException ex) {
686 throw new ValidationException("Cannot read the metadata", ex);
687 }
688 }
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709 public Resource deleteMetadataDoiAsObject(final String doiName) throws ClientMdsException {
710 final Representation rep = this.deleteMetadata(doiName);
711 return parseDataciteResource(rep);
712 }
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733 public Representation deleteMetadata(final String doiName) throws ClientMdsException {
734 final Reference url = createReferenceWithDOI(METADATA_RESOURCE, doiName);
735 this.getLog().debug("DELETE {}", url.toString());
736 this.getClient().setReference(url);
737 try {
738 return this.getClient().delete();
739 } catch (ResourceException ex) {
740 this.getClient().release();
741 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
742 getResponseEntity(), ex);
743 }
744 }
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763 public String getMedia(final String doiName) throws ClientMdsException {
764 final String result;
765 final Reference url = createReferenceWithDOI(MEDIA_RESOURCE, doiName);
766 this.getLog().debug("GET {}", url.toString());
767 this.getClient().setReference(url);
768 try {
769 final Representation response = this.getClient().get();
770 result = this.getText(response);
771 return result;
772 } catch (ResourceException ex) {
773 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
774 getResponseEntity(), ex);
775 } finally {
776 this.getClient().release();
777 }
778 }
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801 public String createMedia(final String doiName,
802 final Form form) throws ClientMdsException {
803 final String result;
804 final Reference url = createReferenceWithDOI(MEDIA_RESOURCE, doiName);
805 this.getLog().debug("POST {}", url.toString());
806 this.getClient().setReference(url);
807 final Representation entity = createEntity(form);
808 try {
809 final Representation response = this.getClient().post(entity, MediaType.TEXT_PLAIN);
810 result = this.getText(response);
811 return result;
812 } catch (ResourceException ex) {
813 throw new ClientMdsException(ex.getStatus(), ex.getMessage(), this.getClient().
814 getResponseEntity(), ex);
815 } finally {
816 this.getClient().release();
817 }
818 }
819
820
821
822
823
824
825
826
827 private Representation createEntity(final Form mediaForm) {
828 final Iterator<Parameter> iter = mediaForm.iterator();
829 StringBuilder entity = new StringBuilder();
830 while (iter.hasNext()) {
831 final Parameter param = iter.next();
832 final String mimeType = param.getName();
833 final String url = param.getValue();
834 entity = entity.append(mimeType).append("=").append(url).append("\n");
835 }
836 return new StringRepresentation(
837 entity.toString(),
838 MediaType.TEXT_PLAIN,
839 Language.ENGLISH,
840 CharacterSet.UTF_8
841 );
842 }
843
844
845
846
847 @Requirement(reqId = Requirement.DOI_ARCHI_020, reqName = Requirement.DOI_ARCHI_020_NAME)
848 private static class MyValidationEventHandler implements ValidationEventHandler {
849
850
851
852
853 private final java.util.logging.Logger logger;
854
855
856
857
858 private boolean hasError = false;
859
860
861
862
863 private String errorMsg = null;
864
865
866
867
868
869
870 MyValidationEventHandler(final java.util.logging.Logger logger) {
871 this.logger = logger;
872 }
873
874
875
876
877
878
879
880 @Override
881 public boolean handleEvent(final ValidationEvent event) {
882 final StringBuilder stringBuilder = new StringBuilder("\nEVENT");
883 stringBuilder.append("SEVERITY: ").append(event.getSeverity()).append("\n");
884 stringBuilder.append("MESSAGE: ").append(event.getMessage()).append("\n");
885 stringBuilder.append("LINKED EXCEPTION: ").append(event.getLinkedException()).append(
886 "\n");
887 stringBuilder.append("LOCATOR\n");
888 stringBuilder.append(" LINE NUMBER: ").append(event.getLocator().getLineNumber()).
889 append("\n");
890 stringBuilder.append(" COLUMN NUMBER: ").
891 append(event.getLocator().getColumnNumber()).append("\n");
892 stringBuilder.append(" OFFSET: ").append(event.getLocator().getOffset()).
893 append("\n");
894 stringBuilder.append(" OBJECT: ").append(event.getLocator().getObject()).
895 append("\n");
896 stringBuilder.append(" NODE: ").append(event.getLocator().getNode()).append("\n");
897 stringBuilder.append(" URL ").append(event.getLocator().getURL()).append("\n");
898 this.errorMsg = stringBuilder.toString();
899 this.logger.info(this.errorMsg);
900 this.hasError = true;
901 return true;
902 }
903
904
905
906
907
908
909
910
911 public boolean isValid() {
912 return !this.isNotValid();
913
914 }
915
916
917
918
919
920
921
922
923 public boolean isNotValid() {
924 return this.hasError;
925 }
926
927
928
929
930
931
932 public String getErrorMsg() {
933 return this.errorMsg;
934 }
935 }
936
937
938
939
940 public enum DATACITE_API_RESPONSE {
941
942
943
944
945 SUCCESS(Status.SUCCESS_OK, "Operation successful"),
946
947
948
949 SUCESS_CREATED(Status.SUCCESS_CREATED, "Operation successful"),
950
951
952
953 SUCCESS_NO_CONTENT(Status.SUCCESS_NO_CONTENT,
954 " the DOI is known to DataCite Metadata Store (MDS), but no metadata have been registered"),
955
956
957
958
959 BAD_REQUEST(Status.CLIENT_ERROR_BAD_REQUEST,
960 "invalid XML, wrong prefix or request body must be exactly two lines: DOI and URL; wrong domain, wrong prefix"),
961
962
963
964
965 UNAUTHORIZED(Status.CLIENT_ERROR_UNAUTHORIZED, "no login"),
966
967
968
969
970 FORBIDDEN(Status.CLIENT_ERROR_FORBIDDEN,
971 "login problem, wrong prefix, permission problem or dataset belongs to another party"),
972
973
974
975 DOI_NOT_FOUND(Status.CLIENT_ERROR_NOT_FOUND, "DOI does not exist in our database"),
976
977
978
979 DOI_INACTIVE(Status.CLIENT_ERROR_GONE,
980 "the requested dataset was marked inactive (using DELETE method)"),
981
982
983
984
985 PROCESS_ERROR(Status.CLIENT_ERROR_PRECONDITION_FAILED, "metadata must be uploaded first"),
986
987
988
989 INTERNAL_SERVER_ERROR(Status.SERVER_ERROR_INTERNAL, "Internal server error");
990
991
992
993
994 private final Status status;
995
996
997
998
999 private final String message;
1000
1001
1002
1003
1004
1005
1006
1007 DATACITE_API_RESPONSE(final Status status, final String message) {
1008 this.status = status;
1009 this.message = message;
1010 }
1011
1012
1013
1014
1015
1016
1017 public Status getStatus() {
1018 return this.status;
1019 }
1020
1021
1022
1023
1024
1025
1026 public String getShortMessage() {
1027 return this.message;
1028 }
1029
1030
1031
1032
1033
1034
1035
1036 public static String getMessageFromStatus(final Status statusToFind) {
1037 String result = "";
1038 final int codeToFind = statusToFind.getCode();
1039 final DATACITE_API_RESPONSE[] values = DATACITE_API_RESPONSE.values();
1040 for (int i = 0; i <= values.length; i++) {
1041 final DATACITE_API_RESPONSE value = values[i];
1042 final int codeValue = value.getStatus().getCode();
1043 if (codeValue == codeToFind) {
1044 result = value.message;
1045 break;
1046 }
1047 }
1048 return result;
1049 }
1050 }
1051
1052
1053
1054
1055 public enum Context {
1056
1057
1058
1059
1060 DEV(false, true, DATA_CITE_MOCK_URL, Level.ALL),
1061
1062
1063
1064 POST_DEV(false, true, DATA_CITE_TEST_URL, Level.ALL),
1065
1066
1067
1068 PRE_PROD(false, true, DATA_CITE_URL, Level.FINE),
1069
1070
1071
1072 PROD(false, false, DATA_CITE_URL, Level.INFO);
1073
1074
1075
1076
1077
1078
1079
1080
1081 private final boolean isTestMode;
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091 private final boolean isDoiPrefix;
1092
1093
1094
1095
1096 private Level levelLog;
1097
1098
1099
1100
1101 private String dataCiteUrl;
1102
1103 Context(final boolean isTestMode,
1104 final boolean isDoiPrefix,
1105 final String dataciteUrl,
1106 final Level levelLog) {
1107 this.isTestMode = isTestMode;
1108 this.isDoiPrefix = isDoiPrefix;
1109 this.dataCiteUrl = dataciteUrl;
1110 this.levelLog = levelLog;
1111 }
1112
1113
1114
1115
1116
1117
1118 public boolean hasDoiTestPrefix() {
1119 return this.isDoiPrefix;
1120 }
1121
1122
1123
1124
1125
1126
1127 public boolean hasTestMode() {
1128 return this.isTestMode;
1129 }
1130
1131
1132
1133
1134
1135
1136 public Level getLevelLog() {
1137 return this.levelLog;
1138 }
1139
1140
1141
1142
1143
1144
1145 public String getDataCiteUrl() {
1146 return this.dataCiteUrl;
1147 }
1148
1149
1150
1151
1152
1153
1154 private void setDataCiteURl(final String dataCiteUrl) {
1155 this.dataCiteUrl = dataCiteUrl;
1156 }
1157
1158
1159
1160
1161
1162
1163 private void setLevelLog(final Level levelLog) {
1164 this.levelLog = levelLog;
1165 }
1166
1167
1168
1169
1170
1171
1172
1173 public static void setLevelLog(final Context context,
1174 final Level levelLog) {
1175 context.setLevelLog(levelLog);
1176 }
1177
1178
1179
1180
1181
1182
1183
1184 public static void setDataCiteUrl(final Context context,
1185 final String dataCiteUrl) {
1186 context.setDataCiteURl(dataCiteUrl);
1187 }
1188
1189 }
1190
1191 }