diff --git a/pom.xml b/pom.xml index 4e5a4dbd..cc5225e7 100644 --- a/pom.xml +++ b/pom.xml @@ -69,8 +69,6 @@ ${jersey.version} - - org.apache.logging.log4j diff --git a/src/main/java/fr/insee/rmes/api/classifications/ClassificationApi.java b/src/main/java/fr/insee/rmes/api/classifications/ClassificationApi.java index 3f1ec649..49c2c32e 100644 --- a/src/main/java/fr/insee/rmes/api/classifications/ClassificationApi.java +++ b/src/main/java/fr/insee/rmes/api/classifications/ClassificationApi.java @@ -16,6 +16,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; import org.apache.commons.lang3.StringUtils; import fr.insee.rmes.api.AbstractMetadataApi; @@ -37,8 +39,7 @@ @Tag(name = "nomenclatures", description = "Nomenclatures API") public class ClassificationApi extends AbstractMetadataApi { - - @Hidden + @GET @Path("/{code}/postes") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) diff --git a/src/main/java/fr/insee/rmes/api/geo/AbstractGeoApi.java b/src/main/java/fr/insee/rmes/api/geo/AbstractGeoApi.java index c5208efb..d5b8678a 100644 --- a/src/main/java/fr/insee/rmes/api/geo/AbstractGeoApi.java +++ b/src/main/java/fr/insee/rmes/api/geo/AbstractGeoApi.java @@ -17,6 +17,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; public abstract class AbstractGeoApi extends AbstractMetadataApi { @@ -135,7 +138,11 @@ else if (StringUtils.equalsAnyIgnoreCase(header, MediaType.APPLICATION_JSON)) { } protected Response generateBadRequestResponse() { - return Response.status(Status.BAD_REQUEST).entity("").build(); + return generateBadRequestResponse(""); + } + + protected Response generateBadRequestResponse(String erreurMessage) { + return Response.status(Status.BAD_REQUEST).entity(erreurMessage).build(); } // Method to find a list of projections diff --git a/src/main/java/fr/insee/rmes/api/geo/ConstGeoApi.java b/src/main/java/fr/insee/rmes/api/geo/ConstGeoApi.java index 0695764b..87344f97 100644 --- a/src/main/java/fr/insee/rmes/api/geo/ConstGeoApi.java +++ b/src/main/java/fr/insee/rmes/api/geo/ConstGeoApi.java @@ -10,6 +10,7 @@ public class ConstGeoApi { public static final String PATH_CIRCO_TER = PATH_SEPARATOR + "circonscriptionTerritoriale"; public static final String PATH_CANTON = PATH_SEPARATOR + "canton"; + public static final String PATH_IRIS=PATH_SEPARATOR + "iris"; public static final String PATH_COMMUNE = PATH_SEPARATOR + "commune"; public static final String PATH_CANTON_OU_VILLE = PATH_SEPARATOR + "cantonOuVille"; public static final String PATH_PAYS = PATH_SEPARATOR + "pays"; @@ -28,7 +29,7 @@ public class ConstGeoApi { public static final String PATH_LISTE_CANTON_OU_VILLE= PATH_SEPARATOR +"cantonsEtVilles"; public static final String PATH_INTERCO= PATH_SEPARATOR + "intercommunalite"; public static final String PATH_BASSINDEVIE= PATH_SEPARATOR + "bassinDeVie2022"; - + public static final String PATH_LISTE_IRIS = PATH_SEPARATOR + "iris"; public static final String PATH_LISTE_COMMUNE = PATH_SEPARATOR + "communes"; public static final String PATH_LISTE_PAYS = PATH_SEPARATOR + "pays"; public static final String PATH_LISTE_CANTON = PATH_SEPARATOR + "cantons"; @@ -59,6 +60,9 @@ public class ConstGeoApi { public static final String PATTERN_DISTRICT ="9[78][1-9]{3}"; public static final String PATTERN_INTERCO ="2[0-4][0-9]{7}"; public static final String PATTERN_CANTON = "(([0-9]{2})|(2[0-9AB])|(97[1-6]))([0-9]{2})"; + + public static final String PATTERN_IRIS="[0-9][0-9AB][0-9]{7}"; + public static final String PATTERN_IRIS_DESCRIPTION= "Code Insee de l’Iris (9 caractères)"; public static final String PATTERN_PAYS = "99[0-9]{3}"; public static final String PATTERN_REGION = "[0-9]{2}"; public static final String PATTERN_ZONE_EMPLOI = "[0-9]{4}"; diff --git a/src/main/java/fr/insee/rmes/api/geo/territoire/IrisApi.java b/src/main/java/fr/insee/rmes/api/geo/territoire/IrisApi.java new file mode 100644 index 00000000..948dbd8c --- /dev/null +++ b/src/main/java/fr/insee/rmes/api/geo/territoire/IrisApi.java @@ -0,0 +1,210 @@ +package fr.insee.rmes.api.geo.territoire; + +import fr.insee.rmes.api.geo.AbstractGeoApi; +import fr.insee.rmes.api.geo.ConstGeoApi; +import fr.insee.rmes.modeles.geo.territoire.CodeIris; +import fr.insee.rmes.modeles.geo.territoire.Iris; +import fr.insee.rmes.modeles.geo.territoire.PseudoIris; +import fr.insee.rmes.modeles.geo.territoire.Territoire; +import fr.insee.rmes.modeles.geo.territoires.Territoires; +import fr.insee.rmes.queries.geo.GeoQueries; +import fr.insee.rmes.utils.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import javax.ws.rs.*; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + + +@Path(ConstGeoApi.PATH_GEO) +@Tag(name = ConstGeoApi.TAG_NAME, description = ConstGeoApi.TAG_DESCRIPTION) +public class IrisApi extends AbstractGeoApi { + private static final String CODE_PATTERN = "/{code:}"; + private static final String LITTERAL_PARAMETER_TYPE_DESCRIPTION = "Filtre sur le type de territoire renvoyé."; + + private static final String LITTERAL_RESPONSE_DESCRIPTION = "Iris"; + private static final String LITTERAL_ID_OPERATION = "getcogiris"; + private static final String LITTERAL_CODE_EXAMPLE = "010040101"; + private static final String LITTERAL_PARAMETER_DATE_DESCRIPTION = + "Filtre pour renvoyer l'Iris active à la date donnée. Par défaut, c’est la date courante. (Format : 'AAAA-MM-JJ')"; + private static final String LITTERAL_OPERATION_SUMMARY = + "Informations sur un Iris identifié par son code (neuf chiffres pour la métropole ou 2A/2B plus 7 chiffres pour la Corse)"; + + + private final IrisUtils irisUtils; + + public IrisApi() { + // Constructeur par défaut + this.irisUtils = new IrisUtils(); + } + + protected IrisApi(SparqlUtils sparqlUtils, CSVUtils csvUtils, ResponseUtils responseUtils, IrisUtils irisUtils) { + super(sparqlUtils, csvUtils, responseUtils); + this.irisUtils = irisUtils; + } + + @Path(ConstGeoApi.PATH_IRIS + CODE_PATTERN) + @GET + @Produces({ + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + }) + @Operation(operationId = LITTERAL_ID_OPERATION, summary = LITTERAL_OPERATION_SUMMARY, responses = { + @ApiResponse( + content = @Content(schema = @Schema(implementation = Iris.class)), + description = LITTERAL_RESPONSE_DESCRIPTION) + + }) + public Response getByCode( + @Parameter( + description = ConstGeoApi.PATTERN_IRIS_DESCRIPTION, + required = true, + schema = @Schema( + pattern = ConstGeoApi.PATTERN_IRIS, + type = Constants.TYPE_STRING, example = LITTERAL_CODE_EXAMPLE)) @PathParam(Constants.CODE) String code, + @Parameter(hidden = true) @HeaderParam(HttpHeaders.ACCEPT) String header, + @Parameter( + description = LITTERAL_PARAMETER_DATE_DESCRIPTION, + schema = @Schema(type = Constants.TYPE_STRING, format = Constants.FORMAT_DATE)) @QueryParam( + value = Constants.PARAMETER_DATE) String date) { + + var codeIris = CodeIris.of(code); + if (codeIris.isInvalid()) { + return generateBadRequestResponse(ConstGeoApi.ERREUR_PATTERN); + } + if (!this.verifyParameterDateIsRightWithoutHistory(date)) { + return this.generateBadRequestResponse(); + } + + return getResponseForIrisOrPseudoIris(codeIris, header, date); + } + + private Response getResponseForIrisOrPseudoIris(CodeIris codeIris, String header, String date) { + if (irisUtils.hasIrisDescendant(codeIris.codeCommune())) { + return getResponseForIris(codeIris, header, date); + } else { + return getResponseForPseudoIris(codeIris, header, date); + } + + } + + private Response getResponseForPseudoIris(CodeIris codeIris, String header, String date) { + if (codeIris.isPseudoIrisCode()) { + return this.generateResponseATerritoireByCode( + sparqlUtils.executeSparqlQuery( + GeoQueries.getIrisByCodeAndDate(codeIris.code(), this.formatValidParameterDateIfIsNull(date))), + header, + new PseudoIris(codeIris.code())); + } else { + return Response.status(Response.Status.NOT_FOUND).entity("").build(); + } + } + + private Response getResponseForIris(CodeIris codeIris, String header, String date) { + if (codeIris.isPseudoIrisCode()) { + return Response.status(Response.Status.NOT_FOUND).entity("").build(); + } else { + Territoire territoire = new Iris(codeIris.code()); + return this.generateResponseATerritoireByCode( + sparqlUtils.executeSparqlQuery( + GeoQueries.getIrisByCodeAndDate(codeIris.code(), this.formatValidParameterDateIfIsNull(date))), + header, + territoire); + } + } + + + @Path(ConstGeoApi.PATH_LISTE_IRIS) + @GET + @Produces({ + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + }) + @Operation( + operationId = LITTERAL_ID_OPERATION + ConstGeoApi.ID_OPERATION_LISTE, + summary = "Informations sur toutes les iris actifs à la date donnée. Par défaut, c’est la date courante.", + responses = { + @ApiResponse( + content = @Content(schema = @Schema(type = ARRAY, implementation = Territoire.class)), + description = LITTERAL_RESPONSE_DESCRIPTION) + }) + public Response getListe( + @Parameter(hidden = true) @HeaderParam(HttpHeaders.ACCEPT) String header, + @Parameter( + description = "Filtre pour renvoyer les Iris ou faux-Iris à la date donnée. Par défaut, c’est la date courante. (Format : 'AAAA-MM-JJ')", + schema = @Schema(type = Constants.TYPE_STRING, format = Constants.FORMAT_DATE)) @QueryParam( + value = Constants.PARAMETER_DATE) String date, + @Parameter(description = "les Iris (et pseudo-iris) des collectivités d'outre-mer", + required = true, + schema = @Schema(type = Constants.TYPE_BOOLEAN, allowableValues = {"true", "false"}, example = "false", defaultValue = "false")) + @QueryParam( + value = Constants.PARAMETER_STRING) Boolean com + ) { + + if (!this.verifyParameterDateIsRightWithHistory(date)) { + return this.generateBadRequestResponse(); + } else { + return this + .generateResponseListOfTerritoire( + sparqlUtils + .executeSparqlQuery(GeoQueries.getListIris(this.formatValidParameterDateIfIsNull(date), this.formatValidParameterBooleanIfIsNull(com))), + header, + Territoires.class, + Territoire.class); + } + } + + + @Path(ConstGeoApi.PATH_IRIS + CODE_PATTERN + ConstGeoApi.PATH_ASCENDANT) + @GET + @Produces({ + MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + }) + @Operation( + operationId = LITTERAL_ID_OPERATION + ConstGeoApi.ID_OPERATION_ASCENDANTS, + summary = "Informations concernant les territoires qui contiennent l'Iris", + responses = { + @ApiResponse( + content = @Content(schema = @Schema(type = ARRAY, implementation = Territoire.class)), + description = LITTERAL_RESPONSE_DESCRIPTION) + }) + public Response getAscendants( + @Parameter( + description = ConstGeoApi.PATTERN_IRIS_DESCRIPTION, + required = true, + schema = @Schema( + pattern = ConstGeoApi.PATTERN_IRIS, + type = Constants.TYPE_STRING, example = LITTERAL_CODE_EXAMPLE)) @PathParam(Constants.CODE) String code, + @Parameter(hidden = true) @HeaderParam(HttpHeaders.ACCEPT) String header, + @Parameter( + description = "Filtre pour renvoyer les territoires contenant l'iris actif à la date donnée. Par défaut, c’est la date courante. (Format : 'AAAA-MM-JJ')", + schema = @Schema(type = Constants.TYPE_STRING, format = Constants.FORMAT_DATE)) @QueryParam( + value = Constants.PARAMETER_DATE) String date, + @Parameter( + description = LITTERAL_PARAMETER_TYPE_DESCRIPTION, + schema = @Schema(type = Constants.TYPE_STRING)) @QueryParam( + value = Constants.PARAMETER_TYPE) String typeTerritoire) { + + if (!this.verifyParametersTypeAndDateAreValid(typeTerritoire, date)) { + return this.generateBadRequestResponse(); + } else { + return this + .generateResponseListOfTerritoire( + sparqlUtils + .executeSparqlQuery( + GeoQueries + .getAscendantsIris( + code, + this.formatValidParameterDateIfIsNull(date), + this.formatValidParametertypeTerritoireIfIsNull(typeTerritoire))), + header, + Territoires.class, + Territoire.class); + } + } + +} diff --git a/src/main/java/fr/insee/rmes/modeles/geo/EnumTypeGeographie.java b/src/main/java/fr/insee/rmes/modeles/geo/EnumTypeGeographie.java index cd440e5d..44eb99c9 100644 --- a/src/main/java/fr/insee/rmes/modeles/geo/EnumTypeGeographie.java +++ b/src/main/java/fr/insee/rmes/modeles/geo/EnumTypeGeographie.java @@ -1,12 +1,12 @@ package fr.insee.rmes.modeles.geo; -import java.util.Optional; -import java.util.stream.Stream; - import fr.insee.rmes.modeles.geo.territoire.*; import fr.insee.rmes.modeles.geo.territoires.*; import fr.insee.rmes.utils.Constants; +import java.util.Optional; +import java.util.stream.Stream; + public enum EnumTypeGeographie { COMMUNE("Commune", Commune.class,Communes.class, Constants.NONE), @@ -25,8 +25,8 @@ public enum EnumTypeGeographie { DISTRICT("District",District.class,Districts.class,Constants.NONE), CIRCONSCRIPTION_TERRITORIALE("CirconscriptionTerritoriale",CirconscriptionTerritoriale.class,CirconscriptionsTerritoriales.class,Constants.NONE), INTERCOMMUNALITE("Intercommunalite",Intercommunalite.class,Intercommunalites.class,Constants.NONE), - BASSINDEVIE("BassinDeVie2022",BassinDeVie2022.class,BassinsDeVie2022.class,Constants.NONE); - + BASSINDEVIE("BassinDeVie2022",BassinDeVie2022.class,BassinsDeVie2022.class,Constants.NONE), + IRIS("Iris", Iris.class,Iriss.class,Constants.NONE); private String typeObjetGeo; private Class classNameOfGeoType; diff --git a/src/main/java/fr/insee/rmes/modeles/geo/IntituleSansArticle.java b/src/main/java/fr/insee/rmes/modeles/geo/IntituleSansArticle.java index 7e735a6a..fa36ade8 100644 --- a/src/main/java/fr/insee/rmes/modeles/geo/IntituleSansArticle.java +++ b/src/main/java/fr/insee/rmes/modeles/geo/IntituleSansArticle.java @@ -1,26 +1,23 @@ package fr.insee.rmes.modeles.geo; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlValue; - import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; - import io.swagger.v3.oas.annotations.media.Schema; +import javax.xml.bind.annotation.*; + @Schema(description="Intitulé sans son article et article associé") @XmlAccessorType(XmlAccessType.PROPERTY) @JsonPropertyOrder({"intituleSansArticle", "typeArticle"}) -@XmlRootElement(name="IntituleSansArticle") +@XmlRootElement(name = "IntituleSansArticle") +@JacksonXmlRootElement(localName = "IntituleSansArticle") public class IntituleSansArticle { - //@Schema(example = "Aigle") - @XmlValue + @Schema(example = "Aigle") + //@XmlValue private String label = null; @Schema(example = "5") @@ -28,8 +25,8 @@ public class IntituleSansArticle { @JacksonXmlText - // @JsonProperty("intituleSansArticle") - // @JacksonXmlProperty(localName="intituleSansArticle") + @JsonProperty("intituleSansArticle") + @JacksonXmlProperty(localName="IntituleSansArticle") @Schema(example = "Aigle") @XmlValue public String getIntituleSansArticle() { diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/CodeIris.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/CodeIris.java new file mode 100644 index 00000000..cb7e2edb --- /dev/null +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/CodeIris.java @@ -0,0 +1,41 @@ +package fr.insee.rmes.modeles.geo.territoire; + +import java.util.regex.Pattern; + +import static fr.insee.rmes.api.geo.ConstGeoApi.PATTERN_IRIS; + +public final class CodeIris { + + private static final Pattern IRIS_CODE_PATTERN = Pattern.compile(PATTERN_IRIS); + public static final String PSEUDO_IRIS_SUFFIX = "0000"; + private final String code; + private final boolean isInvalid; + + private CodeIris(String code) { + this.code = code; + isInvalid = ! IRIS_CODE_PATTERN.matcher(code).matches(); + } + + public static CodeIris of(String code) { + return new CodeIris(code); + } + + public boolean isInvalid() { + return this.isInvalid; + } + + public String code() { + if (isInvalid){ + throw new IllegalStateException("Cannot get an invalid code (which does not match "+PATTERN_IRIS+") from CodeIris"); + } + return code; + } + + public boolean isPseudoIrisCode() { + return code().endsWith(PSEUDO_IRIS_SUFFIX); + } + + public String codeCommune() { + return code().substring(0, 5); + } +} diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/Commune.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Commune.java index 23c1b96f..fbacf409 100644 --- a/src/main/java/fr/insee/rmes/modeles/geo/territoire/Commune.java +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Commune.java @@ -1,19 +1,18 @@ package fr.insee.rmes.modeles.geo.territoire; -import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; - import fr.insee.rmes.modeles.geo.EnumTypeGeographie; import fr.insee.rmes.modeles.geo.IntituleSansArticle; import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + @XmlRootElement(name = "Commune") @JacksonXmlRootElement(localName = "Commune") @XmlAccessorType(XmlAccessType.FIELD) @@ -30,11 +29,13 @@ public Commune() { this.inclusion=null; } + public Commune(String code) { this(); this.code = code; } + @Override @JacksonXmlProperty(isAttribute = true) @Schema(example = "55323") @@ -89,4 +90,5 @@ public Inclusion getInclusion() { } + } diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/Iris.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Iris.java new file mode 100644 index 00000000..a5c21c85 --- /dev/null +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Iris.java @@ -0,0 +1,107 @@ +package fr.insee.rmes.modeles.geo.territoire; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import fr.insee.rmes.modeles.geo.EnumTypeGeographie; +import fr.insee.rmes.modeles.geo.IntituleSansArticle; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "Iris") +@JacksonXmlRootElement(localName = "Iris") +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(description = "Objet représentant un Iris") +public class Iris extends Territoire { + + private final TypeDIris typeDIris; + + public Iris( + TypeDIris typeDIris, + String code, + String uri, + String intitule, + String dateCreation, + String dateSuppression, + IntituleSansArticle intituleSansArticle) { + super(code, uri, intitule, EnumTypeGeographie.IRIS.getTypeObjetGeo(), dateCreation, dateSuppression, intituleSansArticle); + this.typeDIris=typeDIris; + } + + public Iris() { + this(null); + } + + public Iris(String code) { + this(null, code, null, null, null, null, new IntituleSansArticle()); + } + + + @Override + @JacksonXmlProperty(localName = "Type") + @Schema(example = "http://rdf.insee.fr/def/geo#Iris") + public String getType() { + return type; + } + + @Override + @JacksonXmlProperty(isAttribute = true) + @Schema(example = "010040101") + public String getCode() { + return code; + } + + @Override + @JacksonXmlProperty(isAttribute = true) + @Schema(description = "URI de l'Iris ", example = "http://id.insee.fr/geo/iris/b8c772de-9551-4f13-81c5-eca5bb0f2f7d") + public String getUri() { + return uri; + } + + @Override + @JacksonXmlProperty(localName = "Intitule") + @JsonProperty(value = "intitule") + @Schema(description = "Nom de l'Iris (avec article)", example = "Les Pérouses-Triangle d'Activités") + public String getIntitule() { + return intitule; + } + + @Override + @JacksonXmlProperty(localName = "IntituleSansArticle") + @JsonProperty(value = "intituleSansArticle") + @Schema(description = "Nom de l'Iris sans article", example = "Pérouses-Triangle d'Activité") + public IntituleSansArticle getIntituleSansArticle() { + return intituleSansArticle; + } + + + @JacksonXmlProperty(localName = "TypeDIris") + @NotNull + @Schema(description = "Type d'Iris (H , A ou D)", example = "H") + public TypeDIris getTypeDIris() { + return typeDIris; + } + + + @Override + @JacksonXmlProperty(localName = "DateCreation") + @Schema( + description = "Date de création de l'Iris si il n’existait pas au premier COG du 1er janvier 2016", + example = "2016-01-01") + public String getDateCreation() { + return dateCreation; + } + + @Override + @JacksonXmlProperty(localName = "DateSuppression") + @Schema(description = "Date de suppression de l'Iris si il a été supprimé. ", example = "2019-01-01") + public String getDateSuppression() { + return dateSuppression; + } + + // getters and setters +} diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/PseudoIris.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/PseudoIris.java new file mode 100644 index 00000000..033ea75b --- /dev/null +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/PseudoIris.java @@ -0,0 +1,104 @@ +package fr.insee.rmes.modeles.geo.territoire; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import fr.insee.rmes.modeles.geo.EnumTypeGeographie; +import fr.insee.rmes.modeles.geo.IntituleSansArticle; + +import javax.xml.bind.annotation.*; + +@XmlRootElement(name = "Commune") +@JacksonXmlRootElement(localName = "Commune") +@XmlAccessorType(XmlAccessType.FIELD) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class PseudoIris extends Territoire{ + + public PseudoIris( + String code, + String uri, + String intitule, + String dateCreation, + String dateSuppression, + IntituleSansArticle intituleSansArticle) { + super(code, uri, intitule, EnumTypeGeographie.COMMUNE.getTypeObjetGeo(), dateCreation, dateSuppression, intituleSansArticle); + } + + public PseudoIris() { + this(null); + } + + public PseudoIris(String code) { + this(code, null, null, null, null, new IntituleSansArticle()); + } + + + + public String getCode() { + return code; + } + + public void setCode(String code) { + if (this.code==null) { + this.code=code; + } + } + + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + + public String getIntitule() { + return intitule; + } + + public void setIntitule(String intitule) { + this.intitule = intitule; + } + + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public String getDateCreation() { + return dateCreation; + } + + public void setDateCreation(String dateCreation) { + this.dateCreation = dateCreation; + } + + + public String getDateSuppression() { + return dateSuppression; + } + + public void setDateSuppression(String dateSuppression) { + this.dateSuppression = dateSuppression; + } + + @Override + public String toString() { + return "PseudoIris{" + + "code='" + code + '\'' + + ", uri='" + uri + '\'' + + ", intitule='" + intitule + '\'' + + ", type='" + type + '\'' + + ", dateCreation='" + dateCreation + '\'' + + ", dateSuppression='" + dateSuppression + '\'' + + ", intituleSansArticle=" + intituleSansArticle + + '}'; + } +} diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/Territoire.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Territoire.java index 7e19c4e6..4d3be9f6 100644 --- a/src/main/java/fr/insee/rmes/modeles/geo/territoire/Territoire.java +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/Territoire.java @@ -1,29 +1,28 @@ package fr.insee.rmes.modeles.geo.territoire; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; - import fr.insee.rmes.modeles.geo.IntituleSansArticle; import io.swagger.v3.oas.annotations.media.Schema; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + @XmlRootElement(name="Territoire") public abstract class Territoire { @XmlAttribute protected String code = null; - + @XmlAttribute protected String uri = null; - + @XmlElement(name="Intitule") protected String intitule = null; - + @XmlElement(name="Type") protected String type; @@ -42,11 +41,10 @@ public abstract class Territoire { @JsonInclude(Include.NON_EMPTY) @XmlElement(name="ChefLieu") protected String chefLieu = null; - + @JsonInclude(Include.NON_EMPTY) @XmlElement(name="CategorieJuridique") protected String categorieJuridique = null; - @JacksonXmlProperty(isAttribute = true) public String getCode() { @@ -76,13 +74,7 @@ public Territoire( IntituleSansArticle intituleSansArticle, String chefLieu) { - this.code = code; - this.uri = uri; - this.intitule = intitule; - this.type = type; - this.dateCreation = dateCreation; - this.dateSuppression = dateSuppression; - this.intituleSansArticle = intituleSansArticle; + this(code, uri, intitule, type, dateCreation, dateSuppression, null, intituleSansArticle); this.chefLieu = chefLieu; } @@ -95,15 +87,9 @@ public Territoire( String dateSuppression, IntituleSansArticle intituleSansArticle) { - this.code = code; - this.uri = uri; - this.intitule = intitule; - this.type = type; - this.dateCreation = dateCreation; - this.dateSuppression = dateSuppression; - this.intituleSansArticle = intituleSansArticle; + this(code, uri, intitule, type, dateCreation, dateSuppression, null, intituleSansArticle); } - + public Territoire( String code, String uri, @@ -124,9 +110,9 @@ public Territoire( this.intituleSansArticle = intituleSansArticle; this.categorieJuridique= categorieJuridique; } - + public Territoire() { - this.intituleSansArticle = new IntituleSansArticle(); + this(null); } public Territoire(String code) { @@ -153,6 +139,7 @@ public void setType(String type) { this.type = type; } + @JacksonXmlProperty(localName = "DateCreation") public String getDateCreation() { return dateCreation; @@ -170,7 +157,6 @@ public String getDateSuppression() { public void setDateSuppression(String dateSuppression) { this.dateSuppression = dateSuppression; } - @JacksonXmlProperty(localName = "IntituleSansArticle") public IntituleSansArticle getIntituleSansArticle() { return intituleSansArticle; @@ -197,7 +183,7 @@ public void setChefLieu(String chefLieu) { this.chefLieu = chefLieu; } - + @JacksonXmlProperty(localName = "CategorieJuridique") public String getCategorieJuridique() { return categorieJuridique; @@ -206,5 +192,6 @@ public String getCategorieJuridique() { public void setCategorieJuridique(String categorieJuridique) { this.categorieJuridique = categorieJuridique; } - + + } diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoire/TypeDIris.java b/src/main/java/fr/insee/rmes/modeles/geo/territoire/TypeDIris.java new file mode 100644 index 00000000..166552fd --- /dev/null +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoire/TypeDIris.java @@ -0,0 +1,19 @@ +package fr.insee.rmes.modeles.geo.territoire; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; + +import javax.xml.bind.annotation.XmlValue; + +@JsonFormat(shape = JsonFormat.Shape.STRING) +public enum TypeDIris { + H, A, D; + + @JsonValue + @XmlValue + @Override + public String toString(){ + return name(); + } + +} diff --git a/src/main/java/fr/insee/rmes/modeles/geo/territoires/Iriss.java b/src/main/java/fr/insee/rmes/modeles/geo/territoires/Iriss.java new file mode 100644 index 00000000..4285bd0a --- /dev/null +++ b/src/main/java/fr/insee/rmes/modeles/geo/territoires/Iriss.java @@ -0,0 +1,32 @@ +package fr.insee.rmes.modeles.geo.territoires; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import fr.insee.rmes.modeles.geo.territoire.Iris; +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.util.List; +@JacksonXmlRootElement(localName = "Iriss") +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(name = "Iriss", description = "Tableau représentant les Iriss") +public class Iriss extends Territoires{ + private List iriss = null; + + public Iriss() {} + + public Iriss(List iriss) { + this.iriss = iriss; + } + + @JacksonXmlProperty(isAttribute = true, localName = "Iris") + @JacksonXmlElementWrapper(useWrapping = false) + public List getIriss() { + return iriss; + } + + public void setIriss(List cantons) { + this.iriss = iriss; + } +} diff --git a/src/main/java/fr/insee/rmes/queries/geo/GeoQueries.java b/src/main/java/fr/insee/rmes/queries/geo/GeoQueries.java index 0fa08a19..232b1f8f 100644 --- a/src/main/java/fr/insee/rmes/queries/geo/GeoQueries.java +++ b/src/main/java/fr/insee/rmes/queries/geo/GeoQueries.java @@ -1,13 +1,12 @@ package fr.insee.rmes.queries.geo; -import java.util.HashMap; -import java.util.Map; - -import fr.insee.rmes.config.Configuration; import fr.insee.rmes.modeles.geo.EnumTypeGeographie; import fr.insee.rmes.queries.Queries; import fr.insee.rmes.utils.Constants; +import java.util.HashMap; +import java.util.Map; + public class GeoQueries extends Queries { private static final String CODE = "code"; @@ -71,6 +70,13 @@ public static String getCantonByCodeAndDate(String code, String date) { return getTerritoire(code, date, EnumTypeGeographie.CANTON); } + public static String getIrisByCodeAndDate(String code,String date) { + if (code.endsWith("0000")) { + return getTerritoire(code.substring(0,5), date, EnumTypeGeographie.COMMUNE);} + else { + return getTerritoire(code, date, EnumTypeGeographie.IRIS); + } + } public static String getCantonOuVilleByCodeAndDate(String code, String date) { return getTerritoire(code, date, EnumTypeGeographie.CANTON_OU_VILLE); } @@ -119,6 +125,9 @@ public static String getListCantonsOuVilles(String date) { return getTerritoire(Constants.NONE, date, EnumTypeGeographie.CANTON_OU_VILLE); } + public static String getListIris(String date,Boolean com) { + return getTerritoireListIris(Constants.NONE, date,EnumTypeGeographie.IRIS,com); + } public static String getListCantons(String date){ return getTerritoire(Constants.NONE,date,EnumTypeGeographie.CANTON); } @@ -162,7 +171,9 @@ public static String getAscendantsDepartement(String code, String date, String t public static String getAscendantsCanton(String code, String date, String type) { return getAscendantOrDescendantsQuery(code, date, type, EnumTypeGeographie.CANTON,Constants.ABSENT,Constants.NONE, true); } - + public static String getAscendantsIris(String code, String date, String type) { + return getAscendantOrDescendantsQuery(code, date, type, EnumTypeGeographie.IRIS,Constants.ABSENT,Constants.NONE, true); + } public static String getAscendantsDistrict(String code, String date, String type) { return getAscendantOrDescendantsQuery(code, date, type, EnumTypeGeographie.DISTRICT,Constants.ABSENT,Constants.NONE,true); } @@ -415,7 +426,11 @@ private static String getAscendantOrDescendantsQuery( params.put(FILTRE, filtreNom); params.put(COM,com); params.put(ASCENDANT, String.valueOf(ascendant)); - return buildRequest(QUERIES_FOLDER, "getAscendantsOrDescendantsByCodeTypeDate.ftlh", params); + if(code.matches("^.{5}0000$") && typeOrigine.getTypeObjetGeo().equals("Iris")) { + return buildRequest(QUERIES_FOLDER, "getAscendantsIrisByCodeTypeDate.ftlh", params); + } else { + return buildRequest(QUERIES_FOLDER, "getAscendantsOrDescendantsByCodeTypeDate.ftlh", params); + } } @@ -431,17 +446,39 @@ private static String getPreviousOrNextQuery( } private static String getTerritoire(String code, String date, EnumTypeGeographie typeGeo) { - return getTerritoireFiltre(code,date,Constants.ABSENT,typeGeo,true); + if (typeGeo == EnumTypeGeographie.IRIS && code !="none") { + return getIris(code, date,typeGeo); + } else{ + return getTerritoireFiltre(code, date, Constants.ABSENT, typeGeo, true); + } } + private static String getTerritoireListIris(String code, String date, EnumTypeGeographie typeGeo,Boolean com) { + if (com.booleanValue()==true) { + return getIrisList(code, date,typeGeo,true); + } else { + return getIrisList(code, date, typeGeo, false); + } + } private static Map buildCodeAndDateParams(String code, String date) { Map params = new HashMap<>(); params.put(CODE, code); params.put(DATE, date); return params; } - - + + private static String getIris(String code, String date, EnumTypeGeographie typeGeo) { + Map params = buildCodeAndDateParams(code, date); + params.put("territoire", typeGeo.getTypeObjetGeo()); + return buildRequest(QUERIES_FOLDER, "getIrisByCodeDate.ftlh", params); + + } + + private static String getIrisList(String code, String date, EnumTypeGeographie typeGeo,boolean com){ + Map params = buildCodeAndDateAndFilterParams(code, date,Constants.NONE, com); + params.put("territoire", typeGeo.getTypeObjetGeo()); + return buildRequest(QUERIES_FOLDER, "getIrisList.ftlh",params); + } private static String getTerritoireFiltre(String code, String date, String filtreNom, EnumTypeGeographie typeGeo,boolean com) { Map params = buildCodeAndDateAndFilterParams(code, date, filtreNom,com); params.put("territoire", typeGeo.getTypeObjetGeo()); @@ -459,22 +496,18 @@ private static Map buildCodeAndDateAndFilterParams(String code, } public static String getCountry(String code) { - return "SELECT ?uri ?intitule ?intituleEntier \n" - + "FROM \n" - + "WHERE { \n" - + "?uri rdf:type igeo:Etat . \n" - + "?uri igeo:codeINSEE '" - + code - + "' . \n" - + "?uri igeo:nom ?intitule . \n" - + "?uri igeo:nomEntier ?intituleEntier . \n" - // Ensure that is not the dbpedia URI - + "FILTER (REGEX(STR(?uri), '" - + Configuration.getBaseHost() - + "')) \n" - + "FILTER (lang(?intitule) = 'fr') \n" - + "FILTER (lang(?intituleEntier) = 'fr') \n" - + "}"; + return String.format( + "SELECT ?uri ?intitule ?intituleEntier ?code \n" + + "FROM \n" + + "WHERE { \n" + + " ?urilong a igeo:Pays ; \n" + + " igeo:codeINSEE \"%s\" ; \n" + + " igeo:nom ?intitule; \n" + + " owl:sameAs ?uri; \n" + + " igeo:nomLong ?intituleEntier . \n" + + " BIND(\"%s\" AS ?code)\n" + + " FILTER (REGEX(STR(?uri), \"http://id.insee.fr\")) \n" + + "}", code, code); } diff --git a/src/main/java/fr/insee/rmes/utils/CSVUtils.java b/src/main/java/fr/insee/rmes/utils/CSVUtils.java index f3a3e09d..c6e2343b 100644 --- a/src/main/java/fr/insee/rmes/utils/CSVUtils.java +++ b/src/main/java/fr/insee/rmes/utils/CSVUtils.java @@ -3,6 +3,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import fr.insee.rmes.modeles.geo.EnumTypeGeographie; @@ -52,6 +55,9 @@ private List csvToMultiPOJO(String csv, Class childClass) throws IOExc CsvMapper mapper = CsvMapper.csvBuilder().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); + // Activer la coercition pour les chaînes vides vers null pour toutes les propriétés d'énumération + mapper.coercionConfigFor(LogicalType.Enum) + .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull); CsvSchema schema = CsvSchema.emptySchema().withHeader(); MappingIterator> it = mapper.readerFor(Map.class).with(schema).readValues(csv); while (it.hasNext()) { diff --git a/src/main/java/fr/insee/rmes/utils/IrisUtils.java b/src/main/java/fr/insee/rmes/utils/IrisUtils.java new file mode 100644 index 00000000..8c1f5f15 --- /dev/null +++ b/src/main/java/fr/insee/rmes/utils/IrisUtils.java @@ -0,0 +1,31 @@ +package fr.insee.rmes.utils; + +public class IrisUtils { + + + private final SparqlUtils sparqlUtils=new SparqlUtils(); + + public boolean hasIrisDescendant(String codeCommune) { + String sparqlQuery = String.format( + "SELECT DISTINCT ?type\n" + + "FROM \n" + + "WHERE {\n" + + " {\n" + + " ?origine a igeo:Commune ;\n" + + " igeo:codeINSEE \"%s\" .\n" + + " }\n" + + " UNION\n" + + " {\n" + + " ?origine a igeo:ArrondissementMunicipal ;\n" + + " igeo:codeINSEE \"%s\" .\n" + + " }\n" + + " ?uri igeo:subdivisionDirecteDe+ ?origine .\n" + + " ?uri a ?type .\n" + + "}\n" + + "ORDER BY ?type", codeCommune, codeCommune); + + var type=this.sparqlUtils.executeSparqlQuery(sparqlQuery); + return type.endsWith("#Iris\r\n"); + } + +} diff --git a/src/main/resources/request/geographie/getAscendantsIrisByCodeTypeDate.ftlh b/src/main/resources/request/geographie/getAscendantsIrisByCodeTypeDate.ftlh new file mode 100644 index 00000000..978f434f --- /dev/null +++ b/src/main/resources/request/geographie/getAscendantsIrisByCodeTypeDate.ftlh @@ -0,0 +1,49 @@ +SELECT ?uri ?type ?code ?typeArticle ?intitule ?intituleSansArticle ?cheflieu ?dateCreation ?dateSuppression +WHERE { + { + SELECT DISTINCT ?uri ?type ?code ?typeArticle ?intitule ?intituleSansArticle ?cheflieu ?dateCreation ?dateSuppression + WHERE { + { + ?enfant a igeo:Iris ; + igeo:codeINSEE '${code}'; + igeo:subdivisionDirecteDe+ ?ressource . + } + UNION + { + BIND(SUBSTR('${code}', 1, 5) AS ?codeCommune) + ?enfant a igeo:Commune ; + igeo:codeINSEE ?codeCommune . + OPTIONAL { + ?iris igeo:subdivisionDirecteDe+ ?enfant ; a igeo:Iris . + OPTIONAL {?iris ^igeo:creation/igeo:date ?dateDebutIris } + OPTIONAL {?iris ^igeo:suppression/igeo:date ?dateFinIris } + FILTER (!BOUND(?dateDebutIris) || ?dateDebutIris <= '${date}'^^xsd:date ) + FILTER (!BOUND(?dateFinIris) || ?dateFinIris > '${date}'^^xsd:date ) + } + FILTER (!BOUND(?iris)) + BIND(CONCAT(?codeCommune, '0000') AS ?codeIris) + FILTER(?codeIris = '${code}') + ?enfant igeo:subdivisionDirecteDe* ?ressource . + } + ?ressource a ?typeRDF; +<#if type != "none"> + a igeo:${type} ; + + igeo:codeINSEE ?code ; + igeo:codeArticle ?typeArticle ; + igeo:nom ?intitule ; + igeo:nomSansArticle ?intituleSansArticle . + BIND(STR(?typeRDF) AS ?type). + BIND(STR(?ressource) AS ?uri). + OPTIONAL {?enfant (^igeo:creation/igeo:date) ?dateCreationEnfant.} + OPTIONAL {?enfant (^igeo:suppression/igeo:date) ?dateSuppressionEnfant.} + OPTIONAL {?ressource (^igeo:creation/igeo:date) ?dateCreation.} + OPTIONAL {?ressource (^igeo:suppression/igeo:date) ?dateSuppression.} + } + } + FILTER(!BOUND(?dateCreationEnfant) || ?dateCreationEnfant <= '${date}'^^xsd:date) + FILTER(!BOUND(?dateSuppressionEnfant) || ?dateSuppressionEnfant > '${date}'^^xsd:date) + FILTER(!BOUND(?dateCreation) || ?dateCreation <= '${date}'^^xsd:date) + FILTER(!BOUND(?dateSuppression) || ?dateSuppression > '${date}'^^xsd:date) +} +ORDER BY ?type ?code \ No newline at end of file diff --git a/src/main/resources/request/geographie/getAscendantsOrDescendantsByCodeTypeDate.ftlh b/src/main/resources/request/geographie/getAscendantsOrDescendantsByCodeTypeDate.ftlh index 1aea49be..959b32e3 100644 --- a/src/main/resources/request/geographie/getAscendantsOrDescendantsByCodeTypeDate.ftlh +++ b/src/main/resources/request/geographie/getAscendantsOrDescendantsByCodeTypeDate.ftlh @@ -1,29 +1,32 @@ -SELECT DISTINCT ?uri ?code ?type ?typeArticle ?intitule ?intituleSansArticle ?dateCreation ?dateSuppression ?chefLieu +SELECT DISTINCT ?uri ?code ?type ?typeArticle ?intitule ?intituleSansArticle ?dateCreation ?dateSuppression ?chefLieu ?typeDIris FROM WHERE { ?origine a igeo:${typeOrigine} ; igeo:codeINSEE '${code}' . -<#if ascendant = "true"> +<#if ascendant == "true"> ?origine igeo:subdivisionDirecteDe+ ?uri . <#else> ?uri igeo:subdivisionDirecteDe+ ?origine . - - + + ?uri igeo:codeINSEE ?code ; a ?type ; igeo:codeArticle ?typeArticle ; igeo:nom ?intitule ; igeo:nomSansArticle ?intituleSansArticle . - + Optional{ + ?uri igeo:typeDIRIS ?uriTypeDIris ; + BIND(SUBSTR(STR(?uriTypeDIris ), STRLEN(STR(?uriTypeDIris )), 1) AS ?typeDIris) + } <#if type != "none"> ?uri a igeo:${type} . - + OPTIONAL { ?uri (igeo:sousPrefecture|igeo:prefecture|igeo:prefectureDeRegion|igeo:bureauCentralisateur) ?chefLieuRDF . ?chefLieuRDF igeo:codeINSEE ?chefLieu . - + OPTIONAL { ?evenementCreationChefLieu igeo:creation ?chefLieuRDF ; igeo:date ?dateCreationChefLieu . @@ -32,7 +35,7 @@ FROM ?evenementSuppressionChefLieu igeo:suppression ?chefLieuRDF ; igeo:date ?dateSuppressionChefLieu. } - + <#if date != "*"> FILTER(!BOUND(?dateCreationChefLieu) || ?dateCreationChefLieu <= '${date}'^^xsd:date) FILTER(!BOUND(?dateSuppressionChefLieu) || ?dateSuppressionChefLieu > '${date}'^^xsd:date) @@ -43,13 +46,18 @@ FROM ?evenementCreationOrigine igeo:creation ?origine ; igeo:date ?dateCreationOrigine. } + OPTIONAL { ?evenementSuppressionOrigine igeo:suppression ?origine ; igeo:date ?dateSuppressionOrigine. } + + OPTIONAL { FILTER(!BOUND(?dateCreationOrigine) || ?dateCreationOrigine <= '${date}'^^xsd:date) + } + OPTIONAL { FILTER(!BOUND(?dateSuppressionOrigine) || ?dateSuppressionOrigine > '${date}'^^xsd:date) - + } OPTIONAL { ?evenementCreation igeo:creation ?uri ; igeo:date ?dateCreation . diff --git a/src/main/resources/request/geographie/getIrisByCodeDate.ftlh b/src/main/resources/request/geographie/getIrisByCodeDate.ftlh new file mode 100644 index 00000000..bf2a575a --- /dev/null +++ b/src/main/resources/request/geographie/getIrisByCodeDate.ftlh @@ -0,0 +1,25 @@ +SELECT DISTINCT ?uri ?type ?code ?intituleSansArticle ?intitule ?typeDIris ?dateCreation ?dateSuppression ?typeArticle +WHERE { + + ?s a igeo:Iris ; + igeo:typeDIRIS ?uriTypeDIris ; + igeo:codeINSEE '${code}'; + igeo:codeArticle ?typeArticle ; + igeo:codeINSEE ?code . + BIND(SUBSTR(STR(?uriTypeDIris ), STRLEN(STR(?uriTypeDIris )), 1) AS ?typeDIris) + + OPTIONAL { + ?iris igeo:subdivisionDirecteDe+ ?s ; a igeo:Iris . + } + FILTER (!BOUND(?iris)) + FILTER(?code = '${code}') + ?s igeo:nom ?intitule; + igeo:nomSansArticle ?intituleSansArticle ; + a ?typeRDF . + BIND(STRAFTER(STR(?typeRDF),"http://rdf.insee.fr/def/geo#") AS ?type). + BIND(STR(?s) AS ?uri). + OPTIONAL {?s ^igeo:creation/igeo:date ?dateCreation } + OPTIONAL {?s ^igeo:suppression/igeo:date ?dateSuppression } + FILTER(!BOUND(?dateCreation) || ?dateCreation <= '${date}'^^xsd:date) + FILTER(!BOUND(?dateSuppression) || ?dateSuppression > '${date}'^^xsd:date) + } \ No newline at end of file diff --git a/src/main/resources/request/geographie/getIrisList.ftlh b/src/main/resources/request/geographie/getIrisList.ftlh new file mode 100644 index 00000000..be8063fb --- /dev/null +++ b/src/main/resources/request/geographie/getIrisList.ftlh @@ -0,0 +1,41 @@ +SELECT DISTINCT ?uri ?type ?code ?intitule ?intituleSansArticle ?typeArticle ?typeDIris ?dateCreation ?dateSuppression +WHERE { + { + ?s a igeo:Iris ; + igeo:typeDIRIS ?uriTypeDIris ; +<#if com != "true"> + (igeo:subdivisionDirecteDe)+ ; + + igeo:codeINSEE ?code . + BIND(SUBSTR(STR(?uriTypeDIris ), STRLEN(STR(?uriTypeDIris )), 1) AS ?typeDIris) + } + UNION + { + ?s a igeo:Commune ; +<#if com != "true"> + (igeo:subdivisionDirecteDe)+ ; + + igeo:codeINSEE ?codeCommune . + OPTIONAL { + ?iris igeo:subdivisionDirecteDe+ ?s ; a igeo:Iris . + OPTIONAL {?iris ^igeo:creation/igeo:date ?dateCreationIris } + OPTIONAL {?iris ^igeo:suppression/igeo:date ?dateSuppressionIris } + FILTER (!BOUND(?dateCreationIris) || ?dateCreationIris <= '${date}'^^xsd:date ) + FILTER (!BOUND(?dateSuppressionIris) || ?dateSuppressionIris > '${date}'^^xsd:date ) + } + FILTER (!BOUND(?iris)) + BIND(CONCAT(?codeCommune, '0000') AS ?code) + } + ?s igeo:nom ?intitule; + igeo:codeArticle ?typeArticle ; + igeo:nomSansArticle ?intituleSansArticle ; + a ?typeRDF . + #BIND(STRAFTER(STR(?typeRDF),"http://rdf.insee.fr/def/geo#") AS ?type). + BIND(STR(?typeRDF) AS ?type). + BIND(STR(?s) AS ?uri). + OPTIONAL {?s ^igeo:creation/igeo:date ?dateCreation } + OPTIONAL {?s ^igeo:suppression/igeo:date ?dateSuppression } + FILTER (!BOUND(?dateCreation) || ?dateCreation <= '${date}'^^xsd:date ) + FILTER (!BOUND(?dateSuppression) || ?dateSuppression > '${date}'^^xsd:date ) +} +ORDER BY ?code \ No newline at end of file diff --git a/src/test/java/fr/insee/rmes/api/geo/territoire/IrisApiTest.java b/src/test/java/fr/insee/rmes/api/geo/territoire/IrisApiTest.java new file mode 100644 index 00000000..36dcb772 --- /dev/null +++ b/src/test/java/fr/insee/rmes/api/geo/territoire/IrisApiTest.java @@ -0,0 +1,341 @@ +package fr.insee.rmes.api.geo.territoire; + +import fr.insee.rmes.api.AbstractApiTest; +import fr.insee.rmes.api.geo.pseudointegrationtest.ConstantForIntegration; +import fr.insee.rmes.modeles.geo.territoire.Canton; +import fr.insee.rmes.modeles.geo.territoire.CodeIris; +import fr.insee.rmes.modeles.geo.territoire.Iris; +import fr.insee.rmes.utils.CSVUtils; +import fr.insee.rmes.utils.IrisUtils; +import fr.insee.rmes.utils.ResponseUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static fr.insee.rmes.api.geo.pseudointegrationtest.ConstantForIntegration.assertEqualsJson; +import static fr.insee.rmes.api.geo.pseudointegrationtest.ConstantForIntegration.assertEqualsXml; +import static fr.insee.rmes.utils.JavaLangUtils.merge; +import static javax.ws.rs.core.MediaType.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IrisApiTest extends AbstractApiTest { + + private static final String PSEUDOIRIS_EXPECTED_XML = "\t\n" + + "\t\tAbbans-Dessus\n" + + "\t\tCommune\n" + + "\t\t1943-01-01\n" + + "\t\tAbbans-Dessus\n" + + "\t\n"; + private static final String PSEUDOIRIS_CSV = "uri,code,typeArticle,intitule,intituleSansArticle,dateCreation,dateSuppression,chefLieu,categorieJuridique,intituleComplet\n" + + "http://id.insee.fr/geo/commune/d22b67f9-76c1-44fb-99d4-bdbda1e1ec3f,25002,1,Abbans-Dessus,Abbans-Dessus,1943-01-01,,,,"; + private static final String PSEUDOIRIS_EXPECTED_JSON = "{\n" + + " \"code\": \"250020000\",\n" + + " \"uri\": \"http://id.insee.fr/geo/commune/d22b67f9-76c1-44fb-99d4-bdbda1e1ec3f\",\n" + + " \"type\": \"Commune\",\n" + + " \"dateCreation\": \"1943-01-01\",\n" + + " \"intituleSansArticle\": \"Abbans-Dessus\",\n" + + " \"typeArticle\": \"1\",\n" + + " \"intitule\": \"Abbans-Dessus\"\n" + + "}"; + + private static final String IRIS_CSV = "uri,type,code,intituleSansArticle,intitule,typeDIris,dateCreation,dateSuppression,typeArticle\n" + + "http://id.insee.fr/geo/iris/3adf17ae-ac6a-424f-9b08-dea51c72ee8c,Iris,693870103,Centre Berthelot,Centre Berthelot,H,2008-01-01,,X"; + private static final String IRIS_EXPECTED_XML ="\n" + + " Centre Berthelot\n" + + " Iris\n" + + " 2008-01-01\n" + + " Centre Berthelot\n" + + " H\n" + + ""; + private static final String IRIS_EXPECTED_JSON="{\n" + + " \"code\": \"693870103\",\n" + + " \"uri\": \"http://id.insee.fr/geo/iris/3adf17ae-ac6a-424f-9b08-dea51c72ee8c\",\n" + + " \"type\": \"Iris\",\n" + + " \"dateCreation\": \"2008-01-01\",\n" + + " \"typeDIris\": \"H\",\n" + + " \"intitule\": \"Centre Berthelot\",\n" + + " \"intituleSansArticle\": \"Centre Berthelot\",\n" + + " \"typeArticle\": \"X\"\n" + + "}"; + @InjectMocks + private IrisApi geoApi; + + @Mock + private IrisUtils irisUtils; + + private final IrisApi geoApiSansMock=new IrisApi(); + + private final Iris iris = new Iris(); + + private final String codeIrisCorrect="010040101"; + + public Stream getWithCodeAndDatefactory() { + Stream gets = Stream.of(geoApiSansMock::getByCode); + var intermediaire= merge(gets, List.of(APPLICATION_XML, APPLICATION_JSON), Pair::of ); + return merge(intermediaire, Arrays.asList(null, "mauvaiseDate", "1982-07-19"), (pair, d)->Arguments.of(pair.getLeft(), pair.getRight(), d)); + } + + @BeforeEach + void resetSomeMocks(){ + Mockito.reset(super.mockResponseUtils); + super.list.clear(); + iris.setUri(null); + } + + @ParameterizedTest + @MethodSource("getWithCodeAndDatefactory") + void givenAllGetsIrisWithCodeAndDate_whenIncorrectCode_thenResponseIsKo(ConstantForIntegration.GetWithCodeAndDate getWithCodeAndDate, String mediaType, String date) { + + // Call method + String codeIrisIncorrect = "something"; + var response=getWithCodeAndDate.get(codeIrisIncorrect, mediaType, date); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + verify(mockResponseUtils, never()).produceResponse(Mockito.any(), Mockito.any()); + } + + + @Test + void givenGetIris_WhenCorrectRequest_thenParameterDateIsBad() { + + // Call method header content = xml + Response response = geoApi.getByCode(codeIrisCorrect, APPLICATION_XML, "nimportequoi"); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + + @Test + public void given_CorrectPseudoIrisCode_thenReturnCommune_XML() { + //given + String inputCode = "250020000"; + String header= APPLICATION_XML; + when(irisUtils.hasIrisDescendant(inputCode.substring(0,5))).thenReturn(false); + when(mockSparqlUtils.executeSparqlQuery(anyString())).thenReturn(PSEUDOIRIS_CSV); + IrisApi irisApi=new IrisApi(mockSparqlUtils, new CSVUtils(), new ResponseUtils(), irisUtils); + //when + var response=irisApi.getByCode(inputCode, header, null); + //then + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEqualsXml(PSEUDOIRIS_EXPECTED_XML, response.getEntity()); + } + + @Test + public void given_CorrectPseudoIrisCode_thenReturnCommune_Json() { + //given + String inputCode = "250020000"; + String header= APPLICATION_JSON; + when(irisUtils.hasIrisDescendant(inputCode.substring(0,5))).thenReturn(false); + when(mockSparqlUtils.executeSparqlQuery(anyString())).thenReturn(PSEUDOIRIS_CSV); + IrisApi irisApi=new IrisApi(mockSparqlUtils, new CSVUtils(), new ResponseUtils(), irisUtils); + //when + var response=irisApi.getByCode(inputCode, header, null); + //then + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEqualsJson(PSEUDOIRIS_EXPECTED_JSON, response.getEntity()); + } + + @Test + public void given_CorrectIrisCode_thenReturnCommune_XML() { + //given + String inputCode = "693870103"; + String header= APPLICATION_XML; + when(irisUtils.hasIrisDescendant(inputCode.substring(0,5))).thenReturn(true); + when(mockSparqlUtils.executeSparqlQuery(anyString())).thenReturn(IRIS_CSV); + IrisApi irisApi=new IrisApi(mockSparqlUtils, new CSVUtils(), new ResponseUtils(), irisUtils); + //when + var response=irisApi.getByCode(inputCode, header, null); + //then + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEqualsXml(IRIS_EXPECTED_XML, response.getEntity()); + } + + @Test + public void given_CorrectIrisCode_thenReturnCommune_Json() { + //given + String inputCode = "693870103"; + String header= APPLICATION_JSON; + when(irisUtils.hasIrisDescendant(inputCode.substring(0,5))).thenReturn(true); + when(mockSparqlUtils.executeSparqlQuery(anyString())).thenReturn(IRIS_CSV); + IrisApi irisApi=new IrisApi(mockSparqlUtils, new CSVUtils(), new ResponseUtils(), irisUtils); + //when + var response=irisApi.getByCode(inputCode, header, null); + //then + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEqualsJson(IRIS_EXPECTED_JSON, response.getEntity()); + } + @ParameterizedTest + @ValueSource(strings = {MediaType.APPLICATION_JSON, APPLICATION_XML}) + void givenGetListeIris_whenCorrectRequest_thenResponseIsOk(String mediaType) { + + // Mock + this.mockUtilsMethodsThenReturnListOfPojo(Boolean.TRUE); + list.add(new Iris()); + + // Call method + geoApi.getListe(mediaType, null,true); + verify(mockResponseUtils, times(1)).produceResponse(Mockito.any(), Mockito.any()); + } + + @Test + void givenGetListeIris_WhenCorrectRequest_thenResponseIsNotFound() { + + // Mock methods + this.mockUtilsMethodsThenReturnListOfPojo(Boolean.FALSE); + list.clear(); + + // Call method header content = xml + Response response = geoApi.getListe(APPLICATION_XML, null,true); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + // Call method header content = json + response = geoApi.getListe(MediaType.APPLICATION_JSON, null,true); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + verify(mockResponseUtils, never()).produceResponse(Mockito.any(), Mockito.any()); + } + + @ParameterizedTest + @ValueSource(strings = {"2000-01-01", "*"}) + @NullSource + //default = current day + void givenGetListeIris_WhenCorrectRequest_thenParameterDateIsRight(String date) { + + // Mock methods + this.mockUtilsMethodsThenReturnListOfPojo(Boolean.TRUE); + list.add(new Iris()); + + // Call method header content = xml + geoApi.getListe(APPLICATION_XML, date,true); + verify(mockResponseUtils, times(1)).produceResponse(Mockito.any(), Mockito.any()); + } + + @Test + void givenGetListeIris_WhenCorrectRequest_thenParameterDateIsBad() { + + // Call method header content = xml + Response response = geoApi.getListe(APPLICATION_XML, "nimportequoi",true); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + + @Test + void givenGetListIris_WhenHeaderIsNotAcceptable() { + // Mock methods + when(mockSparqlUtils.executeSparqlQuery(Mockito.any())).thenReturn(""); + when(mockCSVUtils.populateMultiPOJO(Mockito.anyString(), Mockito.any())).thenReturn(list); + list.add(new Iris()); + + // Call method header content = text plain + Response response = geoApi.getListe(MediaType.TEXT_PLAIN, null,true); + assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus()); + } + + @ParameterizedTest + @CsvSource({ + "'"+MediaType.APPLICATION_JSON+"', ,", + "'"+APPLICATION_XML+"', ,", + "'"+APPLICATION_XML+"',,'Iris'", + "'"+APPLICATION_XML+"','2000-01-01'," + }) + void givenGetIrisAscendants_whenCorrectRequest_thenResponseIsOk(String mediaType, String date, String typeTerritoire) { + + // Mock methods + this.mockUtilsMethodsThenReturnListOfPojo(Boolean.TRUE); + list.add(new Canton()); + + // Call method + geoApi.getAscendants(codeIrisCorrect, mediaType, date, typeTerritoire); + verify(mockResponseUtils, times(1)).produceResponse(Mockito.any(), Mockito.any()); + } + + @Test + void givenGetIrisAscendants_WhenCorrectRequest_thenResponseIsNotFound() { + + // Mock methods + this.mockUtilsMethodsThenReturnListOfPojo(Boolean.FALSE); + list.clear(); + + // Call method header content = xml + Response response = geoApi.getAscendants(codeIrisCorrect, MediaType.APPLICATION_JSON, null, null); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + // Call method header content = json + response = geoApi.getAscendants(codeIrisCorrect, APPLICATION_XML, null, null); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + verify(mockResponseUtils, never()).produceResponse(Mockito.any(), Mockito.any()); + } + + + @ParameterizedTest + @CsvSource({ + "'"+APPLICATION_XML+"', 'nimportequoi',", + "'"+APPLICATION_XML+"',,'unTypeQuelconque'", + }) + void givenGetIrisAscendants_WhenCorrectRequest_WithBadParameter(String mediaType, String date, String typeTerritoire) { + + // Call method header content = xml + Response response = geoApi.getAscendants(codeIrisCorrect,mediaType, date, typeTerritoire); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + public Stream callsWithBadParameters() { + return Stream.of( + Arguments.of("getListeWithBadDate", (Supplier) () -> geoApi.getListe(APPLICATION_XML, "nimportequoi",null)), + Arguments.of("getAscendantsWithBadDate", (Supplier) () -> geoApi.getAscendants(codeIrisCorrect, APPLICATION_XML, "nimportequoi", null)), + Arguments.of("getAscendantsWithBadTerritory", (Supplier) () -> geoApi.getAscendants(codeIrisCorrect, APPLICATION_XML, null, "unTypeQuelconque")) + ); + } + + public Stream callsWithBadHeaders() { + return Stream.of( + Arguments.of("getListeWithBadHeader", (Supplier) () -> geoApi.getListe(TEXT_PLAIN, null,true)), + Arguments.of("getAscendantsWithBadHeader", (Supplier) () -> geoApi.getAscendants(codeIrisCorrect, TEXT_PLAIN, null, null)) + ); + } + + public Stream callsWithCorrectRequest() { + return Stream.of( + Arguments.of("getListe_JSON_noDate", (Supplier) () -> geoApi.getListe(APPLICATION_JSON, null,true)), + Arguments.of("getListe_XML_noDate", (Supplier) () -> geoApi.getListe(APPLICATION_XML, null,true)), + Arguments.of("getListe_XML_date", (Supplier) () -> geoApi.getListe(APPLICATION_XML, "2000-01-01",true)), + Arguments.of("getListe_dateStar", (Supplier) () -> geoApi.getListe(APPLICATION_JSON, "*",true)) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"123456789", "2A1110000", "2A1110001", "2B1110001"}) + void test_CodeIris_validPattern(String code){ + //given param code + //when + var codeIris= CodeIris.of(code); + //then + assertFalse(codeIris.isInvalid()); + assertDoesNotThrow(codeIris::code); + } + + @ParameterizedTest + @ValueSource(strings = {"12345A789", "2a1110000", "xxxxxxxxx", "2B111000", "2B111000199"}) + void test_CodeIris_invalidPattern(String code){ + //given param code + //when + var codeIris= CodeIris.of(code); + //then + assertTrue(codeIris.isInvalid()); + assertThrows(IllegalStateException.class,codeIris::code); + } +} diff --git a/src/test/java/fr/insee/rmes/utils/CSVUtilsTest.java b/src/test/java/fr/insee/rmes/utils/CSVUtilsTest.java new file mode 100644 index 00000000..16bdc891 --- /dev/null +++ b/src/test/java/fr/insee/rmes/utils/CSVUtilsTest.java @@ -0,0 +1,23 @@ +package fr.insee.rmes.utils; + +import fr.insee.rmes.modeles.geo.territoire.Commune; +import org.junit.jupiter.api.Test; + +class CSVUtilsTest { + + @Test + void populateMultiPOJO() { + CSVUtils csvutils= new CSVUtils(); + csvutils.populateMultiPOJO("uri,type,code,intitule,intituleSansArticle,typeArticle,typeDIris,dateCreation,dateSuppression\n" + + "http://id.insee.fr/geo/commune/6be6a231-0e6f-4462-9799-94903bdf3ca5,Commune,988330000,Kouaoua,Kouaoua,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/44901691-98ac-4ac0-ad8d-dd84d75c3017,Commune,988320000,Yaté,Yaté,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/775edf31-a9f5-4b1a-a848-f4b1cbe07812,Commune,988310000,Voh,Voh,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/2cb6fd01-6d8d-4f58-99fd-771ae51305b7,Commune,988300000,Touho,Touho,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/389da151-6883-4a3f-b7bf-2f50308c0ff0,Commune,988290000,Thio,Thio,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/f168b24d-1880-418c-9ccd-1a31736d6996,Commune,988280000,Sarraméa,Sarraméa,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/98741ff3-4355-415a-9c72-3d81b29873ab,Commune,988270000,Poya,Poya,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/fa1abbc5-b24e-4d8b-a8c2-c3f2df4e40e4,Commune,988260000,Poum,Poum,0,,2008-01-01,\n" + + "http://id.insee.fr/geo/commune/f2c631cb-f134-4101-b7a7-15d60e07b8a7,Iris,988250000,Pouembout,Pouembout,0,H,2008-01-01,\n", Commune.class); + + } +} \ No newline at end of file