diff --git a/klass-api/pom.xml b/klass-api/pom.xml index 7bae142a..65a6624d 100644 --- a/klass-api/pom.xml +++ b/klass-api/pom.xml @@ -10,7 +10,7 @@ no.ssb.klass klass-root - 2.0.18 + 2.1.0 diff --git a/klass-api/src/main/asciidoc/api-guide.adoc b/klass-api/src/main/asciidoc/api-guide.adoc index e87ef8fd..1059553b 100644 --- a/klass-api/src/main/asciidoc/api-guide.adoc +++ b/klass-api/src/main/asciidoc/api-guide.adoc @@ -216,7 +216,7 @@ Used to search codes from a classification variant. A range is specified when re and the response will for each code indicate its valid range (validFrom/validTo). The format and character set used, can be specified in the http header (character set is only available for csv). For more information about the range see <<_range, range>>. + -To get a snapshot of codes valid at a specified date, use <<_variantaAt, variantAt>>. +To get a snapshot of codes valid at a specified date, use <<_variantat, variantAt>>. See also <<_variants_by_id, variants>> ===== Supported formats @@ -510,15 +510,28 @@ include::{snippets}/select-codes-example-csv/http-response.adoc[] === csvSeparator -`csvSeparator` is used to specify separator to be used for csv format. Default is `,` +`csvSeparator` is used to specify a separator symbol to be used for csv format. Default is `,` ===== Example request for csvSeparator=; include::{snippets}/csv-separator-example/curl-request.adoc[] ===== Example response include::{snippets}/csv-separator-example/http-response.adoc[] + +=== csvFields +`csvFields` is an optional parameter that allows a user to filter columns and specify columns order in the Csv output. +It is applicable for `/codes`, `/codesAt`, `/variantAt`, `/correspondsAt` services. + +The field names are case-sensitive. Misspelled field names will cause a 400 BAD REQUEST error. + +===== Example request for csvFields=name,code with /codesAt; +include::{snippets}/csv-fields-codes-at-example/curl-request.adoc[] +===== Example response +include::{snippets}/csv-fields-codes-at-example/http-response.adoc[] + + === language -`language` is used to specify which language data shall be presented in. Default if none is selected is nb (Norwegian Bokmål). +`language` is used to specify a language data shall be presented in. If none selected, the default is "nb" (Norwegian Bokmål). Supported languages |=== diff --git a/klass-api/src/main/java/no/ssb/klass/api/controllers/ClassificationController.java b/klass-api/src/main/java/no/ssb/klass/api/controllers/ClassificationController.java index 42bc3cbf..29f6573e 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/controllers/ClassificationController.java +++ b/klass-api/src/main/java/no/ssb/klass/api/controllers/ClassificationController.java @@ -6,11 +6,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDate; +import java.util.Arrays; import java.util.Date; import java.util.List; - import javax.transaction.Transactional; - +import no.ssb.klass.api.controllers.validators.CsvFieldsValidator; import no.ssb.klass.core.util.AlphaNumericalComparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,15 +90,18 @@ public class ClassificationController { private final SubscriberService subscriberService; private final SearchService searchService; private final StatisticsService statisticsService; + private final CsvFieldsValidator csvFieldsValidator; @Autowired public ClassificationController(ClassificationService classificationService, SubscriberService subscriberService, - SearchService searchService, StatisticsService statisticsService) { + SearchService searchService, StatisticsService statisticsService, + CsvFieldsValidator csvFieldsValidator) { this.classificationService = classificationService; this.subscriberService = subscriberService; this.searchService = searchService; this.statisticsService = statisticsService; + this.csvFieldsValidator = csvFieldsValidator; } @ExceptionHandler @@ -121,6 +124,12 @@ public String argumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchExc return exception.getMessage(); } + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public String argumentTypeMismatchExceptionHandler(IllegalArgumentException exception) { + return exception.getMessage(); + } + @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String serverErrorExceptionHandler(Exception exception) { @@ -200,7 +209,7 @@ public KlassPagedResources search( // @formatter:off @RequestParam(value = "query") String query, @RequestParam(value = "ssbSection", required = false) String ssbSection, - @RequestParam(value = "includeCodelists", defaultValue = "false") boolean includeCodelists, + @RequestParam(value = "includeCodelists", defaultValue = "false") boolean includeCodelists, Pageable pageable, PagedResourcesAssembler assembler) { // @formatter:on Link self = new Link(getCurrentRequest(), Link.REL_SELF); @@ -258,6 +267,7 @@ public CodeList codes(@PathVariable Long id, @RequestParam(value = "from") @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate from, @RequestParam(value = "to", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate to, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "selectLevel", required = false) String selectLevel, @RequestParam(value = "selectCodes", required = false) String selectCodes, @RequestParam(value = "presentationNamePattern", required = false) String presentationNamePattern, @@ -265,8 +275,15 @@ public CodeList codes(@PathVariable Long id, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture ) { // @formatter:on - return codesInternal(id, new DateRangeHolder(from, to), csvSeparator, selectLevel, selectCodes, + CodeList codeList = codesInternal(id, new DateRangeHolder(from, to), csvSeparator, selectLevel, selectCodes, presentationNamePattern, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCodeItem( csvFieldsList); + codeList.setCsvFields(csvFieldsList); + } + return codeList; } @RequestMapping(value = "/classifications/{id}/codesAt", method = RequestMethod.GET) @@ -274,6 +291,7 @@ public CodeList codesAt(@PathVariable Long id, // @formatter:off @RequestParam(value = "date") @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate date, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "selectLevel", required = false) String selectLevel, @RequestParam(value = "selectCodes", required = false) String selectCodes, @RequestParam(value = "presentationNamePattern", required = false) String presentationNamePattern, @@ -281,8 +299,16 @@ public CodeList codesAt(@PathVariable Long id, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture ) { // @formatter:on - return codesInternal(id, new DateRangeHolder(date), csvSeparator, selectLevel, selectCodes, + + CodeList codeList = codesInternal(id, new DateRangeHolder(date), csvSeparator, selectLevel, selectCodes, presentationNamePattern, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCodeItem(csvFieldsList); + codeList.setCsvFields(csvFieldsList); + } + return codeList; } private CodeList codesInternal(Long id, DateRangeHolder dateRangeHolder, String csvSeparator, String selectLevel, @@ -304,6 +330,7 @@ public CodeChangeList changes(@PathVariable Long id, @RequestParam(value = "from") @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate from, @RequestParam(value = "to", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate to, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "language", defaultValue = "nb") Language language, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture) { // @formatter:on @@ -315,6 +342,13 @@ public CodeChangeList changes(@PathVariable Long id, for (CorrespondenceTable changeTable : changeTables) { codeChanges = codeChanges.merge(codeChanges.convert(changeTable, language)); } + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsChangeItemSchema(csvFieldsList); + codeChanges.setCsvFields(csvFieldsList); + } + return codeChanges; } @@ -325,6 +359,7 @@ public CodeList variant(@PathVariable Long id, @RequestParam(value = "from") @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate from, @RequestParam(value = "to", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate to, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "level", required = false) String selectLevel, @RequestParam(value = "selectCodes", required = false) String selectCodes, @RequestParam(value = "presentationNamePattern", required = false) String presentationNamePattern, @@ -332,8 +367,17 @@ public CodeList variant(@PathVariable Long id, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture ) { // @formatter:on - return variantInternal(id, variantName, new DateRangeHolder(from, to), csvSeparator, selectLevel, selectCodes, + CodeList codeList = variantInternal(id, variantName, new DateRangeHolder(from, to), csvSeparator, selectLevel, selectCodes, presentationNamePattern, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCodeItem(csvFieldsList); + codeList.setCsvFields(csvFieldsList); + } + + return codeList; + } @RequestMapping(value = "/classifications/{id}/variantAt", method = RequestMethod.GET) @@ -342,6 +386,7 @@ public CodeList variantAt(@PathVariable Long id, @RequestParam(value = "variantName") String variantName, @RequestParam(value = "date", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate date, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "level", required = false) String selectLevel, @RequestParam(value = "selectCodes", required = false) String selectCodes, @RequestParam(value = "presentationNamePattern", required = false) String presentationNamePattern, @@ -349,10 +394,20 @@ public CodeList variantAt(@PathVariable Long id, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture ) { // @formatter:on - return variantInternal(id, variantName, new DateRangeHolder(date), csvSeparator, selectLevel, selectCodes, + CodeList codeList = variantInternal(id, variantName, new DateRangeHolder(date), csvSeparator, selectLevel, selectCodes, presentationNamePattern, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCodeItem(csvFieldsList); + codeList.setCsvFields(csvFieldsList); + } + + return codeList; } + + private CodeList variantInternal(Long id, String variantName, DateRangeHolder dateRangeHolder, String csvSeparator, String selectLevel, String selectCodes, String presentationNamePattern, Language language, Boolean includeFuture) { List codes = classificationService.findVariantClassificationCodes(id, variantName, language, @@ -369,10 +424,19 @@ public CorrespondenceItemList corresponds(@PathVariable Long id, @RequestParam(value = "from") @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate from, @RequestParam(value = "to", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate to, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "language", defaultValue = "nb") Language language, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture) { // @formatter:on - return correspondsInternal(id, targetClassificationId, new DateRangeHolder(from, to), csvSeparator, language, includeFuture); + CorrespondenceItemList correspondenceList = correspondsInternal(id, targetClassificationId, new DateRangeHolder(from, to), csvSeparator, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCorrespondenceItem(csvFieldsList); + correspondenceList.setCsvFields(csvFieldsList); + } + + return correspondenceList; } @RequestMapping(value = "/classifications/{id}/correspondsAt", method = RequestMethod.GET) @@ -381,10 +445,19 @@ public CorrespondenceItemList correspondsAt(@PathVariable Long id, @RequestParam(value = "targetClassificationId") Long targetClassificationId, @RequestParam(value = "date", required = false) @DateTimeFormat(pattern = RestConstants.DATE_FORMAT) LocalDate date, @RequestParam(value = "csvSeparator", defaultValue = ",") String csvSeparator, + @RequestParam(value = "csvFields", defaultValue = "") String csvFields, @RequestParam(value = "language", defaultValue = "nb") Language language, @RequestParam(value = "includeFuture", defaultValue = "false") Boolean includeFuture) { // @formatter:on - return correspondsInternal(id, targetClassificationId, new DateRangeHolder(date), csvSeparator, language, includeFuture); + CorrespondenceItemList correspondenceList = correspondsInternal(id, targetClassificationId, new DateRangeHolder(date), csvSeparator, language, includeFuture); + + if (!csvFields.isEmpty()) { + List csvFieldsList = getCsvFieldsList(csvFields); + csvFieldsValidator.validateFieldsCorrespondenceItem(csvFieldsList); + correspondenceList.setCsvFields(csvFieldsList); + } + + return correspondenceList; } private CorrespondenceItemList correspondsInternal(Long id, Long targetClassificationId, @@ -479,6 +552,11 @@ private String extractSsbSection(String ssbSection) { return Strings.isNullOrEmpty(ssbSection) ? null : ssbSection; } + private List getCsvFieldsList(String csvFields) { + return Arrays.asList(csvFields.split(",")); + } + + @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Language.class, new CaseInsensitiveConverter<>(Language.class)); diff --git a/klass-api/src/main/java/no/ssb/klass/api/controllers/validators/CsvFieldsValidator.java b/klass-api/src/main/java/no/ssb/klass/api/controllers/validators/CsvFieldsValidator.java new file mode 100644 index 00000000..d6b99f23 --- /dev/null +++ b/klass-api/src/main/java/no/ssb/klass/api/controllers/validators/CsvFieldsValidator.java @@ -0,0 +1,40 @@ +package no.ssb.klass.api.controllers.validators; + +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import no.ssb.klass.api.dto.CodeChangeItem; +import no.ssb.klass.api.dto.CodeItem; +import no.ssb.klass.api.dto.CorrespondenceItem; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@Component +public class CsvFieldsValidator { + + private final CsvSchema correspondenceItemSchema = new CsvMapper().schemaFor(CorrespondenceItem.RangedCorrespondenceItem.class); + private final CsvSchema codeItemSchema = new CsvMapper().schemaFor(CodeItem.class); + private final CsvSchema codeChangeItemSchema = new CsvMapper().schemaFor(CodeChangeItem.class); + + public void validateFieldsCorrespondenceItem(List csvFields) { + List fieldsNotFound = csvFields.stream().filter(s -> correspondenceItemSchema.column(s) == null).collect(toList()); + if(!fieldsNotFound.isEmpty()) { + throw new IllegalArgumentException("CorrespondenceList does not contain the following field(s): " + String.join(",", fieldsNotFound)); + } + } + public void validateFieldsCodeItem(List csvFields) { + List fieldsNotFound = csvFields.stream().filter(s -> codeItemSchema.column(s) == null).collect(toList()); + if(!fieldsNotFound.isEmpty()) { + throw new IllegalArgumentException("CorrespondenceList does not contain the following field(s): " + String.join(",", fieldsNotFound)); + } + } + public void validateFieldsChangeItemSchema(List csvFields) { + List fieldsNotFound = csvFields.stream().filter(s -> codeChangeItemSchema.column(s) == null).collect(toList()); + if(!fieldsNotFound.isEmpty()) { + throw new IllegalArgumentException("CorrespondenceList does not contain the following field(s): " + String.join(",", fieldsNotFound)); + } + } + +} diff --git a/klass-api/src/main/java/no/ssb/klass/api/converters/AbstractCsvConverter.java b/klass-api/src/main/java/no/ssb/klass/api/converters/AbstractCsvConverter.java index ff9568a8..599f0b26 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/converters/AbstractCsvConverter.java +++ b/klass-api/src/main/java/no/ssb/klass/api/converters/AbstractCsvConverter.java @@ -3,7 +3,9 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.LocalDate; +import java.util.List; +import com.fasterxml.jackson.core.JsonGenerator; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -29,11 +31,24 @@ protected ObjectWriter createWriter(Class clazz) { } protected ObjectWriter createWriter(Class clazz, char csvSeparator) { + return createWriter(clazz, csvSeparator, null); + } + protected ObjectWriter createWriter(Class clazz, char csvSeparator, List csvFields) { CsvMapper mapper = new CsvMapper(); - CsvSchema schema = mapper.schemaFor(clazz).withHeader().withColumnSeparator(csvSeparator); + CsvSchema schema; + if (csvFields == null) { + schema = mapper + .schemaFor(clazz) + .withHeader() + .withColumnSeparator(csvSeparator); + } else { + mapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN); + schema = CsvSchema.builder() + .addColumns(csvFields, CsvSchema.ColumnType.STRING) + .build().withHeader().withColumnSeparator(csvSeparator); + } - return mapper.writer(schema).withFeatures( - CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS, + return mapper.writer(schema).withFeatures(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS, CsvGenerator.Feature.OMIT_MISSING_TAIL_COLUMNS); } diff --git a/klass-api/src/main/java/no/ssb/klass/api/converters/CodeChangeListCsvConverter.java b/klass-api/src/main/java/no/ssb/klass/api/converters/CodeChangeListCsvConverter.java index ebd81600..317d0baa 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/converters/CodeChangeListCsvConverter.java +++ b/klass-api/src/main/java/no/ssb/klass/api/converters/CodeChangeListCsvConverter.java @@ -20,7 +20,7 @@ protected boolean supports(Class clazz) { @Override protected void writeInternal(CodeChangeList codeChangeList, HttpOutputMessage outputMessage) throws IOException { Charset charset = selectCharsetAndUpdateOutput(outputMessage); - ObjectWriter writer = createWriter(CodeChangeItem.class, codeChangeList.getCsvSeparator()); + ObjectWriter writer = createWriter(CodeChangeItem.class, codeChangeList.getCsvSeparator(), codeChangeList.getCsvFields()); writer.writeValue(new OutputStreamWriter(outputMessage.getBody(), charset), codeChangeList.getCodeChanges()); } } \ No newline at end of file diff --git a/klass-api/src/main/java/no/ssb/klass/api/converters/CodeListCsvConverter.java b/klass-api/src/main/java/no/ssb/klass/api/converters/CodeListCsvConverter.java index 4f7afaf9..94f58355 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/converters/CodeListCsvConverter.java +++ b/klass-api/src/main/java/no/ssb/klass/api/converters/CodeListCsvConverter.java @@ -19,7 +19,7 @@ protected boolean supports(Class clazz) { @Override protected void writeInternal(CodeList codeList, HttpOutputMessage outputMessage) throws IOException { Charset charset = selectCharsetAndUpdateOutput(outputMessage); - ObjectWriter writer = createWriter(codeList.codeItemsJavaType(), codeList.getCsvSeparator()); + ObjectWriter writer = createWriter(codeList.codeItemsJavaType(), codeList.getCsvSeparator(), codeList.getCsvFields()); writer.writeValue(new OutputStreamWriter(outputMessage.getBody(), charset), codeList.getCodes()); } } \ No newline at end of file diff --git a/klass-api/src/main/java/no/ssb/klass/api/converters/CorrespondenceItemListCsvConverter.java b/klass-api/src/main/java/no/ssb/klass/api/converters/CorrespondenceItemListCsvConverter.java index 5720e026..d7b43cdf 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/converters/CorrespondenceItemListCsvConverter.java +++ b/klass-api/src/main/java/no/ssb/klass/api/converters/CorrespondenceItemListCsvConverter.java @@ -21,7 +21,7 @@ protected void writeInternal(CorrespondenceItemList correspondenceItemList, Http throws IOException { Charset charset = selectCharsetAndUpdateOutput(outputMessage); ObjectWriter writer = createWriter(correspondenceItemList.classificationItemsJavaType(), correspondenceItemList - .getCsvSeparator()); + .getCsvSeparator(), correspondenceItemList.getCsvFields()); writer.writeValue(new OutputStreamWriter(outputMessage.getBody(), charset), correspondenceItemList .getCorrespondenceItems()); } diff --git a/klass-api/src/main/java/no/ssb/klass/api/dto/CodeChangeList.java b/klass-api/src/main/java/no/ssb/klass/api/dto/CodeChangeList.java index 3aa22ede..ababa13a 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/dto/CodeChangeList.java +++ b/klass-api/src/main/java/no/ssb/klass/api/dto/CodeChangeList.java @@ -18,6 +18,7 @@ public class CodeChangeList { private final char csvSeparator; private final List codeChanges; + private List csvFields; public CodeChangeList(String csvSeparator) { if (csvSeparator.toCharArray().length != 1) { @@ -43,6 +44,16 @@ public char getCsvSeparator() { return csvSeparator; } + @JsonIgnore + public List getCsvFields() { + return csvFields; + } + + @JsonIgnore + public void setCsvFields(List csvFields) { + this.csvFields = csvFields; + } + public CodeChangeList convert(CorrespondenceTable correspondenceTable, Language language) { LocalDate changeOccurred = correspondenceTable.getOccured(); boolean isTargetOldest = correspondenceTable.getTarget().getDateRange().getFrom().isBefore(correspondenceTable diff --git a/klass-api/src/main/java/no/ssb/klass/api/dto/CodeList.java b/klass-api/src/main/java/no/ssb/klass/api/dto/CodeList.java index 1f63b348..e0435cd0 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/dto/CodeList.java +++ b/klass-api/src/main/java/no/ssb/klass/api/dto/CodeList.java @@ -33,6 +33,7 @@ public class CodeList { private final List codeItems; private final Boolean includeFuture; private final DateRange dateRange; + private List csvFields; public CodeList(String csvSeparator, boolean displayWithValidRange, DateRange dateRange, Boolean includeFuture) { if (csvSeparator.toCharArray().length != 1) { @@ -67,6 +68,16 @@ public char getCsvSeparator() { return csvSeparator; } + @JsonIgnore + public List getCsvFields() { + return csvFields; + } + + @JsonIgnore + public void setCsvFields(List csvFields) { + this.csvFields = csvFields; + } + public CodeList merge(CodeList other) { List combined = new ArrayList<>(codeItems); combined.addAll(other.codeItems); diff --git a/klass-api/src/main/java/no/ssb/klass/api/dto/CorrespondenceItemList.java b/klass-api/src/main/java/no/ssb/klass/api/dto/CorrespondenceItemList.java index cec07ea2..c4defbd7 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/dto/CorrespondenceItemList.java +++ b/klass-api/src/main/java/no/ssb/klass/api/dto/CorrespondenceItemList.java @@ -17,6 +17,7 @@ public class CorrespondenceItemList { private final boolean displayWithValidRange; private final List correspondenceItems; private final Boolean includeFuture; + private List csvFields; public CorrespondenceItemList(String csvSeparator, boolean displayWithValidRange, boolean includeFuture) { if (csvSeparator.toCharArray().length != 1) { @@ -50,6 +51,17 @@ public char getCsvSeparator() { return csvSeparator; } + @JsonIgnore + public List getCsvFields() { + return csvFields; + } + + @JsonIgnore + public void setCsvFields(List csvFields) { + this.csvFields = csvFields; + } + + public CorrespondenceItemList convert(List correspondences) { return newList(correspondences.stream() .map(RangedCorrespondenceItem::new) diff --git a/klass-api/src/main/java/no/ssb/klass/api/dto/hal/ClassificationResource.java b/klass-api/src/main/java/no/ssb/klass/api/dto/hal/ClassificationResource.java index 1e1a5697..e115f955 100644 --- a/klass-api/src/main/java/no/ssb/klass/api/dto/hal/ClassificationResource.java +++ b/klass-api/src/main/java/no/ssb/klass/api/dto/hal/ClassificationResource.java @@ -95,14 +95,14 @@ public List getVersions() { private Link createVariantAtRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).variantAt(id, "name", - LocalDate.now(), ",", "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); + LocalDate.now(), ",", null, "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, "variantName", date(), "csvSeparator", "level", "selectCodes", "presentationNamePattern"), "variantAt"); } private Link createVariantRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).variant(id, "name", - LocalDate.now(), LocalDate.now(), ",", "level", "selectCodes", "presentationNamePattern", Language + LocalDate.now(), LocalDate.now(), ",",null, "level", "selectCodes", "presentationNamePattern", Language .getDefault(), null)); return new Link(createUriTemplate(linkBuilder, "variantName", from(), to(), "csvSeparator", "level", "selectCodes", "presentationNamePattern"), "variant"); @@ -110,34 +110,34 @@ private Link createVariantRelation(Long id) { private Link createCodesAtRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).codesAt(id, LocalDate.now(), - ",", "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); + ",",null, "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, date(), "csvSeparator", "level", "selectCodes", "presentationNamePattern"), "codesAt"); } private Link createCodesRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).codes(id, LocalDate.now(), - LocalDate.now(), ",", "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); + LocalDate.now(), ",",null, "level", "selectCodes", "presentationNamePattern", Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, from(), to(), "csvSeparator", "level", "selectCodes", "presentationNamePattern"), "codes"); } private Link createChangesRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).changes(id, LocalDate.now(), - LocalDate.now(), ",", Language.getDefault(), null)); + LocalDate.now(), ",",null, Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, from(), to(), "csvSeparator"), "changes"); } private Link createCorrespondsAtRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).correspondsAt(id, 2L, - LocalDate.now(), ",", Language.getDefault(), null)); + LocalDate.now(), ",",null, Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, "targetClassificationId", date(), "csvSeparator"), "correspondsAt"); } private Link createCorrespondsRelation(Long id) { ControllerLinkBuilder linkBuilder = linkTo(ControllerLinkBuilder.methodOn(ClassificationController.class).corresponds(id, 2L, - LocalDate.now(), LocalDate.now(), ",", Language.getDefault(), null)); + LocalDate.now(), LocalDate.now(), ",",null, Language.getDefault(), null)); return new Link(createUriTemplate(linkBuilder, "targetClassificationId", from(), to(), "csvSeparator"), "corresponds"); } diff --git a/klass-api/src/test/java/no/ssb/klass/api/ApiDocumentation.java b/klass-api/src/test/java/no/ssb/klass/api/ApiDocumentation.java index 0a3912e5..454d4fc2 100644 --- a/klass-api/src/test/java/no/ssb/klass/api/ApiDocumentation.java +++ b/klass-api/src/test/java/no/ssb/klass/api/ApiDocumentation.java @@ -442,7 +442,7 @@ public void codesOptionalParametersExample() throws Exception { List codes = createKommuneInndelingCodes(dateRange); when(classificationServiceMock.findClassificationCodes(any(), any(), any(), any())).thenReturn(codes); // @formatter:off - this.mockMvc.perform(getWithContextUri("/classifications/" + CLASS_ID_KOMMUNEINNDELING + "/codes?from=2014-01-01&to=2015-01-01&csvSeparator=;" + this.mockMvc.perform(getWithContextUri("/classifications/" + CLASS_ID_KOMMUNEINNDELING + "/codes?from=2014-01-01&to=2015-01-01&csvSeparator=;&csvFields=name,code" + "&selectLevel=1&selectCodes=01*&presentationNamePattern={code}-{name}&language=nb&includeFuture=true") .accept("text/csv")) .andDo(this.documentationHandler @@ -451,6 +451,7 @@ public void codesOptionalParametersExample() throws Exception { fromParameterDescription(), toParameterDescription(), csvSeparatorParameterDescription(), + csvFieldsParameterDescription(), selectCodesParameterDescription(), selectLevelParameterDescription(), presentationNamePatternParameterDescription(), @@ -494,12 +495,13 @@ public void codesAtOptionalParametersExample() throws Exception { when(classificationServiceMock.findClassificationCodes(any(), any(), any(), any())).thenReturn(codes); // @formatter:off this.mockMvc.perform(getWithContextUri("/classifications/" + CLASS_ID_KOMMUNEINNDELING - + "/codesAt?date=2015-01-01&csvSeparator=;&selectLevel=1&selectCodes=01*" + + "/codesAt?date=2015-01-01&csvSeparator=;&csvFields=name,code&selectLevel=1&selectCodes=01*" + "&presentationNamePattern={code}-{name}&language=nb&includeFuture=true").accept("text/csv")) .andDo(this.documentationHandler.document( requestParameters( dateParameterDescription(), csvSeparatorParameterDescription(), + csvFieldsParameterDescription(), selectCodesParameterDescription(), selectLevelParameterDescription(), presentationNamePatternParameterDescription(), @@ -549,7 +551,7 @@ public void variantOptionalParametersExample() throws Exception { this.mockMvc.perform( getWithContextUri("/classifications/" + CLASS_ID_GREENHOUSE_GASES + "/variant?variantName=Klimagasser" - + "&from=2014-01-01&to=2015-01-01&csvSeparator=;&selectLevel=1&selectCodes=01*" + + "&from=2014-01-01&to=2015-01-01&csvSeparator=;&csvFields=name,code&selectLevel=1&selectCodes=01*" + "&presentationNamePattern={code}-{name}&language=nb&includeFuture=true") .accept("text/csv")) .andDo(this.documentationHandler = document("{method-name}", @@ -561,6 +563,7 @@ public void variantOptionalParametersExample() throws Exception { fromParameterDescription(), toParameterDescription(), csvSeparatorParameterDescription(), + csvFieldsParameterDescription(), selectCodesParameterDescription(), selectLevelParameterDescription(), presentationNamePatternParameterDescription(), @@ -610,7 +613,7 @@ public void variantAtOptionalParametersExample() throws Exception { this.mockMvc.perform( getWithContextUri("/classifications/" + CLASS_ID_GREENHOUSE_GASES + "/variantAt?variantName=Klimagasser" - + "&date=2015-01-01&csvSeparator=;&selectLevel=1&selectCodes=01*" + + "&date=2015-01-01&csvSeparator=;&csvFields=name,code&selectLevel=1&selectCodes=01*" + "&presentationNamePattern={code}-{name}&language=nb&includeFuture=true").accept("text/csv")) .andDo(this.documentationHandler = document("{method-name}", preprocessRequest(prettyPrint()), @@ -620,6 +623,7 @@ public void variantAtOptionalParametersExample() throws Exception { variantNameParameterDescription(), dateParameterDescription(), csvSeparatorParameterDescription(), + csvFieldsParameterDescription(), selectCodesParameterDescription(), selectLevelParameterDescription(), presentationNamePatternParameterDescription(), @@ -715,7 +719,7 @@ public void correspondsOptionalParametersExample() throws Exception { this.mockMvc.perform( getWithContext("/classifications/" + CLASS_ID_KOMMUNEINNDELING + "/corresponds?targetClassificationId=" + CLASS_ID_BYDELSINNDELING - + "&from=2014-01-01&to=2016-01-01&csvSeparator=;" + + "&from=2014-01-01&to=2016-01-01&csvSeparator=;&csvFields=sourceCode,sourceName" + "&language=nb&includeFuture=true").accept("text/csv")) .andDo(this.documentationHandler = document("{method-name}", preprocessRequest(prettyPrint()), @@ -725,6 +729,7 @@ public void correspondsOptionalParametersExample() throws Exception { targetClassificationIdParameterDescription(), fromParameterDescription(), toParameterDescription(), + csvFieldsParameterDescription(), csvSeparatorParameterDescription(), languageDescription(), includeFutureDescription("")))) @@ -757,7 +762,7 @@ public void correspondsAtOptionalParametersExample() throws Exception { // @formatter:off this.mockMvc.perform( getWithContext("/classifications/" + CLASS_ID_KOMMUNEINNDELING - + "/correspondsAt?targetClassificationId=" + CLASS_ID_BYDELSINNDELING + "&date=2016-01-01&csvSeparator=;" + + "/correspondsAt?targetClassificationId=" + CLASS_ID_BYDELSINNDELING + "&date=2016-01-01&csvSeparator=;&csvFields=sourceCode,sourceName" + "&language=nb&includeFuture=true").accept("text/csv")) .andDo(this.documentationHandler = document("{method-name}", preprocessRequest(prettyPrint()), @@ -767,6 +772,7 @@ public void correspondsAtOptionalParametersExample() throws Exception { targetClassificationIdParameterDescription(), dateParameterDescription(), csvSeparatorParameterDescription(), + csvFieldsParameterDescription(), languageDescription(), includeFutureDescription("")))) .andExpect(status().isOk()); @@ -884,7 +890,38 @@ public void csvSeparatorExample() throws Exception { .andExpect(status().isOk()); // @formatter:on } - + @Test + public void csvFieldsCodesExample() throws Exception { + DateRange dateRange = DateRange.create("2014-01-01", "2015-01-01"); + when(classificationServiceMock.findClassificationCodes(any(), any(), any(), any())).thenReturn( + createKommuneInndelingCodes(dateRange)); + // @formatter:off + this.mockMvc.perform( + getWithContext("/classifications/" + CLASS_ID_KOMMUNEINNDELING + + "/codes?from=2014-01-01&to=2015-01-01&csvSeparator=;&csvFields=name,code") + .accept("text/csv")) + .andDo(this.documentationHandler = document("{method-name}", + preprocessRequest(prettyPrint()), + preprocessResponse(/*prettyPrint()*/))) + .andExpect(status().isOk()); + // @formatter:on + } + @Test + public void csvFieldsCodesAtExample() throws Exception { + DateRange dateRange = DateRange.create("2014-01-01", "2015-01-01"); + when(classificationServiceMock.findClassificationCodes(any(), any(), any(), any())).thenReturn( + createKommuneInndelingCodes(dateRange)); + // @formatter:off + this.mockMvc.perform( + getWithContext("/classifications/" + CLASS_ID_KOMMUNEINNDELING + + "/codesAt?date=2014-01-01&csvSeparator=;&csvFields=name,code") + .accept("text/csv")) + .andDo(this.documentationHandler = document("{method-name}", + preprocessRequest(prettyPrint()), + preprocessResponse(/*prettyPrint()*/))) + .andExpect(status().isOk()); + // @formatter:on + } @Test public void selectLevelExample() throws Exception { DateRange dateRange = DateRange.create("2014-01-01", "2015-01-01"); @@ -1107,6 +1144,10 @@ private ParameterDescriptor csvSeparatorParameterDescription() { return parameterWithName("csvSeparator").description( "[Optional] specifies separator to be used for csv format. For details see <<_csvseparator, csvSeparator>>"); } + private ParameterDescriptor csvFieldsParameterDescription() { + return parameterWithName("csvFields").description( + "[Optional] specifies which fields should be included in the csv output. For details see <<_csvfields, csvfields>>"); + } private ParameterDescriptor selectCodesParameterDescription() { return parameterWithName("selectCodes").description( diff --git a/klass-api/src/test/java/no/ssb/klass/api/config/MockConfig.java b/klass-api/src/test/java/no/ssb/klass/api/config/MockConfig.java index e4aa900d..3520f71e 100644 --- a/klass-api/src/test/java/no/ssb/klass/api/config/MockConfig.java +++ b/klass-api/src/test/java/no/ssb/klass/api/config/MockConfig.java @@ -1,5 +1,6 @@ package no.ssb.klass.api.config; +import no.ssb.klass.api.controllers.validators.CsvFieldsValidator; import no.ssb.klass.core.service.*; import no.ssb.klass.api.controllers.ClassificationController; import org.mockito.Mockito; @@ -19,10 +20,13 @@ public class MockConfig { @Autowired private StatisticsService statisticsService; - @Autowired + @Autowired private UserService userService; + @Autowired + private CsvFieldsValidator csvFieldsValidator; + @Bean public ClassificationService classificationService() { return Mockito.mock(ClassificationService.class); @@ -48,8 +52,14 @@ public UserService userService() { return Mockito.mock(UserService.class); } + @Bean + private CsvFieldsValidator csvFieldsValidator() { + return new CsvFieldsValidator(); + } + + @Bean public ClassificationController classificationController() { - return new ClassificationController(classificationService, subscriberService, searchService, statisticsService); + return new ClassificationController(classificationService, subscriberService, searchService, statisticsService, csvFieldsValidator); } } diff --git a/klass-forvaltning/pom.xml b/klass-forvaltning/pom.xml index e41716b3..e7a385d7 100644 --- a/klass-forvaltning/pom.xml +++ b/klass-forvaltning/pom.xml @@ -14,7 +14,7 @@ no.ssb.klass klass-root - 2.0.18 + 2.1.0 ../ diff --git a/klass-shared/pom.xml b/klass-shared/pom.xml index 4780f534..2a811c76 100644 --- a/klass-shared/pom.xml +++ b/klass-shared/pom.xml @@ -11,7 +11,7 @@ no.ssb.klass klass-root - 2.0.18 + 2.1.0 diff --git a/klass-solr/pom.xml b/klass-solr/pom.xml index bbeaa266..c816cbc2 100644 --- a/klass-solr/pom.xml +++ b/klass-solr/pom.xml @@ -13,7 +13,7 @@ no.ssb.klass klass-root - 2.0.18 + 2.1.0 diff --git a/pom.xml b/pom.xml index 716069e7..792c87e3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ no.ssb.klass klass-root - 2.0.18 + 2.1.0 pom