From a7de8e61c39fb5087327cd574738811c2ac0ded8 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Mon, 20 Sep 2021 16:47:35 +0200 Subject: [PATCH] Merged repositories --- CHANGELOG.md | 6 + FUNDING.md | 26 + LICENSE.md | 312 ++++++++ README.md | 52 +- geoportal-client/CHANGELOG.md | 27 + geoportal-client/FUNDING.md | 26 + geoportal-client/LICENSE.md | 312 ++++++++ geoportal-client/README.md | 50 ++ geoportal-client/pom.xml | 139 ++++ .../client/DefaultMongoConcessioni.java | 218 +++++ .../geoportal/client/DefaultProjects.java | 93 +++ .../geoportal/client/GeoPortalClient.java | 8 + .../client/GeoportalAbstractPlugin.java | 72 ++ .../geoportal/client/ProjectsPlugin.java | 41 + .../client/legacy/ConcessioniManager.java | 136 ++++ .../client/legacy/ConcessioniManagerI.java | 29 + .../client/legacy/ConcessioniPlugin.java | 44 ++ .../client/legacy/DefaultConcessioni.java | 71 ++ .../client/legacy/MongoConcessioniPlugin.java | 41 + .../legacy/StatefulMongoConcessioni.java | 135 ++++ .../StatefulMongoConcessioniPlugin.java | 38 + .../client/model/CommunicationException.java | 36 + .../geoportal/client/utils/FileSets.java | 86 ++ .../geoportal/client/utils/Queries.java | 42 + .../geoportal/client/utils/Serialization.java | 41 + .../application/geoportal/StorageTests.java | 34 + .../geoportal/clients/BasicVreTests.java | 14 + .../clients/StatefulClientTests.java | 113 +++ .../clients/StatelessClientTests.java | 216 +++++ .../geoportal/clients/TokenSetter.java | 35 + .../clients/legacy/ConcessioniTests.java | 81 ++ .../common/model/Serializations.java | 33 + .../geoportal/common/model/TestModel.java | 130 +++ .../geoportal/usecases/ClearConcessioni.java | 59 ++ .../geoportal/usecases/EditFileSet.java | 67 ++ .../geoportal/usecases/Export.java | 159 ++++ .../geoportal/usecases/Import.java | 110 +++ .../geoportal/usecases/RepublishAll.java | 54 ++ .../geoportal/usecases/UnpublishSingle.java | 17 + .../usecases/mocks/MockFromFolder.java | 171 ++++ .../resources/concessioni/Concessione.json | 747 ++++++++++++++++++ .../concessioni/ConcessioniList.json | 1 + .../resources/concessioni/DATASET_GNA_01.csv | 6 + .../resources/concessioni/DATASET_GNA_02.csv | 5 + .../concessioni/concessioni04-03.csv | 18 + .../concessioni/concessioni04-03_filtered.csv | 18 + .../concessioni/concessioni_23_04.csv | 4 + .../resources/concessioni/filters/all.json | 1 + .../concessioni/filters/legacyid.json | 3 + .../concessioni/filters/missingCentroid.json | 4 + .../concessioni/filters/nonFabio.json | 3 + .../concessioni/filters/validated.json | 3 + .../test/resources/concessioni/immagine.png | Bin 0 -> 5879 bytes .../test/resources/concessioni/immagine2.png | Bin 0 -> 5879 bytes .../resources/concessioni/invio_08_05.csv | 7 + .../src/test/resources/concessioni/pianta.shp | Bin 0 -> 96444 bytes .../src/test/resources/concessioni/pianta.shx | Bin 0 -> 3308 bytes .../src/test/resources/concessioni/pos.dbf | Bin 0 -> 461 bytes .../src/test/resources/concessioni/pos.prj | 1 + .../src/test/resources/concessioni/pos.qpj | 1 + .../src/test/resources/concessioni/pos.shp | Bin 0 -> 660 bytes .../src/test/resources/concessioni/pos.shx | Bin 0 -> 132 bytes .../concessioni/queries/firstRegistered.json | 10 + .../queries/lastNameRegisteredByFabio.json | 18 + .../concessioni/queries/lastRegistered.json | 11 + .../queries/publicationWarningMessages.json | 10 + .../test/resources/concessioni/relazione.pdf | Bin 0 -> 107941 bytes geoportal-common/CHANGELOG.md | 41 + geoportal-common/FUNDING.md | 26 + geoportal-common/LICENSE.md | 312 ++++++++ geoportal-common/README.md | 50 ++ geoportal-common/pom.xml | 103 +++ .../common/faults/JsonParseException.java | 37 + .../common/model/BasicJSONObject.java | 7 + .../model/legacy/AbstractRelazione.java | 16 + .../common/model/legacy/AccessPolicy.java | 7 + .../model/legacy/AssociatedContent.java | 132 ++++ .../geoportal/common/model/legacy/BBOX.java | 36 + .../common/model/legacy/Concessione.java | 512 ++++++++++++ .../common/model/legacy/GeoServerContent.java | 28 + .../model/legacy/InputStreamDescriptor.java | 21 + .../common/model/legacy/LayerConcessione.java | 152 ++++ .../common/model/legacy/OtherContent.java | 15 + .../common/model/legacy/PersistedContent.java | 36 + .../geoportal/common/model/legacy/Record.java | 74 ++ .../common/model/legacy/RecordType.java | 6 + .../common/model/legacy/RelazioneScavo.java | 60 ++ .../model/legacy/SDILayerDescriptor.java | 25 + .../common/model/legacy/UploadedImage.java | 73 ++ .../common/model/legacy/WorkspaceContent.java | 19 + .../common/model/legacy/report/Check.java | 56 ++ .../common/model/legacy/report/Checks.java | 7 + .../model/legacy/report/ConstraintCheck.java | 103 +++ .../model/legacy/report/DeletionReport.java | 5 + .../legacy/report/PublicationReport.java | 29 + .../model/legacy/report/ValidationReport.java | 80 ++ .../common/model/profile/DefaultCompiler.java | 7 + .../geoportal/common/model/profile/Field.java | 11 + .../common/model/profile/FieldMapping.java | 7 + .../common/model/profile/IndexDefinition.java | 7 + .../common/model/profile/IsoMapper.java | 7 + .../common/model/profile/Profile.java | 24 + .../common/model/profile/Validator.java | 5 + .../common/model/project/Centroid.java | 9 + .../common/model/project/Project.java | 43 + .../model/project/PublicationDetails.java | 22 + .../common/model/project/Status.java | 11 + .../common/model/project/StatusPhase.java | 13 + .../common/model/project/StoredFile.java | 4 + .../rest/AddSectionToConcessioneRequest.java | 21 + .../common/model/rest/Configuration.java | 15 + .../common/model/rest/DatabaseConnection.java | 16 + .../model/rest/PostgisIndexDescriptor.java | 13 + .../common/model/rest/QueryRequest.java | 28 + .../geoportal/common/rest/ConcessioniI.java | 10 + .../common/rest/InterfaceConstants.java | 44 ++ .../common/rest/MongoConcessioni.java | 32 + .../geoportal/common/rest/ProjectsI.java | 19 + .../geoportal/common/rest/TempFile.java | 15 + .../common/utils/CollectionsUtils.java | 44 ++ .../geoportal/common/utils/ContextUtils.java | 41 + .../geoportal/common/utils/Files.java | 48 ++ .../geoportal/common/utils/StorageUtils.java | 55 ++ .../geoportal/common/model/PathsTest.java | 67 ++ .../common/model/SerializationTest.java | 65 ++ .../geoportal/common/model/TestModel.java | 130 +++ .../src/test/resources/Concessione.json | 747 ++++++++++++++++++ .../test/resources/ProfileConcessioni.json | 29 + geoportal-service/CHANGELOG.md | 36 + geoportal-service/FUNDING.md | 26 + geoportal-service/LICENSE.md | 312 ++++++++ geoportal-service/README.md | 53 ++ geoportal-service/pom.xml | 230 ++++++ .../geoportal/service/AppManager.java | 18 + .../geoportal/service/GeoPortalService.java | 39 + .../geoportal/service/InterfaceUtils.java | 5 + .../geoportal/service/ServiceConstants.java | 15 + .../engine/ImplementationProvider.java | 63 ++ .../geoportal/service/engine/SDIManager.java | 410 ++++++++++ .../service/engine/WorkspaceManager.java | 137 ++++ .../geoportal/service/engine/cache/Cache.java | 9 + .../engine/mongo/ConcessioniMongoManager.java | 433 ++++++++++ .../service/engine/mongo/MongoManager.java | 152 ++++ .../engine/mongo/ProfiledMongoManager.java | 5 + .../engine/postgis/PostgisDBManager.java | 194 +++++ .../engine/postgis/PostgisDBManagerI.java | 37 + .../service/engine/postgis/PostgisIndex.java | 187 +++++ .../engine/providers/AbstractScopedMap.java | 67 ++ .../service/engine/providers/Engine.java | 12 + .../engine/providers/MongoClientProvider.java | 47 ++ .../providers/MongoConnectionProvider.java | 32 + .../providers/PostgisConnectionProvider.java | 33 + .../engine/providers/ProfileMapCache.java | 34 + .../providers/StorageClientProvider.java | 64 ++ .../engine/providers/StorageHubProvider.java | 25 + .../service/engine/providers/TTLObject.java | 17 + .../model/internal/db/DBConstants.java | 138 ++++ .../model/internal/db/MongoConnection.java | 16 + .../model/internal/db/PostgisTable.java | 314 ++++++++ .../faults/ConfigurationException.java | 38 + .../internal/faults/DataParsingException.java | 30 + .../internal/faults/DeletionException.java | 23 + .../faults/InvalidStateException.java | 38 + .../internal/faults/PublishException.java | 42 + .../faults/SDIInteractionException.java | 38 + .../geoportal/service/rest/Commons.java | 8 + .../service/rest/ConcessioniOverMongo.java | 237 ++++++ .../geoportal/service/rest/GuardedMethod.java | 49 ++ .../geoportal/service/rest/Profiles.java | 5 + .../geoportal/service/rest/Projects.java | 134 ++++ .../geoportal/service/rest/Sections.java | 51 ++ .../geoportal/service/utils/ContextUtils.java | 38 + .../geoportal/service/utils/ISUtils.java | 127 +++ .../service/utils/Serialization.java | 57 ++ .../resources/styles/clustered_points.sld | 173 ++++ .../src/main/webapp/WEB-INF/gcube-app.xml | 8 + .../src/main/webapp/WEB-INF/web.xml | 25 + .../service/BasicServiceTestUnit.java | 81 ++ .../service/ConcessioniOverMongoTest.java | 327 ++++++++ .../geoportal/service/MongoTests.java | 86 ++ .../geoportal/service/ProjectTests.java | 75 ++ .../geoportal/service/SDITests.java | 42 + .../geoportal/service/TestModel.java | 124 +++ .../geoportal/service/TokenSetter.java | 35 + .../service/ws/DescribeWSFolder.java | 31 + .../service/ws/DisplayWorkspaceTree.java | 12 + .../service/ws/SetWSFolderVisibility.java | 28 + .../src/test/resources/Concessione.json | 3 + .../test/resources/META-INF/persistence.xml | 35 + .../test/resources/concessioni/immagine.png | Bin 0 -> 5879 bytes .../test/resources/concessioni/immagine2.png | Bin 0 -> 5879 bytes .../concessioni/jsonFilters/all.json | 1 + .../concessioni/jsonFilters/legacyid.json | 3 + .../jsonFilters/missingCentroid.json | 4 + .../concessioni/jsonFilters/nonFabio.json | 3 + .../concessioni/jsonFilters/validated.json | 3 + .../jsonQueries/firstRegistered.json | 10 + .../lastNamesRegisteredByFabio.json | 18 + .../jsonQueries/lastRegistered.json | 11 + .../publicationWarningMessages.json | 10 + .../src/test/resources/concessioni/pianta.shp | Bin 0 -> 96444 bytes .../src/test/resources/concessioni/pianta.shx | Bin 0 -> 3308 bytes .../src/test/resources/concessioni/pos.dbf | Bin 0 -> 461 bytes .../src/test/resources/concessioni/pos.prj | 1 + .../src/test/resources/concessioni/pos.qpj | 1 + .../src/test/resources/concessioni/pos.shp | Bin 0 -> 660 bytes .../src/test/resources/concessioni/pos.shx | Bin 0 -> 132 bytes .../test/resources/concessioni/relazione.pdf | Bin 0 -> 107941 bytes .../src/test/resources/fakeProfile.json | 29 + .../src/test/resources/log4j.properties | 9 + .../src/test/resources/logback.xml | 15 + pom.xml | 142 ++++ 212 files changed, 13341 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 FUNDING.md create mode 100644 LICENSE.md create mode 100644 geoportal-client/CHANGELOG.md create mode 100644 geoportal-client/FUNDING.md create mode 100644 geoportal-client/LICENSE.md create mode 100644 geoportal-client/README.md create mode 100644 geoportal-client/pom.xml create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultMongoConcessioni.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultProjects.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoPortalClient.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoportalAbstractPlugin.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/ProjectsPlugin.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniManager.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniManagerI.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniPlugin.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/DefaultConcessioni.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/MongoConcessioniPlugin.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioni.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioniPlugin.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/model/CommunicationException.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/FileSets.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Queries.java create mode 100644 geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Serialization.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/StorageTests.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/clients/BasicVreTests.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatefulClientTests.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatelessClientTests.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/clients/TokenSetter.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/clients/legacy/ConcessioniTests.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/Serializations.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/ClearConcessioni.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/EditFileSet.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Export.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Import.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/RepublishAll.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/UnpublishSingle.java create mode 100644 geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/mocks/MockFromFolder.java create mode 100644 geoportal-client/src/test/resources/concessioni/Concessione.json create mode 100644 geoportal-client/src/test/resources/concessioni/ConcessioniList.json create mode 100644 geoportal-client/src/test/resources/concessioni/DATASET_GNA_01.csv create mode 100644 geoportal-client/src/test/resources/concessioni/DATASET_GNA_02.csv create mode 100644 geoportal-client/src/test/resources/concessioni/concessioni04-03.csv create mode 100644 geoportal-client/src/test/resources/concessioni/concessioni04-03_filtered.csv create mode 100644 geoportal-client/src/test/resources/concessioni/concessioni_23_04.csv create mode 100644 geoportal-client/src/test/resources/concessioni/filters/all.json create mode 100644 geoportal-client/src/test/resources/concessioni/filters/legacyid.json create mode 100644 geoportal-client/src/test/resources/concessioni/filters/missingCentroid.json create mode 100644 geoportal-client/src/test/resources/concessioni/filters/nonFabio.json create mode 100644 geoportal-client/src/test/resources/concessioni/filters/validated.json create mode 100644 geoportal-client/src/test/resources/concessioni/immagine.png create mode 100644 geoportal-client/src/test/resources/concessioni/immagine2.png create mode 100644 geoportal-client/src/test/resources/concessioni/invio_08_05.csv create mode 100644 geoportal-client/src/test/resources/concessioni/pianta.shp create mode 100644 geoportal-client/src/test/resources/concessioni/pianta.shx create mode 100644 geoportal-client/src/test/resources/concessioni/pos.dbf create mode 100644 geoportal-client/src/test/resources/concessioni/pos.prj create mode 100644 geoportal-client/src/test/resources/concessioni/pos.qpj create mode 100644 geoportal-client/src/test/resources/concessioni/pos.shp create mode 100644 geoportal-client/src/test/resources/concessioni/pos.shx create mode 100644 geoportal-client/src/test/resources/concessioni/queries/firstRegistered.json create mode 100644 geoportal-client/src/test/resources/concessioni/queries/lastNameRegisteredByFabio.json create mode 100644 geoportal-client/src/test/resources/concessioni/queries/lastRegistered.json create mode 100644 geoportal-client/src/test/resources/concessioni/queries/publicationWarningMessages.json create mode 100644 geoportal-client/src/test/resources/concessioni/relazione.pdf create mode 100644 geoportal-common/CHANGELOG.md create mode 100644 geoportal-common/FUNDING.md create mode 100644 geoportal-common/LICENSE.md create mode 100644 geoportal-common/README.md create mode 100644 geoportal-common/pom.xml create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/faults/JsonParseException.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/BasicJSONObject.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AbstractRelazione.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AccessPolicy.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AssociatedContent.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/BBOX.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Concessione.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/GeoServerContent.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/InputStreamDescriptor.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/LayerConcessione.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/OtherContent.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/PersistedContent.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Record.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RecordType.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RelazioneScavo.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/SDILayerDescriptor.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/UploadedImage.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/WorkspaceContent.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Check.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Checks.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/ConstraintCheck.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/DeletionReport.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/PublicationReport.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/ValidationReport.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/DefaultCompiler.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Field.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/FieldMapping.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IndexDefinition.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IsoMapper.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Profile.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Validator.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Centroid.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Project.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/PublicationDetails.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Status.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StatusPhase.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StoredFile.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/AddSectionToConcessioneRequest.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/Configuration.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/DatabaseConnection.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/PostgisIndexDescriptor.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/QueryRequest.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ConcessioniI.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/MongoConcessioni.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ProjectsI.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/TempFile.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/CollectionsUtils.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/ContextUtils.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/Files.java create mode 100644 geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/StorageUtils.java create mode 100644 geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/PathsTest.java create mode 100644 geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/SerializationTest.java create mode 100644 geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java create mode 100644 geoportal-common/src/test/resources/Concessione.json create mode 100644 geoportal-common/src/test/resources/ProfileConcessioni.json create mode 100644 geoportal-service/CHANGELOG.md create mode 100644 geoportal-service/FUNDING.md create mode 100644 geoportal-service/LICENSE.md create mode 100644 geoportal-service/README.md create mode 100644 geoportal-service/pom.xml create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/AppManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/InterfaceUtils.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/cache/Cache.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManager.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManagerI.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisIndex.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/AbstractScopedMap.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/Engine.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoConnectionProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/PostgisConnectionProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/ProfileMapCache.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageClientProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageHubProvider.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/TTLObject.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/DBConstants.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/MongoConnection.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/PostgisTable.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ConfigurationException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DataParsingException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DeletionException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/PublishException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Commons.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Profiles.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Projects.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Sections.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ContextUtils.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ISUtils.java create mode 100644 geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java create mode 100644 geoportal-service/src/main/resources/styles/clustered_points.sld create mode 100644 geoportal-service/src/main/webapp/WEB-INF/gcube-app.xml create mode 100644 geoportal-service/src/main/webapp/WEB-INF/web.xml create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/MongoTests.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/ProjectTests.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/SDITests.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/TestModel.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/TokenSetter.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/ws/DescribeWSFolder.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/ws/DisplayWorkspaceTree.java create mode 100644 geoportal-service/src/test/java/org/gcube/application/geoportal/service/ws/SetWSFolderVisibility.java create mode 100644 geoportal-service/src/test/resources/Concessione.json create mode 100644 geoportal-service/src/test/resources/META-INF/persistence.xml create mode 100644 geoportal-service/src/test/resources/concessioni/immagine.png create mode 100644 geoportal-service/src/test/resources/concessioni/immagine2.png create mode 100644 geoportal-service/src/test/resources/concessioni/jsonFilters/all.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonFilters/legacyid.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonFilters/missingCentroid.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonFilters/nonFabio.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonFilters/validated.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonQueries/firstRegistered.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonQueries/lastNamesRegisteredByFabio.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonQueries/lastRegistered.json create mode 100644 geoportal-service/src/test/resources/concessioni/jsonQueries/publicationWarningMessages.json create mode 100644 geoportal-service/src/test/resources/concessioni/pianta.shp create mode 100644 geoportal-service/src/test/resources/concessioni/pianta.shx create mode 100644 geoportal-service/src/test/resources/concessioni/pos.dbf create mode 100644 geoportal-service/src/test/resources/concessioni/pos.prj create mode 100644 geoportal-service/src/test/resources/concessioni/pos.qpj create mode 100644 geoportal-service/src/test/resources/concessioni/pos.shp create mode 100644 geoportal-service/src/test/resources/concessioni/pos.shx create mode 100644 geoportal-service/src/test/resources/concessioni/relazione.pdf create mode 100644 geoportal-service/src/test/resources/fakeProfile.json create mode 100644 geoportal-service/src/test/resources/log4j.properties create mode 100644 geoportal-service/src/test/resources/logback.xml create mode 100644 pom.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ce44319 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# Changelog for org.gcube.spatial.data.gcube-sdi-suite + +## [v1.0.0-SNAPSHOT] - 2021-2-11 +First release diff --git a/FUNDING.md b/FUNDING.md new file mode 100644 index 0000000..9e48b94 --- /dev/null +++ b/FUNDING.md @@ -0,0 +1,26 @@ +# Acknowledgments + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - [DILIGENT](https://cordis.europa.eu/project/id/004260) (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - [D4Science](https://cordis.europa.eu/project/id/212488) (grant no. 212488); + - [D4Science-II](https://cordis.europa.eu/project/id/239019) (grant no.239019); + - [ENVRI](https://cordis.europa.eu/project/id/283465) (grant no. 283465); + - [iMarine](https://cordis.europa.eu/project/id/283644) (grant no. 283644); + - [EUBrazilOpenBio](https://cordis.europa.eu/project/id/288754) (grant no. 288754). +- the H2020 research and innovation programme + - [SoBigData](https://cordis.europa.eu/project/id/654024) (grant no. 654024); + - [PARTHENOS](https://cordis.europa.eu/project/id/654119) (grant no. 654119); + - [EGI-Engage](https://cordis.europa.eu/project/id/654142) (grant no. 654142); + - [ENVRI PLUS](https://cordis.europa.eu/project/id/654182) (grant no. 654182); + - [BlueBRIDGE](https://cordis.europa.eu/project/id/675680) (grant no. 675680); + - [PerformFISH](https://cordis.europa.eu/project/id/727610) (grant no. 727610); + - [AGINFRA PLUS](https://cordis.europa.eu/project/id/731001) (grant no. 731001); + - [DESIRA](https://cordis.europa.eu/project/id/818194) (grant no. 818194); + - [ARIADNEplus](https://cordis.europa.eu/project/id/823914) (grant no. 823914); + - [RISIS 2](https://cordis.europa.eu/project/id/824091) (grant no. 824091); + - [EOSC-Pillar](https://cordis.europa.eu/project/id/857650) (grant no. 857650); + - [Blue Cloud](https://cordis.europa.eu/project/id/862409) (grant no. 862409); + - [SoBigData-PlusPlus](https://cordis.europa.eu/project/id/871042) (grant no. 871042); diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3af0507 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,312 @@ +# European Union Public Licence V. 1.1 + + +EUPL © the European Community 2007 + + +This European Union Public Licence (the “EUPL”) applies to the Work or Software +(as defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Original Work is provided under the terms of this Licence when the Licensor +(as defined below) has placed the following notice immediately following the +copyright notice for the Original Work: + +Licensed under the EUPL V.1.1 + +or has expressed by any other mean his willingness to license under the EUPL. + + + +## 1. Definitions + +In this Licence, the following terms have the following meaning: + +- The Licence: this Licence. + +- The Original Work or the Software: the software distributed and/or + communicated by the Licensor under this Licence, available as Source Code and + also as Executable Code as the case may be. + +- Derivative Works: the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not + define the extent of modification or dependence on the Original Work required + in order to classify a work as a Derivative Work; this extent is determined by + copyright law applicable in the country mentioned in Article 15. + +- The Work: the Original Work and/or its Derivative Works. + +- The Source Code: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- The Executable Code: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- The Licensor: the natural or legal person that distributes and/or communicates + the Work under the Licence. + +- Contributor(s): any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- The Licensee or “You”: any natural or legal person who makes any usage of the + Software under the terms of the Licence. + +- Distribution and/or Communication: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, on-line or off-line, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + + + +## 2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, +sub-licensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, reproduce the Work, modify +- the Original Work, and make Derivative Works based upon the Work, communicate +- to the public, including the right to make available or display the Work or +- copies thereof to the public and perform publicly, as the case may be, the +- Work, distribute the Work or copies thereof, lend and rent the Work or copies +- thereof, sub-license rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + + + +## 3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute and/or communicate the Work. + + + +## 4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Original Work or Software, of the exhaustion of those rights or of other +applicable limitations thereto. + + + +## 5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: the Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes and/or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes and/or communicates copies of the +Original Works or Derivative Works based upon the Original Work, this +Distribution and/or Communication will be done under the terms of this Licence +or of a later version of this Licence unless the Original Work is expressly +distributed only under this version of the Licence. The Licensee (becoming +Licensor) cannot offer or impose any additional terms or conditions on the Work +or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes and/or Communicates Derivative +Works or copies thereof based upon both the Original Work and another work +licensed under a Compatible Licence, this Distribution and/or Communication can +be done under the terms of this Compatible Licence. For the sake of this clause, +“Compatible Licence” refers to the licences listed in the appendix attached to +this Licence. Should the Licensee’s obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing and/or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available for +as long as the Licensee continues to distribute and/or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + + + +## 6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + + + +## 7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +contributors. It is not a finished work and may therefore contain defects or +“bugs” inherent to this type of software development. + +For the above reason, the Work is provided under the Licence on an “as is” basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + + + +## 8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + + + +## 9. Additional agreements + +While distributing the Original Work or Derivative Works, You may choose to +conclude an additional agreement to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or services +consistent with this Licence. However, in accepting such obligations, You may +act only on your own behalf and on your sole responsibility, not on behalf of +the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred +by, or claims asserted against such Contributor by the fact You have accepted +any such warranty or additional liability. + + + +## 10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon “I agree” +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution and/or Communication by You of the Work or copies thereof. + + + +## 11. Information to the public + +In case of any Distribution and/or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + + + +## 12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + + + +## 13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work licensed hereunder. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed and/or reformed so as necessary to make +it valid and enforceable. + +The European Commission may publish other linguistic versions and/or new +versions of this Licence, so far this is required and reasonable, without +reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + + + +## 14. Jurisdiction + +Any litigation resulting from the interpretation of this License, arising +between the European Commission, as a Licensor, and any Licensee, will be +subject to the jurisdiction of the Court of Justice of the European Communities, +as laid down in article 238 of the Treaty establishing the European Community. + +Any litigation arising between Parties, other than the European Commission, and +resulting from the interpretation of this License, will be subject to the +exclusive jurisdiction of the competent court where the Licensor resides or +conducts its primary business. + + + +## 15. Applicable Law + +This Licence shall be governed by the law of the European Union country where +the Licensor resides or has his registered office. + +This licence shall be governed by the Belgian law if: + +- a litigation arises between the European Commission, as a Licensor, and any +- Licensee; the Licensor, other than the European Commission, has no residence +- or registered office inside a European Union country. + + + +## Appendix + + + +“Compatible Licences” according to article 5 EUPL are: + + +- GNU General Public License (GNU GPL) v. 2 + +- Open Software License (OSL) v. 2.1, v. 3.0 + +- Common Public License v. 1.0 + +- Eclipse Public License v. 1.0 + +- Cecill v. 2.0 + diff --git a/README.md b/README.md index 7b74897..85dd719 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ -# gcube-cms-suite +gCube CMS Suite +-------------------------------------------------- + +gCube CMS Suite is a set of components designed to manage complex space-temporal Documents defined by metadata Profiles. + +## Built with +* [gCube SmartGears] (https://gcube.wiki.gcube-system.org/gcube/SmartGears) - The gCube SmartGears framework +* [OpenJDK](https://openjdk.java.net/) - The JDK used +* [JAX-RS](https://github.com/eclipse-ee4j/jaxrs-api) - Java™ API for RESTful Web Services +* [Jersey](https://jersey.github.io/) - JAX-RS runtime +* [Maven](https://maven.apache.org/) - Dependency Management + +## Documentation + +Documentation can be found [here](https://gcube.wiki.gcube-system.org/gcube/GeoPortal_Service). + +## Change log + +See [CHANGELOG.md](CHANGELOG.md). + +## License + +This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LICENSE.md) file for details. + +## About the gCube Framework +This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an +open-source software toolkit used for building and operating Hybrid Data +Infrastructures enabling the dynamic deployment of Virtual Research Environments +by favouring the realisation of reuse oriented policies. + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - DILIGENT (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - D4Science (grant no. 212488); + - D4Science-II (grant no.239019); + - ENVRI (grant no. 283465); + - iMarine(grant no. 283644); + - EUBrazilOpenBio (grant no. 288754). +- the H2020 research and innovation programme + - SoBigData (grant no. 654024); + - PARTHENOS (grant no. 654119); + - EGIEngage (grant no. 654142); + - ENVRIplus (grant no. 654182); + - BlueBRIDGE (grant no. 675680); + - PerformFish (grant no. 727610); + - AGINFRAplus (grant no. 731001); + - DESIRA (grant no. 818194); + - ARIADNEplus (grant no. 823914); + - RISIS2 (grant no. 824091); diff --git a/geoportal-client/CHANGELOG.md b/geoportal-client/CHANGELOG.md new file mode 100644 index 0000000..edc4e93 --- /dev/null +++ b/geoportal-client/CHANGELOG.md @@ -0,0 +1,27 @@ +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# Changelog for org.gcube.application.geoportal-client + +## [v1.0.5-SNAPSHOT] - 2021-09-20 +Refactored repositories + +## [v1.0.4-SNAPSHOT] - 2020-11-11 +Serialization utils +Queries & Filters support +Test Reports +UseCases +Fixes #21897 + +## [v1.0.3] - 2020-11-11 +Stateful Concessioni Manager client over mongo + +## [v1.0.2] - 2020-11-11 +Fixed dulicate dependency declaration + +## [v1.0.1] - 2020-11-11 +Excluded common-calls 1.2.0 + + +## [v1.0.0] - 2020-11-11 + +First release \ No newline at end of file diff --git a/geoportal-client/FUNDING.md b/geoportal-client/FUNDING.md new file mode 100644 index 0000000..9e48b94 --- /dev/null +++ b/geoportal-client/FUNDING.md @@ -0,0 +1,26 @@ +# Acknowledgments + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - [DILIGENT](https://cordis.europa.eu/project/id/004260) (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - [D4Science](https://cordis.europa.eu/project/id/212488) (grant no. 212488); + - [D4Science-II](https://cordis.europa.eu/project/id/239019) (grant no.239019); + - [ENVRI](https://cordis.europa.eu/project/id/283465) (grant no. 283465); + - [iMarine](https://cordis.europa.eu/project/id/283644) (grant no. 283644); + - [EUBrazilOpenBio](https://cordis.europa.eu/project/id/288754) (grant no. 288754). +- the H2020 research and innovation programme + - [SoBigData](https://cordis.europa.eu/project/id/654024) (grant no. 654024); + - [PARTHENOS](https://cordis.europa.eu/project/id/654119) (grant no. 654119); + - [EGI-Engage](https://cordis.europa.eu/project/id/654142) (grant no. 654142); + - [ENVRI PLUS](https://cordis.europa.eu/project/id/654182) (grant no. 654182); + - [BlueBRIDGE](https://cordis.europa.eu/project/id/675680) (grant no. 675680); + - [PerformFISH](https://cordis.europa.eu/project/id/727610) (grant no. 727610); + - [AGINFRA PLUS](https://cordis.europa.eu/project/id/731001) (grant no. 731001); + - [DESIRA](https://cordis.europa.eu/project/id/818194) (grant no. 818194); + - [ARIADNEplus](https://cordis.europa.eu/project/id/823914) (grant no. 823914); + - [RISIS 2](https://cordis.europa.eu/project/id/824091) (grant no. 824091); + - [EOSC-Pillar](https://cordis.europa.eu/project/id/857650) (grant no. 857650); + - [Blue Cloud](https://cordis.europa.eu/project/id/862409) (grant no. 862409); + - [SoBigData-PlusPlus](https://cordis.europa.eu/project/id/871042) (grant no. 871042); diff --git a/geoportal-client/LICENSE.md b/geoportal-client/LICENSE.md new file mode 100644 index 0000000..3af0507 --- /dev/null +++ b/geoportal-client/LICENSE.md @@ -0,0 +1,312 @@ +# European Union Public Licence V. 1.1 + + +EUPL © the European Community 2007 + + +This European Union Public Licence (the “EUPL”) applies to the Work or Software +(as defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Original Work is provided under the terms of this Licence when the Licensor +(as defined below) has placed the following notice immediately following the +copyright notice for the Original Work: + +Licensed under the EUPL V.1.1 + +or has expressed by any other mean his willingness to license under the EUPL. + + + +## 1. Definitions + +In this Licence, the following terms have the following meaning: + +- The Licence: this Licence. + +- The Original Work or the Software: the software distributed and/or + communicated by the Licensor under this Licence, available as Source Code and + also as Executable Code as the case may be. + +- Derivative Works: the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not + define the extent of modification or dependence on the Original Work required + in order to classify a work as a Derivative Work; this extent is determined by + copyright law applicable in the country mentioned in Article 15. + +- The Work: the Original Work and/or its Derivative Works. + +- The Source Code: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- The Executable Code: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- The Licensor: the natural or legal person that distributes and/or communicates + the Work under the Licence. + +- Contributor(s): any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- The Licensee or “You”: any natural or legal person who makes any usage of the + Software under the terms of the Licence. + +- Distribution and/or Communication: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, on-line or off-line, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + + + +## 2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, +sub-licensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, reproduce the Work, modify +- the Original Work, and make Derivative Works based upon the Work, communicate +- to the public, including the right to make available or display the Work or +- copies thereof to the public and perform publicly, as the case may be, the +- Work, distribute the Work or copies thereof, lend and rent the Work or copies +- thereof, sub-license rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + + + +## 3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute and/or communicate the Work. + + + +## 4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Original Work or Software, of the exhaustion of those rights or of other +applicable limitations thereto. + + + +## 5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: the Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes and/or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes and/or communicates copies of the +Original Works or Derivative Works based upon the Original Work, this +Distribution and/or Communication will be done under the terms of this Licence +or of a later version of this Licence unless the Original Work is expressly +distributed only under this version of the Licence. The Licensee (becoming +Licensor) cannot offer or impose any additional terms or conditions on the Work +or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes and/or Communicates Derivative +Works or copies thereof based upon both the Original Work and another work +licensed under a Compatible Licence, this Distribution and/or Communication can +be done under the terms of this Compatible Licence. For the sake of this clause, +“Compatible Licence” refers to the licences listed in the appendix attached to +this Licence. Should the Licensee’s obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing and/or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available for +as long as the Licensee continues to distribute and/or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + + + +## 6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + + + +## 7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +contributors. It is not a finished work and may therefore contain defects or +“bugs” inherent to this type of software development. + +For the above reason, the Work is provided under the Licence on an “as is” basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + + + +## 8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + + + +## 9. Additional agreements + +While distributing the Original Work or Derivative Works, You may choose to +conclude an additional agreement to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or services +consistent with this Licence. However, in accepting such obligations, You may +act only on your own behalf and on your sole responsibility, not on behalf of +the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred +by, or claims asserted against such Contributor by the fact You have accepted +any such warranty or additional liability. + + + +## 10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon “I agree” +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution and/or Communication by You of the Work or copies thereof. + + + +## 11. Information to the public + +In case of any Distribution and/or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + + + +## 12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + + + +## 13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work licensed hereunder. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed and/or reformed so as necessary to make +it valid and enforceable. + +The European Commission may publish other linguistic versions and/or new +versions of this Licence, so far this is required and reasonable, without +reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + + + +## 14. Jurisdiction + +Any litigation resulting from the interpretation of this License, arising +between the European Commission, as a Licensor, and any Licensee, will be +subject to the jurisdiction of the Court of Justice of the European Communities, +as laid down in article 238 of the Treaty establishing the European Community. + +Any litigation arising between Parties, other than the European Commission, and +resulting from the interpretation of this License, will be subject to the +exclusive jurisdiction of the competent court where the Licensor resides or +conducts its primary business. + + + +## 15. Applicable Law + +This Licence shall be governed by the law of the European Union country where +the Licensor resides or has his registered office. + +This licence shall be governed by the Belgian law if: + +- a litigation arises between the European Commission, as a Licensor, and any +- Licensee; the Licensor, other than the European Commission, has no residence +- or registered office inside a European Union country. + + + +## Appendix + + + +“Compatible Licences” according to article 5 EUPL are: + + +- GNU General Public License (GNU GPL) v. 2 + +- Open Software License (OSL) v. 2.1, v. 3.0 + +- Common Public License v. 1.0 + +- Eclipse Public License v. 1.0 + +- Cecill v. 2.0 + diff --git a/geoportal-client/README.md b/geoportal-client/README.md new file mode 100644 index 0000000..b80f923 --- /dev/null +++ b/geoportal-client/README.md @@ -0,0 +1,50 @@ +GeoPortal - Client +-------------------------------------------------- + +GeoPortal - Client is the java library wrapping Geoportal REST Service + +## Built with +* [Maven](https://maven.apache.org/) - Dependency Management + +## Documentation + +Documentation can be found [here](https://gcube.wiki.gcube-system.org/gcube/GeoPortal). + +## Change log + +See [CHANGELOG.md](CHANGELOG.md). + +## License + +This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LICENSE.md) file for details. + +## About the gCube Framework +This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an +open-source software toolkit used for building and operating Hybrid Data +Infrastructures enabling the dynamic deployment of Virtual Research Environments +by favouring the realisation of reuse oriented policies. + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - DILIGENT (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - D4Science (grant no. 212488); + - D4Science-II (grant no.239019); + - ENVRI (grant no. 283465); + - iMarine(grant no. 283644); + - EUBrazilOpenBio (grant no. 288754). +- the H2020 research and innovation programme + - SoBigData (grant no. 654024); + - PARTHENOS (grant no. 654119); + - EGIEngage (grant no. 654142); + - ENVRIplus (grant no. 654182); + - BlueBRIDGE (grant no. 675680); + - PerformFish (grant no. 727610); + - AGINFRAplus (grant no. 731001); + - DESIRA (grant no. 818194); + - ARIADNEplus (grant no. 823914); + - RISIS2 (grant no. 824091); + + + diff --git a/geoportal-client/pom.xml b/geoportal-client/pom.xml new file mode 100644 index 0000000..bfbc227 --- /dev/null +++ b/geoportal-client/pom.xml @@ -0,0 +1,139 @@ + + 4.0.0 + org.gcube.application + geoportal-client + 1.0.5-SNAPSHOT + Geoportal Client + + + + org.gcube.application.cms + gcube-cms-suite + 1.0.0-SNAPSHOT + + + + + https://code-repo.d4science.org/gCubeSystem + 1.0 + + + + + + + scm:git:${gitBaseUrl}/${project.artifactId}.git + scm:git:${gitBaseUrl}/${project.artifactId}.git + ${gitBaseUrl}/${project.artifactId}.git + + + + + + + + org.gcube.distribution + gcube-bom + 2.0.1 + pom + import + + + org.gcube.distribution + gcube-smartgears-bom + 2.1.0 + pom + test + + + + + + + + + org.projectlombok + lombok + 1.14.8 + + + + org.gcube.application + geoportal-common + [1.0.0,2.0.0) + + + + + org.gcube.core + common-fw-clients + + + + org.gcube.core + common-generic-clients + + + + + + + org.gcube.common + common-jaxrs-client + [1.0.0,2.0.0) + + + + + junit + junit + test + 4.11 + + + + + org.gcube.common + storagehub-client-library + [1.0.0,2.0.0-SNAPSHOT) + + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.8.8 + + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + + ch.qos.logback + logback-classic + test + + + + com.opencsv + opencsv + 3.9 + test + + + + com.github.cliftonlabs + json-simple + 3.1.0 + test + + + + + + \ No newline at end of file diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultMongoConcessioni.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultMongoConcessioni.java new file mode 100644 index 0000000..c520d58 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultMongoConcessioni.java @@ -0,0 +1,218 @@ +package org.gcube.application.geoportal.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.model.rest.Configuration; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.common.clients.Call; +import org.gcube.common.clients.delegates.ProxyDelegate; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.rmi.RemoteException; +import java.util.Iterator; + +@RequiredArgsConstructor +@Slf4j +public class DefaultMongoConcessioni implements MongoConcessioni{ + + @NonNull + private final ProxyDelegate delegate; + + private final ObjectMapper mapper=Serialization.mapper; + + @Override + public Concessione createNew(Concessione c) throws Exception { + log.debug("Serializing {} ",c); + final String serialized=mapper.writeValueAsString(c); + Call call= endpoint -> { + Response resp= endpoint.request(MediaType.APPLICATION_JSON).post(Entity.entity(serialized, MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.info("Registered {}",toReturn); + return toReturn; + } + + @Override + public void deleteById(String id) throws Exception { + deleteById(id,false); + } + + @Override + public void deleteById(String id,Boolean force) throws Exception { + log.debug("Deleting by id {}",id); + delegate.make((Call) endpoint -> { + check(endpoint.path(id).queryParam(InterfaceConstants.Parameters.FORCE,force).request(MediaType.APPLICATION_JSON).delete(),null); + log.info("Deleted by id {}",id); + return null; + }); + } + + @Override + public Concessione getById(String id) throws Exception { + log.debug("Loading by id {}",id); + Call call= endpoint -> { + Response resp= endpoint.path(id).request(MediaType.APPLICATION_JSON).get(); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.debug("Loaded {}",toReturn); + return toReturn; + } + + @Override + public Iterator getList() throws Exception { + log.debug("Getting list"); + Call> call= endpoint -> mapper.readerFor(Concessione.class).readValues( + endpoint.request(MediaType.APPLICATION_JSON).get(InputStream.class)); + return delegate.make(call); + } + + @Override + public Concessione publish(String id) throws Exception { + log.info("Publishing {} ",id); + Call call= endpoint -> { + Response resp= endpoint.path(InterfaceConstants.Methods.PUBLISH_PATH). + path(id).request(MediaType.APPLICATION_JSON).put(Entity.entity(id, MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.debug("Published {} ",toReturn); + return toReturn; + } + + @Override + public void unPublish(String id) throws Exception { + log.info("Publishing {} ",id); + + Call call= endpoint -> { + Response resp= endpoint.path(InterfaceConstants.Methods.PUBLISH_PATH). + path(id).request(MediaType.APPLICATION_JSON).delete(); + check(resp,null); + return null; + }; + delegate.make(call); + log.debug("UnPublished {} ",id); + } + + @Override + public Configuration getCurrentConfiguration() throws Exception { + return delegate.make(webTarget -> check(webTarget.path(InterfaceConstants.Methods.CONFIGURATION_PATH). + request(MediaType.APPLICATION_JSON).get(),Configuration.class)); + } + + @Override + public Iterator search(String s) throws Exception { + log.debug("Searching for {}",s); + Call> call= endpoint -> { + Response resp =endpoint.path(InterfaceConstants.Methods.SEARCH_PATH).request(MediaType.APPLICATION_JSON). + post(Entity.entity(s,MediaType.APPLICATION_JSON)); + return checkCollection(resp,Concessione.class); + }; + return delegate.make(call); + } + + @Override + public Iterator query(QueryRequest queryRequest) throws Exception { + log.debug("Querying for {}",queryRequest); + return queryForType(queryRequest,Concessione.class); + } + + @Override + public String queryForJSON(QueryRequest queryRequest) throws Exception { + return null; + } + + @Override + public Iterator queryForType(QueryRequest queryRequest, Class aClass) throws Exception { + log.debug("Querying for {}",queryRequest); + Call> call= endpoint -> { + Response resp =endpoint.path(InterfaceConstants.Methods.QUERY_PATH).request(MediaType.APPLICATION_JSON). + post(Entity.entity(queryRequest,MediaType.APPLICATION_JSON)); + return checkCollection(resp,aClass); + }; + return delegate.make(call); + } + + + @Override + public Concessione registerFileSet(String id, AddSectionToConcessioneRequest request) throws Exception { + log.info("Registering {} in {}",request,id); + Call call= endpoint -> { + Response resp= endpoint.path(InterfaceConstants.Methods.REGISTER_FILES_PATH). + path(id).request(MediaType.APPLICATION_JSON).post(Entity.entity(mapper.writeValueAsString(request), + MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.debug("Registered Fileset {} result is {} ",request,toReturn); + return toReturn; + } + + @Override + public Concessione cleanFileSet(String id, String path)throws Exception{ + log.info("Cleaning Fileset at {} in {}",path,id); + Call call= endpoint -> { + Response resp= endpoint.path(InterfaceConstants.Methods.DELETE_FILES_PATH). + path(id).request(MediaType.APPLICATION_JSON).post(Entity.entity(path, + MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.debug("Cleaned path {} result {} ",path,toReturn); + return toReturn; + } + + @Override + public Concessione update(String id, String jsonUpdate) throws Exception { + throw new Exception("To implement"); + } + + @Override + public Concessione replace(Concessione replacement) throws Exception { + log.info("Replacing {}",replacement); + Call call= endpoint -> { + Response resp= endpoint. + request(MediaType.APPLICATION_JSON). + put(Entity.entity(mapper.writeValueAsString(replacement), + MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + }; + Concessione toReturn = delegate.make(call); + log.debug("Reloaded {} ",toReturn); + return toReturn; + } + + + protected static T check(Response resp, Class clazz) throws IOException { + String resString=resp.readEntity(String.class); + if(resp.getStatus()<200||resp.getStatus()>=300) + throw new RemoteException("RESP STATUS IS "+resp.getStatus()+". Message : "+resString); + System.out.println("Resp String is "+resString); + if(clazz!=null) + return Serialization.read(resString, clazz); + else return null; + } + + protected static Iterator checkCollection(Response resp, Class clazz) throws IOException { + if(resp.getStatus()<200||resp.getStatus()>=300) + throw new RemoteException("RESP STATUS IS "+resp.getStatus()+". Message : "+resp.readEntity(String.class)); + if(clazz!=null) + return Serialization.readCollection((InputStream) resp.getEntity(), clazz); + else return null; + } + + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultProjects.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultProjects.java new file mode 100644 index 0000000..37415f2 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/DefaultProjects.java @@ -0,0 +1,93 @@ +package org.gcube.application.geoportal.client; + +import java.rmi.RemoteException; +import java.util.Iterator; + +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; + +import org.gcube.application.geoportal.common.model.project.Project; +import org.gcube.application.geoportal.common.rest.ProjectsI; +import org.gcube.common.clients.Call; +import org.gcube.common.clients.delegates.ProxyDelegate; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DefaultProjects implements ProjectsI{ + + @NonNull + private final ProxyDelegate delegate; + + @Override + public void deleteById(String profileId, String projectId) throws RemoteException { + deleteById(profileId,projectId,false); + } + @Override + public void deleteById(String profileId, String projectId,Boolean force) throws RemoteException { +// Call call= new Call() { +// +// @Override +// public String call(WebTarget endpoint) throws Exception { +// endpoint.path(profileId).path(projectId).request(MediaType.APPLICATION_JSON).delete(); +// } +// }; +// try{ +// delegate.make(call); +// }catch(Exception e) { +//// throw new RemoteException(e); +// } + } + + @Override + public Iterator getAll() throws RemoteException { + Call> call=new Call>(){ + @Override + public Iterator call(WebTarget endpoint) throws Exception { + throw new Exception("Client method not ready"); + } + }; + try{ + return delegate.make(call); + }catch(Exception e) { + throw new RemoteException(e.getMessage()); + } + } + + @Override + public Iterator getByFilter(String filter) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterator getByFilter(String filter, String profileId) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Project getById(String profileId, String id) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterator getByProfile(String profileId) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Project registrNew(String profileId, String jsonDocument) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Project update(String profileId, String projectId, String jsonDocument) throws RemoteException { + // TODO Auto-generated method stub + return null; + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoPortalClient.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoPortalClient.java new file mode 100644 index 0000000..f3a0a83 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoPortalClient.java @@ -0,0 +1,8 @@ +package org.gcube.application.geoportal.client; + +public class GeoPortalClient { + + + + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoportalAbstractPlugin.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoportalAbstractPlugin.java new file mode 100644 index 0000000..abbe88d --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/GeoportalAbstractPlugin.java @@ -0,0 +1,72 @@ +package org.gcube.application.geoportal.client; + +import javax.ws.rs.client.WebTarget; + +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.client.legacy.ConcessioniPlugin; +import org.gcube.application.geoportal.client.legacy.MongoConcessioniPlugin; +import org.gcube.application.geoportal.client.legacy.StatefulMongoConcessioni; +import org.gcube.application.geoportal.client.legacy.StatefulMongoConcessioniPlugin; +import org.gcube.application.geoportal.common.rest.ConcessioniI; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.common.clients.Plugin; +import org.gcube.common.clients.ProxyBuilder; +import org.gcube.common.clients.ProxyBuilderImpl; +import org.gcube.common.clients.config.ProxyConfig; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public abstract class GeoportalAbstractPlugin implements Plugin{ + + + + private static ConcessioniPlugin concessioni_plugin=new ConcessioniPlugin(); + + private static MongoConcessioniPlugin mongo_concessioni_plugin=new MongoConcessioniPlugin(); + + private static StatefulMongoConcessioniPlugin stateful_mongo_concessioni_plugin=new StatefulMongoConcessioniPlugin(); + + public static ProxyBuilder concessioni() { + return new ProxyBuilderImpl(concessioni_plugin); + } + + public static ProxyBuilder mongoConcessioni(){ + return new ProxyBuilderImpl(mongo_concessioni_plugin); + } + + public static ProxyBuilder statefulMongoConcessioni(){ + return new ProxyBuilderImpl(stateful_mongo_concessioni_plugin); + } + + + @Override + public Exception convert(Exception fault, ProxyConfig config) { + return fault; + } + + + @Override + public String name() { + return InterfaceConstants.APPLICATION_BASE_PATH+InterfaceConstants.APPLICATION_PATH; + } + + @Override + public String namespace() { + return InterfaceConstants.NAMESPACE; + } + + @Override + public String serviceClass() { + return InterfaceConstants.SERVICE_CLASS; + } + + @Override + public String serviceName() { + return InterfaceConstants.SERVICE_NAME; + } + + + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/ProjectsPlugin.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/ProjectsPlugin.java new file mode 100644 index 0000000..aaa17a5 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/ProjectsPlugin.java @@ -0,0 +1,41 @@ +package org.gcube.application.geoportal.client; + +import javax.ws.rs.client.WebTarget; +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMResult; +import javax.xml.ws.EndpointReference; + +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.ProjectsI; +import org.gcube.common.calls.jaxrs.GcubeService; +import org.gcube.common.calls.jaxrs.TargetFactory; +import org.gcube.common.clients.config.ProxyConfig; +import org.gcube.common.clients.delegates.ProxyDelegate; +import org.w3c.dom.Node; + +public class ProjectsPlugin extends GeoportalAbstractPlugin{ + + + @Override + public Exception convert(Exception fault, ProxyConfig config) { + return fault; + } + + @Override + public ProjectsI newProxy(ProxyDelegate delegate) { + return new DefaultProjects(delegate); + } + + @Override + public WebTarget resolve(EndpointReference address, ProxyConfig config) throws Exception { + DOMResult result = new DOMResult(); + address.writeTo(result); + Node node =result.getNode(); + Node child=node.getFirstChild(); + String addressString = child.getTextContent(); + GcubeService service = GcubeService.service(). + withName(new QName(InterfaceConstants.NAMESPACE,InterfaceConstants.Methods.PROJECTS)). + andPath(InterfaceConstants.Methods.PROJECTS); + return TargetFactory.stubFor(service).at(addressString); + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniManager.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniManager.java new file mode 100644 index 0000000..ea840ca --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/ConcessioniManager.java @@ -0,0 +1,136 @@ +package org.gcube.application.geoportal.client.legacy; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.concessioni; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.gcube.application.geoportal.common.model.legacy.report.PublicationReport; +import org.gcube.application.geoportal.common.rest.ConcessioniI; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ConcessioniManager{ + + private static ObjectMapper mapper = new ObjectMapper(); + + private static ObjectReader concessioniReader=null; + private static ObjectReader collectionReader=null; + + static { + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); + mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false); + mapper.setSerializationInclusion(Include.NON_NULL); + // mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + mapper.registerModule(new JavaTimeModule()); + mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + + + concessioniReader=mapper.readerFor(Concessione.class); + collectionReader=mapper.readerFor(Collection.class); + } + + private ConcessioniI service; + + private static enum ImplementationType{ + + } + + public ConcessioniManager() { + service=concessioni().build(); + } + + + + + + public Concessione getById(String id) throws Exception { + log.info("Reading by ID "+id); + try { + String result=service.readById(id); + log.debug("Reading json object : "+result); + return concessioniReader.readValue(result); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + public static String toJson(Object obj) throws JsonProcessingException { + return mapper.writeValueAsString(obj); + } + + + public ArrayList getList() throws Exception{ + log.info("Getting list"); + try { + String result=service.getAll(); + log.debug("Reading json object : "+result); + + + Collection coll=collectionReader.readValue(result); +// JsonArray array=Json.createReader(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))).readArray(); + ArrayList toReturn=new ArrayList(); + +// MappingIterator it=concessioniReader.readValues(result); +// while(it.hasNext()) +// toReturn.add(it.next()); + + for(String s: coll) { + toReturn.add(concessioniReader.readValue(s)); + } + + +// for(int i=0;i{ + + + + @Override + public Exception convert(Exception fault, ProxyConfig config) { + return fault; + } + + @Override + public ConcessioniI newProxy(ProxyDelegate delegate) { + return new DefaultConcessioni(delegate); + } + + + @Override + public WebTarget resolve(EndpointReference address, ProxyConfig config) throws Exception { + DOMResult result = new DOMResult(); + address.writeTo(result); + Node node =result.getNode(); + Node child=node.getFirstChild(); + String addressString = child.getTextContent(); + GcubeService service = GcubeService.service(). + withName(new QName(InterfaceConstants.NAMESPACE,InterfaceConstants.Methods.CONCESSIONI)). + andPath(InterfaceConstants.Methods.CONCESSIONI); + return TargetFactory.stubFor(service).at(addressString); + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/DefaultConcessioni.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/DefaultConcessioni.java new file mode 100644 index 0000000..1bf3a42 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/DefaultConcessioni.java @@ -0,0 +1,71 @@ +package org.gcube.application.geoportal.client.legacy; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; + +import org.gcube.application.geoportal.common.rest.ConcessioniI; +import org.gcube.common.clients.Call; +import org.gcube.common.clients.delegates.ProxyDelegate; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DefaultConcessioni implements ConcessioniI{ + + @NonNull + private final ProxyDelegate delegate; + + @Override + public String create(final String toCreate) throws Exception { + Call call= new Call(){ + @Override + public String call(WebTarget endpoint) throws Exception { + return endpoint.request(MediaType.APPLICATION_JSON).put(Entity.entity(toCreate, MediaType.APPLICATION_JSON)).readEntity(String.class); + } + }; + return delegate.make(call); + } + + + @Override + public String readById(final String readById) throws Exception { + Call call= new Call(){ + @Override + public String call(WebTarget endpoint) throws Exception { + return endpoint.path(readById).request(MediaType.APPLICATION_JSON).get().readEntity(String.class); + } + }; + return delegate.make(call); + } + + @Override + public String getAll() throws Exception { + Call call= new Call(){ + @Override + public String call(WebTarget endpoint) throws Exception { + return endpoint.request(MediaType.APPLICATION_JSON).get().readEntity(String.class); + } + }; + return delegate.make(call); + } + +// @Override +// public String update(final String id, final String updated) throws Exception { +// Call call= new Call(){ +// @Override +// public String call(WebTarget endpoint) throws Exception { +// return endpoint.path(id).request(MediaType.APPLICATION_JSON).put(Entity.entity(updated, MediaType.APPLICATION_JSON)).readEntity(String.class); +// } +// }; +// return delegate.make(call); +// } + + + @Override + public String addSection(String arg0, String arg1) throws Exception { + // TODO Auto-generated method stub + return null; + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/MongoConcessioniPlugin.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/MongoConcessioniPlugin.java new file mode 100644 index 0000000..0b16b7d --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/MongoConcessioniPlugin.java @@ -0,0 +1,41 @@ +package org.gcube.application.geoportal.client.legacy; + +import javax.ws.rs.client.WebTarget; +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMResult; +import javax.xml.ws.EndpointReference; + +import org.gcube.application.geoportal.client.DefaultMongoConcessioni; +import org.gcube.application.geoportal.client.GeoportalAbstractPlugin; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.common.calls.jaxrs.GcubeService; +import org.gcube.common.calls.jaxrs.TargetFactory; +import org.gcube.common.clients.config.ProxyConfig; +import org.gcube.common.clients.delegates.ProxyDelegate; +import org.w3c.dom.Node; + +public class MongoConcessioniPlugin extends GeoportalAbstractPlugin{ + + + + @Override + public WebTarget resolve(EndpointReference address, ProxyConfig config) throws Exception { + DOMResult result = new DOMResult(); + address.writeTo(result); + Node node =result.getNode(); + Node child=node.getFirstChild(); + String addressString = child.getTextContent(); + GcubeService service = GcubeService.service(). + withName(new QName(InterfaceConstants.NAMESPACE,InterfaceConstants.Methods.MONGO_CONCESSIONI)). + andPath(InterfaceConstants.Methods.MONGO_CONCESSIONI); + return TargetFactory.stubFor(service).at(addressString); + } + + @Override + public MongoConcessioni newProxy(ProxyDelegate delegate) { + return new DefaultMongoConcessioni(delegate); + } + + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioni.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioni.java new file mode 100644 index 0000000..27d7e24 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioni.java @@ -0,0 +1,135 @@ +package org.gcube.application.geoportal.client.legacy; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +import javax.ws.rs.client.WebTarget; + +import org.gcube.application.geoportal.client.DefaultMongoConcessioni; +import org.gcube.application.geoportal.client.utils.FileSets; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.common.clients.delegates.ProxyDelegate; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; + + +public class StatefulMongoConcessioni extends DefaultMongoConcessioni implements ConcessioniManagerI{ + + + + public StatefulMongoConcessioni(ProxyDelegate delegate) { + super(delegate); + } + + private Concessione currentC=null; + private StorageUtils storage=new StorageUtils(); + + + // Override methods to handle state + @Override + public Concessione createNew(Concessione c) throws Exception { + currentC=super.createNew(c); + return currentC; + } + + @Override + public Concessione getById(String id) throws Exception { + currentC= super.getById(id); + return currentC; + } + + @Override + public Concessione publish() throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + currentC=super.publish(currentC.getMongo_id()); + return currentC; + } + + + @Override + public void delete() throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + super.deleteById(currentC.getMongo_id()); + currentC=null; + } + + @Override + public Concessione addImmagineRappresentativa(UploadedImage toAdd, TempFile f) throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + if(currentC.getImmaginiRappresentative()==null) currentC.setImmaginiRappresentative(new ArrayList()); + currentC.getImmaginiRappresentative().add(toAdd); + currentC=replace(currentC); + + + currentC=super.registerFileSet(currentC.getMongo_id(), + FileSets.build(Paths.imgByIndex(currentC.getImmaginiRappresentative().size()-1),f).getTheRequest()); + return currentC; + } + + @Override + public Concessione addPiantaFineScavo(LayerConcessione toAdd, TempFile... files) throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + if(currentC.getPianteFineScavo()==null) currentC.setPianteFineScavo(new ArrayList<>()); + currentC.getPianteFineScavo().add(toAdd); + currentC=replace(currentC); + + + currentC=super.registerFileSet(currentC.getMongo_id(), + FileSets.build(Paths.piantaByIndex(currentC.getPianteFineScavo().size()-1),files).getTheRequest()); + return currentC; + } + + @Override + public Concessione setPosizionamento(LayerConcessione toSet, TempFile... files) throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + currentC.setPosizionamentoScavo(toSet); + currentC=replace(currentC); + + + currentC=super.registerFileSet(currentC.getMongo_id(), + FileSets.build(Paths.POSIZIONAMENTO,files).getTheRequest()); + return currentC; + } + + @Override + public Concessione setRelazioneScavo(RelazioneScavo toSet, TempFile... files) throws Exception { + if(currentC==null) throw new Exception("Invalid operation : current Concessione is null."); + currentC.setRelazioneScavo(toSet); + currentC=replace(currentC); + + + currentC=super.registerFileSet(currentC.getMongo_id(), + FileSets.build(Paths.RELAZIONE,files).getTheRequest()); + return currentC; + } + + + + @Override + public Concessione addImmagineRappresentativa(UploadedImage toAdd, InputStreamDescriptor f) throws Exception { + return addImmagineRappresentativa(toAdd,FileSets.asTemp(storage,f)); + } + + @Override + public Concessione addPiantaFineScavo(LayerConcessione toAdd, InputStreamDescriptor... files) throws Exception { + return addPiantaFineScavo(toAdd, FileSets.asTemp(storage,files)); + } + + @Override + public Concessione setPosizionamento(LayerConcessione toSet, InputStreamDescriptor... files) throws Exception { + return setPosizionamento(toSet,FileSets.asTemp(storage,files)); + } + + @Override + public Concessione setRelazioneScavo(RelazioneScavo toSet, InputStreamDescriptor f) throws Exception { + return setRelazioneScavo(toSet,FileSets.asTemp(storage,f)); + } + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioniPlugin.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioniPlugin.java new file mode 100644 index 0000000..e3df770 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/legacy/StatefulMongoConcessioniPlugin.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.client.legacy; + +import javax.ws.rs.client.WebTarget; +import javax.xml.namespace.QName; +import javax.xml.transform.dom.DOMResult; +import javax.xml.ws.EndpointReference; + +import org.gcube.application.geoportal.client.GeoportalAbstractPlugin; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.common.calls.jaxrs.GcubeService; +import org.gcube.common.calls.jaxrs.TargetFactory; +import org.gcube.common.clients.config.ProxyConfig; +import org.gcube.common.clients.delegates.ProxyDelegate; +import org.w3c.dom.Node; + +public class StatefulMongoConcessioniPlugin extends GeoportalAbstractPlugin{ + + + @Override + public WebTarget resolve(EndpointReference address, ProxyConfig config) throws Exception { + DOMResult result = new DOMResult(); + address.writeTo(result); + Node node =result.getNode(); + Node child=node.getFirstChild(); + String addressString = child.getTextContent(); + GcubeService service = GcubeService.service(). + withName(new QName(InterfaceConstants.NAMESPACE,InterfaceConstants.Methods.MONGO_CONCESSIONI)). + andPath(InterfaceConstants.Methods.MONGO_CONCESSIONI); + return TargetFactory.stubFor(service).at(addressString); + } + + @Override + public ConcessioniManagerI newProxy(ProxyDelegate delegate) { + return new StatefulMongoConcessioni(delegate); + } + + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/model/CommunicationException.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/model/CommunicationException.java new file mode 100644 index 0000000..fd34809 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/model/CommunicationException.java @@ -0,0 +1,36 @@ +package org.gcube.application.geoportal.client.model; + +import lombok.Data; + +@Data +public class CommunicationException extends Exception { + + private String remoteMessage; + private Integer responseHTTPCode; + + public CommunicationException() { + // TODO Auto-generated constructor stub + } + + public CommunicationException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public CommunicationException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public CommunicationException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public CommunicationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/FileSets.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/FileSets.java new file mode 100644 index 0000000..5698731 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/FileSets.java @@ -0,0 +1,86 @@ +package org.gcube.application.geoportal.client.utils; + +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +public class FileSets { + + public static class RequestBuilder { + AddSectionToConcessioneRequest theRequest=new AddSectionToConcessioneRequest(); + + public RequestBuilder add(TempFile... f){ + if(theRequest.getStreams()==null) + theRequest.setStreams(new ArrayList()); + for(TempFile temp: f ) + theRequest.getStreams().add(temp); + return this; + } + + public RequestBuilder add(TempFile f){ + if(theRequest.getStreams()==null) + theRequest.setStreams(new ArrayList()); + theRequest.getStreams().add(f); + return this; + } + + public RequestBuilder setPath(String path){ + theRequest.setDestinationPath(path); + return this; + } + + + + + + public AddSectionToConcessioneRequest getTheRequest(){return theRequest;} + } + + + public static RequestBuilder build(String path) { + return new RequestBuilder().setPath(path); + } + + public static RequestBuilder build(String path, TempFile...files) { + return new RequestBuilder().setPath(path).add(files); + } + + public static TempFile asTemp(StorageUtils storage,InputStreamDescriptor descriptor) throws RemoteBackendException, FileNotFoundException { + return storage.putOntoStorage(descriptor.getStream(), descriptor.getFilename()); + } + + public static TempFile[] asTemp(StorageUtils storage,InputStreamDescriptor... descriptors) throws RemoteBackendException, FileNotFoundException { + ArrayList toReturn=new ArrayList(); + for(InputStreamDescriptor desc:descriptors) + toReturn.add(storage.putOntoStorage(desc.getStream(), desc.getFilename())); + return toReturn.toArray(new TempFile[toReturn.size()]); + } + + public static AddSectionToConcessioneRequest prepareRequestFromFolder(StorageUtils storage, String path, File directory) throws FileNotFoundException { + + File[] children =directory.listFiles(); + System.out.println("Found "+children+ " files to push"); + InputStreamDescriptor[] iss=new InputStreamDescriptor[children.length]; + return prepareRequest(storage,path,children); + + } + + public static AddSectionToConcessioneRequest prepareRequest(StorageUtils storage, String path, File... toUpload) throws FileNotFoundException { + + FileSets.RequestBuilder builder = FileSets.build(path); + for (File f : toUpload) { + if(!f.isDirectory()) + builder.add(FileSets.asTemp(storage, new InputStreamDescriptor(new FileInputStream(f), f.getName()))); + } + return builder.getTheRequest(); + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Queries.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Queries.java new file mode 100644 index 0000000..83d9664 --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Queries.java @@ -0,0 +1,42 @@ +package org.gcube.application.geoportal.client.utils; + +import org.apache.commons.io.IOUtils; +import org.bson.Document; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; +import org.gcube.application.geoportal.common.utils.Files; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +public class Queries { + + + + + + public static QueryRequest parse(String json) throws IOException { + Document queryDocument=Document.parse(json); + QueryRequest req=new QueryRequest(); + if(queryDocument.containsKey("ordering")) + req.setOrdering(Serialization.read(((Document)queryDocument.get("ordering")).toJson(),QueryRequest.OrderedRequest.class)); + if(queryDocument.containsKey("paging")) + req.setPaging(Serialization.read(((Document)queryDocument.get("paging")).toJson(),QueryRequest.PagedRequest.class)); + req.setProjection(queryDocument.get("projection",Document.class)); + req.setFilter(queryDocument.get("filter",Document.class)); + + return req; + }; + + public static QueryRequest readPath(String jsonFilePath) throws IOException{ + return parse(Files.readFileAsString(jsonFilePath, Charset.defaultCharset())); + } + + public static QueryRequest readFile(File jsonFile)throws IOException{ + return parse(Files.readFileAsString(jsonFile.getAbsolutePath(), Charset.defaultCharset())); + } + public static QueryRequest read(InputStream is)throws IOException{ + return parse(IOUtils.toString(is)); + } +} diff --git a/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Serialization.java b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Serialization.java new file mode 100644 index 0000000..e4f45ef --- /dev/null +++ b/geoportal-client/src/main/java/org/gcube/application/geoportal/client/utils/Serialization.java @@ -0,0 +1,41 @@ +package org.gcube.application.geoportal.client.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.time.format.DateTimeFormatter; +import java.util.Iterator; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public class Serialization { + +public static final DateTimeFormatter FULL_FORMATTER=DateTimeFormatter.ofPattern("uuuuMMdd_HH-mm-ss"); + + public static ObjectMapper mapper; + + static { + mapper=new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); + mapper.registerModule(new JavaTimeModule()); + } + + public static T read(String jsonString,Class clazz) throws JsonProcessingException, IOException { + return mapper.readerFor(clazz).readValue(jsonString); + } + + + public static String write(Object obj) throws JsonProcessingException, IOException { + return mapper.writeValueAsString(obj); + } + + public static Iterator readCollection(String jsonString, Class clazz) throws IOException { + return mapper.readerFor(clazz).readValues(jsonString); + } + + public static Iterator readCollection(InputStream is, Class clazz) throws IOException { + return mapper.readerFor(clazz).readValues(is); + } +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/StorageTests.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/StorageTests.java new file mode 100644 index 0000000..5942bcb --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/StorageTests.java @@ -0,0 +1,34 @@ +package org.gcube.application.geoportal; + +import org.gcube.application.geoportal.clients.TokenSetter; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +public class StorageTests { + + public static void main(String[] args) throws FileNotFoundException { + FileInputStream fis =new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")); + String filename= "relazione.pdf"; + + String sourceContext="/d4science.research-infrastructures.eu"; + String targetContext="/pred4s/preprod/preVRE"; + + TokenSetter.set(sourceContext); + + StorageHubClient sgClient=new StorageHubClient(); + + TokenSetter.set(targetContext); + + TempFile file= new StorageUtils().putOntoStorage(fis,filename); + + + System.out.println(new StorageUtils().getUrlById(file.getId())); + + } + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/BasicVreTests.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/BasicVreTests.java new file mode 100644 index 0000000..d6cb21b --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/BasicVreTests.java @@ -0,0 +1,14 @@ +package org.gcube.application.geoportal.clients; + +import org.junit.BeforeClass; + +public class BasicVreTests { + + @BeforeClass + public static void setScope(){ +// TokenSetter.set("/gcube/devNext/NextNext"); + TokenSetter.set("/gcube/devsec/devVRE"); + } + + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatefulClientTests.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatefulClientTests.java new file mode 100644 index 0000000..e46aa7e --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatefulClientTests.java @@ -0,0 +1,113 @@ +package org.gcube.application.geoportal.clients; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.statefulMongoConcessioni; +import static org.junit.Assert.*; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.common.model.TestModel; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; +import org.junit.Test; + + +public class StatefulClientTests extends BasicVreTests{ + + + + private Concessione publishNew() throws Exception { + ConcessioniManagerI manager=statefulMongoConcessioni().build(); + StorageUtils storage=new StorageUtils(); + + + Concessione toRegister= TestModel.prepareEmptyConcessione(); + toRegister.setNome("Mock module"); + manager.createNew(toRegister); + + UploadedImage toRegisterImg=TestModel.prepareConcessione().getImmaginiRappresentative().get(0); + + // TEMP Files are hosted in INFRASTRUCTURE's VOLATILE AREA + TempFile toUpload=storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/immagine.png")), "immagine.png"); + manager.addImmagineRappresentativa(toRegisterImg, toUpload); + + //Alternative Method + InputStreamDescriptor isDesc=new InputStreamDescriptor(new FileInputStream(Files.getFileFromResources("concessioni/immagine.png")), "immagine.png"); + manager.addImmagineRappresentativa(toRegisterImg, isDesc); + + + + //Relazione + manager.setRelazioneScavo(TestModel.prepareConcessione().getRelazioneScavo(), + storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione_it.pdf"), + storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione_en.pdf")); + + // Posizionamento scavo + manager.setPosizionamento(TestModel.prepareConcessione().getPosizionamentoScavo(), + storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/pos.shp")), "pos.shp")); + + // Piante + manager.addPiantaFineScavo(TestModel.prepareConcessione().getPianteFineScavo().get(0), + storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/pianta.shp")), "pianta.shp"), + storage.putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/pianta.shx")), "pianta.shx")); + + return manager.publish(); + } + + + + @Test + public void testRegisterNew() throws RemoteBackendException, FileNotFoundException, Exception { + Concessione published=publishNew(); + + + // VARIOUS CHECKS + assertNotNull(published.getReport()); + assertEquals(published.getReport().getStatus(),ValidationStatus.PASSED); + + assertEquals(published.getImmaginiRappresentative().size(),2); + assertEquals(published.getPianteFineScavo().size(),1); + assertNotNull(published.getPosizionamentoScavo().getWmsLink()); + for(LayerConcessione l : published.getPianteFineScavo()) + assertNotNull(l.getWmsLink()); + assertNotNull(published.getCentroidLat()); + assertNotNull(published.getCentroidLong()); + + System.out.println(Serialization.write(published)); + System.out.println(Serialization.write(published.getReport())); + } + + @Test + public void delete() throws Exception { + ConcessioniManagerI manager=statefulMongoConcessioni().build(); + StorageUtils storage=new StorageUtils(); + + manager.createNew(TestModel.prepareEmptyConcessione()); + + manager.delete(); + } + + @Test + public void replace() { + + } + + @Test + public void getById() { + + } + + @Test + public void list() { + + } +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatelessClientTests.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatelessClientTests.java new file mode 100644 index 0000000..b01957a --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/StatelessClientTests.java @@ -0,0 +1,216 @@ +package org.gcube.application.geoportal.clients; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.mongoConcessioni; +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.statefulMongoConcessioni; +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicLong; + +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.client.utils.Queries; +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.common.model.TestModel; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.junit.Test; + +public class StatelessClientTests extends BasicVreTests{ + + + private MongoConcessioni client=mongoConcessioni().build(); + + @Test + public void testCreateNew() throws Exception { + Concessione c= client.createNew(TestModel.prepareEmptyConcessione()); + assertNotNull(c); + assertNotNull(c.getMongo_id()); + } + + @Test + public void testDeleteById() throws Exception { + Concessione c= client.createNew(TestModel.prepareEmptyConcessione()); + client.deleteById(c.getMongo_id()); + } + + @Test + public void testList() throws Exception { + final AtomicLong counter=new AtomicLong(); + long before=System.currentTimeMillis(); + client.getList().forEachRemaining((Concessione c)-> {counter.addAndGet(1);}); + System.out.println("Loaded "+counter+" in "+(System.currentTimeMillis()-before)+" ms"); + } + + @Test + public void testReplace() throws Exception { + Concessione c= client.createNew(TestModel.prepareEmptyConcessione()); + + String title="My new shiny Title"; + c.setNome(title); + Concessione c1=client.replace(c); + assertEquals(title, c1.getNome()); + } + + @Test + public void testUploadFileSet() throws Exception { + Concessione c= client.createNew(TestModel.prepareConcessione()); + assertNotNull(c.getRelazioneScavo()); + AddSectionToConcessioneRequest request= + new AddSectionToConcessioneRequest(Paths.RELAZIONE, + Collections.singletonList(new StorageUtils().putOntoStorage( + new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione.pdf"))); + + c= client.registerFileSet(c.getMongo_id(), request); + + assertNotNull(c.getRelazioneScavo().getActualContent()); + assertNotNull(c.getRelazioneScavo().getActualContent().get(0)); + } + + @Test + public void testPublsh() throws Exception { + publish(true); + } + + + public Concessione publish(Boolean verify) throws Exception { + return publish(verify,client); + } + + + + public static Concessione publish(Boolean verify,MongoConcessioni client) throws Exception { + Concessione c= client.createNew(TestModel.prepareConcessione()); + + String mongoId=c.getMongo_id(); + + AddSectionToConcessioneRequest request= + new AddSectionToConcessioneRequest(Paths.RELAZIONE, + Collections.singletonList(new StorageUtils().putOntoStorage( + new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione.pdf"))); + + client.registerFileSet(mongoId, request); + + request= + new AddSectionToConcessioneRequest(Paths.imgByIndex(0), + Collections.singletonList(new StorageUtils().putOntoStorage( + new FileInputStream(Files.getFileFromResources("concessioni/immagine.png")), "immagine.png"))); + + client.registerFileSet(mongoId, request); + + + request= + new AddSectionToConcessioneRequest(Paths.POSIZIONAMENTO, + Collections.singletonList(new StorageUtils().putOntoStorage( + new FileInputStream(Files.getFileFromResources("concessioni/pos.shp")), "pos.shp"))); + + client.registerFileSet(mongoId, request); + + request= + new AddSectionToConcessioneRequest(Paths.piantaByIndex(0), + Collections.singletonList(new StorageUtils().putOntoStorage( + new FileInputStream(Files.getFileFromResources("concessioni/pianta.shp")), "pianta.shp"))); + + client.registerFileSet(mongoId, request); + + + c=client.publish(mongoId); + + if(verify) + assertTrue(c.getReport().getStatus().equals(ValidationStatus.PASSED)); + + return c; + } + + + @Test + public void getConfiguration() throws Exception { + System.out.println(client.getCurrentConfiguration()); + } + + @Test + public void searches() throws Exception { + for(File filterFile:new File("src/test/resources/concessioni/filters").listFiles()) { + String query=Files.readFileAsString(filterFile.getAbsolutePath(), Charset.defaultCharset()); + System.out.println("Count for "+filterFile.getName()+"\t"+ count(client.search(query))); + } + } + + + @Test + public void query() throws Exception { + // No Transformation + System.out.print("First Registered \t"); + Iterator queriedDocuments=client.query( + Queries.readPath("src/test/resources/concessioni/queries/firstRegistered.json")); + // Expected one result + assertTrue(count(queriedDocuments)==1); + + + + System.out.print("Last Registered \t"); + // Expected one result + queriedDocuments=client.query( + Queries.readPath("src/test/resources/concessioni/queries/lastRegistered.json")); + assertTrue(count(queriedDocuments)==1); + + queriedDocuments.forEachRemaining((Concessione c)->{System.out.println(c.getNome());}); + + + // Transformations + System.out.println( + client.queryForJSON( + Queries.readPath("src/test/resources/concessioni/queries/lastNameRegisteredByFabio.json"))); + + System.out.println( + client.queryForJSON( + Queries.readPath("src/test/resources/concessioni/queries/publicationWarningMessages.json"))); + + + + + + +// String query=Files.readFileAsString(filterFile.getAbsolutePath(), Charset.defaultCharset()); +// System.out.println("Count for "+filterFile.getName()+"\t"+ count(client.search(query))); +// } + } + + @Test + public void testCleanFileSet() throws Exception { + + Concessione c=publish(false); + client.unPublish(c.getMongo_id()); + + //Precheck to be sure + assertFalse(c.getPosizionamentoScavo().getActualContent().isEmpty()); + assertFalse(c.getPianteFineScavo().get(0).getActualContent().isEmpty()); + + //Clear pos + c=client.cleanFileSet(c.getMongo_id(),Paths.POSIZIONAMENTO); + assertTrue(c.getPosizionamentoScavo().getActualContent().isEmpty()); + + //Clear pianta [0] + c=client.cleanFileSet(c.getMongo_id(),Paths.piantaByIndex(0)); + assertTrue(c.getPianteFineScavo().get(0).getActualContent().isEmpty()); + } + + + // UTILS + + public static long count(Iterator iterator){ + AtomicLong l=new AtomicLong(0); + iterator.forEachRemaining(el->{l.incrementAndGet();}); + return l.get(); + } + + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/TokenSetter.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/TokenSetter.java new file mode 100644 index 0000000..ae53e71 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/TokenSetter.java @@ -0,0 +1,35 @@ +package org.gcube.application.geoportal.clients; + +import java.util.Properties; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.scope.api.ScopeProvider; + +public class TokenSetter { + + + + private static Properties props=new Properties(); + + static{ + try { + props.load(TokenSetter.class.getResourceAsStream("/tokens.properties")); + } catch (Exception e) { + throw new RuntimeException("YOU NEED TO SET TOKEN FILE IN CONFIGURATION"); + } + } + + + public static void set(String scope){ + try{ + if(!props.containsKey(scope)) throw new RuntimeException("No token found for scope : "+scope); + SecurityTokenProvider.instance.set(props.getProperty(scope)); + }catch(Exception e){ + e.printStackTrace(System.err); + throw e; + } + ScopeProvider.instance.set(scope); + } + + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/legacy/ConcessioniTests.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/legacy/ConcessioniTests.java new file mode 100644 index 0000000..afb1726 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/clients/legacy/ConcessioniTests.java @@ -0,0 +1,81 @@ +package org.gcube.application.geoportal.clients.legacy; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicLong; + +import org.gcube.application.geoportal.client.legacy.ConcessioniManager; +import org.gcube.application.geoportal.clients.BasicVreTests; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.GeoServerContent; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.PersistedContent; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.junit.Test; + +public class ConcessioniTests extends BasicVreTests { + + static boolean STOP_ON_FAIL=true; + + + + @Test + public void readAll() throws Exception { + ConcessioniManager manager=new ConcessioniManager(); + ArrayList found=manager.getList(); + System.out.println("Found "+found.size()+" elements."); + final AtomicLong byId=new AtomicLong(0); + + final ArrayList validIDs=new ArrayList<>(); + + final ArrayList imgs=new ArrayList(); + final AtomicLong error=new AtomicLong(0); + + final ArrayList piante=new ArrayList(); + final ArrayList pos=new ArrayList(); + final ArrayList wmsLink=new ArrayList(); + + found.forEach((Concessione c )->{ + try { + manager.getById(c.getId()+""); + byId.incrementAndGet(); + if(c.validate().getStatus().equals(ValidationStatus.PASSED)) + validIDs.add(c.getId()); + if(c.getImmaginiRappresentative()!=null&&c.getImmaginiRappresentative().size()>0) + imgs.add(c.getId()); + if(c.getPianteFineScavo()!=null) + c.getPianteFineScavo().forEach((LayerConcessione l)->{ + if(l.getActualContent()!=null) + l.getActualContent().forEach((PersistedContent p)->{ + if(p instanceof GeoServerContent) piante.add(c.getId()); + }); + if(l.getWmsLink()!=null) wmsLink.add(c.getId()); + }); + + if(c.getPosizionamentoScavo()!=null) { + LayerConcessione l=c.getPosizionamentoScavo(); + l.getActualContent().forEach((PersistedContent p)->{ + if(p instanceof GeoServerContent) pos.add(c.getId()); + }); + if(l.getWmsLink()!=null) wmsLink.add(c.getId()); + } + }catch(Throwable t) { + error.incrementAndGet(); + if(STOP_ON_FAIL) { + throw new RuntimeException(t); + }else t.printStackTrace(System.err); + } + }); + System.out.println("Valid count "+validIDs.size()+ "Load BY ID : "+byId.get()+" Error : "+error.get()+" OUT OF "+found.size()); + System.out.println("Valid IDS "+validIDs); + System.out.println("With imgs : "+imgs); + System.out.println("With piante : "+piante); + System.out.println("With pos : "+pos); + System.out.println("With wmsLink : "+wmsLink); + } + + + + + + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/Serializations.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/Serializations.java new file mode 100644 index 0000000..993c926 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/Serializations.java @@ -0,0 +1,33 @@ +package org.gcube.application.geoportal.common.model; + +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicLong; + +public class Serializations { + + @Test + public void readOne(){} + + @Test + public void readMulti() throws IOException { + + Iterator it=Serialization.readCollection( + new FileInputStream(new File("src/test/resources/concessioni/ConcessioniList.json")), + Concessione.class); + AtomicLong l=new AtomicLong(0); + it.forEachRemaining(element->{l.incrementAndGet();}); + + + Assert.assertTrue(l.get()==4); + } + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java new file mode 100644 index 0000000..aac4795 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java @@ -0,0 +1,130 @@ +package org.gcube.application.geoportal.common.model; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.legacy.AccessPolicy; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; + + + +public class TestModel { + + + public static Concessione prepareEmptyConcessione() { + Concessione concessione=new Concessione(); + + // Generic fields + + // Concessione fields + + concessione.setNome("MONGO Italia, forse, ma su "); + concessione.setIntroduzione("This is my MONGO project"); + concessione.setDescrizioneContenuto("It contains this and that"); + + concessione.setAuthors(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + + concessione.setContributore("Contrib 1"); + concessione.setTitolari(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + concessione.setResponsabile("Someone"); + concessione.setEditore("Editore"); + + concessione.setFontiFinanziamento(Arrays.asList(new String[] {"Big pharma","Pentagon"})); + + + concessione.setSoggetto(Arrays.asList(new String[] {"Research Excavation","Archeology"})); + + + concessione.setDataInizioProgetto(LocalDateTime.now()); + concessione.setDataFineProgetto(LocalDateTime.now()); + + concessione.setLicenzaID("CC-BY"); + + concessione.setTitolareLicenza(Arrays.asList(new String[] {"Qualcun altro"})); + concessione.setTitolareCopyright(Arrays.asList(new String[] {"Chiedilo in giro"})); + + concessione.setParoleChiaveLibere(Arrays.asList(new String[] {"Robba","Stuff"})); + concessione.setParoleChiaveICCD(Arrays.asList(new String[] {"vattelapesca","somthing something"})); + + +// concessione.setCentroidLat(43.0); //N-S +// concessione.setCentroidLong(9.0); //E-W +// + return concessione; + } + + public static final Concessione setIds(Concessione c) { +// c.setMongo_id(rnd()); + c.getRelazioneScavo().setMongo_id(rnd()); + c.getPosizionamentoScavo().setMongo_id(rnd()); + c.getPianteFineScavo().forEach((LayerConcessione l)->{l.setMongo_id(rnd());}); + c.getImmaginiRappresentative().forEach((UploadedImage i)->{i.setMongo_id(rnd());}); + return c; + } + + public static final String rnd() { + return new ObjectId().toHexString(); + } + public static Concessione prepareConcessione() { + return prepareConcessione(4,2); + } + + public static Concessione prepareConcessione(int pianteCount ,int imgsCount) { + + Concessione concessione=prepareEmptyConcessione(); + + + + // Attachments + + // Relazione scavo + RelazioneScavo relScavo=new RelazioneScavo(); + + relScavo.setAbstractEng("simple abstract section"); + relScavo.setAbstractIta("semplice sezione abstract"); + relScavo.setResponsabili(concessione.getAuthors()); + + concessione.setRelazioneScavo(relScavo); + //Immagini rappresentative + ArrayList imgs=new ArrayList<>(); + for(int i=0;i piante=new ArrayList(); + for(int i=0;i toSkipIds=new ArrayList<>(); +// toSkipIds.add("6102c8dd02ad3d05b5f81df4"); +// toSkipIds.add("610415af02ad3d05b5f81ee3"); + + AtomicLong count=new AtomicLong(0); + AtomicLong nullCount=new AtomicLong(0); + AtomicLong errCount=new AtomicLong(0); + + + Iterator it=null; +// it=manager.getList(); + it=manager.search("{\"centroidLat\" : 0}"); + + it.forEachRemaining((Concessione c)->{ + try{ + String currentId=c.getMongo_id(); + if(currentId==null) { + System.out.println("ID IS NULL " + c); + nullCount.incrementAndGet(); + } + else + if(toSkipIds.contains(currentId)) + System.out.println("Skipping "+currentId); + else { + System.out.println("Deleting " + c.getMongo_id()); + manager.deleteById(c.getMongo_id(),true); + } + }catch(Throwable throwable){ + System.err.println(throwable); + errCount.incrementAndGet(); + }finally { + count.incrementAndGet(); + } + }); + + System.out.println("Done "+count.get()+" [null : "+nullCount.get()+", err : "+errCount.get()+"]"); + + } +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/EditFileSet.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/EditFileSet.java new file mode 100644 index 0000000..fdc5187 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/EditFileSet.java @@ -0,0 +1,67 @@ +package org.gcube.application.geoportal.usecases; + +import org.gcube.application.geoportal.client.utils.FileSets; +import org.gcube.application.geoportal.clients.TokenSetter; +import org.gcube.application.geoportal.common.model.legacy.AssociatedContent; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.application.geoportal.common.utils.StorageUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.mongoConcessioni; + +public class EditFileSet { + + public static void main(String[] args) throws Exception { + // params + String context="/gcube/devsec/devVRE"; + String publishOption="true"; + + String toUpdateId="6131f42502ad3d2580412da7"; + String toEditPath= Concessione.Paths.piantaByIndex(0); + String folderPath="/Users/fabioisti/Documents/GNA_Ferrandina_2020_inserimento/New Folder With Items/topografia/Piante"; + + //Check params + Boolean publish = Boolean.parseBoolean(publishOption); + File sourceFolder=new File(folderPath); + if(!sourceFolder.canRead()) throw new Exception("Cannot read from "+folderPath); + + System.out.println("!!!!!!! SETTING CONTEXT "+context); + TokenSetter.set(context); + + + //Prepare Fileset + System.out.println("Preparing request.."); + StorageUtils storage=new StorageUtils(); + AddSectionToConcessioneRequest request= FileSets.prepareRequestFromFolder(storage,toEditPath,sourceFolder); + + MongoConcessioni client=mongoConcessioni().build(); + + // Unpublish + System.out.println("Unpublishing "+toUpdateId); + client.unPublish(toUpdateId); + + // update Fileset + System.out.println("Removing old fileset.. "); + client.cleanFileSet(toUpdateId,toEditPath); + + System.out.println("Sending new Fileset .."); + Concessione result= client.registerFileSet(toUpdateId,request); + + if(publish) + result = client.publish(toUpdateId); + + System.out.println("Done "); + System.out.println("Result : "+result); + + + } + + + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Export.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Export.java new file mode 100644 index 0000000..4db84af --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Export.java @@ -0,0 +1,159 @@ +package org.gcube.application.geoportal.usecases; + +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.clients.TokenSetter; +import org.gcube.application.geoportal.common.model.legacy.*; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.statefulMongoConcessioni; + +@Slf4j +/* +Pushes concessioni JSON from folder to geoportal-service + */ +public class Export { + + + public static void main(String[] args) { + + File dir= new File("/Users/fabioisti/git/geoportal-client/import1628178107083"); +// String targetContext="/pred4s/preprod/preVRE"; + String targetContext="/gcube/devsec/devVRE"; + + + + ArrayList found=new ArrayList<>(); + for(File elementFolder:dir.listFiles()) + for(File jsonFile:elementFolder.listFiles((dir1, name) -> {return name.endsWith(".json");})) + try { + log.info("Reading "+jsonFile.getAbsolutePath()); + String json= Files.readFileAsString(jsonFile.getAbsolutePath(), Charset.defaultCharset()); + found.add(Serialization.read(json,Concessione.class)); + } catch (IOException e) { + e.printStackTrace(); + } + + System.out.println("Loaded "+found.size()+" elements from "+dir.getAbsolutePath()); + + TokenSetter.set(targetContext); + ConcessioniManagerI targetManager = statefulMongoConcessioni().build(); + StorageUtils storage = new StorageUtils(); + + AtomicLong count = new AtomicLong(0); + AtomicLong warnCount = new AtomicLong(0); + AtomicLong errCount = new AtomicLong(0); + + for (Concessione c : found) { + try { + log.info("Using {} {}",c.getNome(),c.getMongo_id()); + Concessione result = push(c, targetManager, new File(dir.getAbsolutePath(), + c.getId() + ""), storage); + if (!result.getReport().getStatus().equals(ValidationReport.ValidationStatus.PASSED)) + warnCount.incrementAndGet(); + } catch (Throwable throwable) { + System.err.println(throwable); + errCount.incrementAndGet(); + } finally { + count.incrementAndGet(); + } + } + + System.out.println("Done "+count.get()+" [warn : "+warnCount.get()+", err : "+errCount.get()+"]"); + } + + public static Concessione push(Concessione c, ConcessioniManagerI manager, File dir, StorageUtils storage) throws Exception { + // remove GIS references + LayerConcessione posizionamento= c.getPosizionamentoScavo(); + c.setPosizionamentoScavo(null); + + List piante=c.getPianteFineScavo(); + c.setPianteFineScavo(new ArrayList()); + + List imgs=c.getImmaginiRappresentative(); + c.setImmaginiRappresentative(new ArrayList()); + + List other=c.getGenericContent(); + c.setGenericContent(new ArrayList()); + + RelazioneScavo rel=c.getRelazioneScavo(); + c.setRelazioneScavo(null); + + + // remove source folder id + c.setFolderId(null); + c.setMongo_id(null); + + // PUSH PROJECT + manager.createNew(c); + + + // UPLOAD WOrKSPaCE content + // Relazione + removeContent(rel); + manager.setRelazioneScavo(rel, fromPath(dir.toPath().toAbsolutePath()+File.separator+"rel",storage)[0]); + + // Posizionamento + removeContent(posizionamento); + manager.setPosizionamento(posizionamento, + fromPath(dir.toPath().toAbsolutePath()+File.separator+"pos", storage)); + + + //Piante + for(int i=0; i toReturn=new ArrayList<>(); + for(File f : baseDir.toFile().listFiles()){ + toReturn.add(storage.putOntoStorage(new FileInputStream(f),f.getName())); + } + log.info("Loaded {} files from {} ",toReturn.size(),path); + return toReturn.toArray(new TempFile[toReturn.size()]); + } + + + private static void removeContent(AssociatedContent c){ c.setActualContent(new ArrayList<>());} + +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Import.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Import.java new file mode 100644 index 0000000..5dea760 --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/Import.java @@ -0,0 +1,110 @@ +package org.gcube.application.geoportal.usecases; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.gcube.application.geoportal.client.legacy.ConcessioniManager; +import org.gcube.application.geoportal.client.utils.Serialization; +import org.gcube.application.geoportal.clients.TokenSetter; +import org.gcube.application.geoportal.common.model.legacy.*; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; +import org.gcube.common.storagehub.model.exceptions.StorageHubException; + +import java.io.*; +import java.util.ArrayList; + +@Slf4j +/* +Imports Json concessioni into import + */ +public class Import { + + + public static void main(String[] args) throws Exception { + +// Path dir=Files.createTempDirectory(System.currentTimeMillis()+""); + + File dir=new File("import"+System.currentTimeMillis()); + dir.mkdirs(); + String sourceContext="/d4science.research-infrastructures.eu/D4OS/GeoNA-Prototype"; +// String sourceContext="/pred4s/preprod/preVRE"; + + + + // GET FOM SOURCE + TokenSetter.set(sourceContext); + + StorageHubClient sgClient=new StorageHubClient(); + + +// ConcessioniManagerI manager= statefulMongoConcessioni().build(); + + ConcessioniManager sourceManager = new ConcessioniManager(); + ArrayList found=new ArrayList<>(); + sourceManager.getList().forEach((Concessione c)->{ + try { + File currentFolder=new File (dir.toString(),c.getId()+""); + currentFolder.mkdirs(); + + //Load locally + // POSIZIONAMENTO + loadFiles(c.getPosizionamentoScavo(),new File(currentFolder,"pos"),sgClient); + // RELAZIONE + loadFiles(c.getRelazioneScavo(),new File(currentFolder,"rel"),sgClient); + // IMGs + for(int i=0;i toSkipIds=new ArrayList<>(); + + + AtomicLong count=new AtomicLong(0); + AtomicLong nullCount=new AtomicLong(0); + AtomicLong errCount=new AtomicLong(0); + manager.getList().forEachRemaining((Concessione c)->{ + try{ + String currentId=c.getMongo_id(); + if(currentId==null) { + System.out.println("ID IS NULL " + c); + nullCount.incrementAndGet(); + } + else + if(toSkipIds.contains(currentId)) + System.out.println("Skipping "+currentId); + else { + System.out.println("Upublishing " + c.getMongo_id()); + manager.unPublish(c.getMongo_id()); + + System.out.println("Republishing" + c.getMongo_id()); + manager.publish(c.getMongo_id()); + } + }catch(Throwable throwable){ + System.err.println(throwable); + errCount.incrementAndGet(); + }finally { + count.incrementAndGet(); + } + }); + + System.out.println("Done "+count.get()+" [null : "+nullCount.get()+", err : "+errCount.get()+"]"); + + } +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/UnpublishSingle.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/UnpublishSingle.java new file mode 100644 index 0000000..369ed4d --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/UnpublishSingle.java @@ -0,0 +1,17 @@ +package org.gcube.application.geoportal.usecases; + +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.clients.TokenSetter; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.statefulMongoConcessioni; + +public class UnpublishSingle { + + public static void main(String[] args) throws Exception { + TokenSetter.set("/gcube/devsec/devVRE"); + + ConcessioniManagerI manager=statefulMongoConcessioni().build(); + + manager.publish("610415af02ad3d05b5f81ee3"); + } +} diff --git a/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/mocks/MockFromFolder.java b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/mocks/MockFromFolder.java new file mode 100644 index 0000000..b7fb9ab --- /dev/null +++ b/geoportal-client/src/test/java/org/gcube/application/geoportal/usecases/mocks/MockFromFolder.java @@ -0,0 +1,171 @@ +package org.gcube.application.geoportal.usecases.mocks; + +import com.opencsv.CSVReader; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.client.legacy.ConcessioniManagerI; +import org.gcube.application.geoportal.client.utils.FileSets; +import org.gcube.application.geoportal.clients.StatelessClientTests; +import org.gcube.application.geoportal.clients.TokenSetter; +import org.gcube.application.geoportal.common.model.TestModel; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.InputStreamDescriptor; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.MongoConcessioni; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.contentmanagement.blobstorage.service.IClient; + +import java.io.*; +import java.lang.reflect.Array; +import java.util.*; + +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.mongoConcessioni; +import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.statefulMongoConcessioni; +import static org.junit.Assert.assertTrue; + +@Slf4j +public class MockFromFolder { + + public static void main(String[] args) throws Exception { + //PARAMS + String context="/gcube/devsec/devVRE"; + + //Concessioni 04-03 + //String packageBaseDir="/Users/fabioisti/Documents/Concessioni 04-03/"; + //String csvDescriptor="src/test/resources/concessioni/concessioni04-03.csv"; + + //DATASET_GNA_01 +// String packageBaseDir="/Users/fabioisti/Documents/DATASET_GNA_01"; +// String csvDescriptor="src/test/resources/concessioni/DATASET_GNA_01.csv"; + + //DATASET_GNA_02 + //String packageBaseDir="/Users/fabioisti/Documents/DATASET_GNA_02"; + //String csvDescriptor="src/test/resources/concessioni/DATASET_GNA_02.csv"; + + + // invio_08_02 +// String packageBaseDir="/Users/fabioisti/Documents/invio_08_05"; +// String csvDescriptor="src/test/resources/concessioni/invio_08_05.csv"; + + // concessioni 23_04 + String packageBaseDir="/Users/fabioisti/Documents/Concessioni_23_04"; + String csvDescriptor="src/test/resources/concessioni/concessioni_23_04.csv"; + + + + + TokenSetter.set(context); + + MongoConcessioni client=mongoConcessioni().build(); + StorageUtils storage=new StorageUtils(); + + + long publishedCount=0l; + long successcount=0l; + long entrycount=0l; + + //Parse CSV descriptor + File baseDir=new File(packageBaseDir); + ArrayList pushed=new ArrayList<>(); + + CSVReader reader = new CSVReader(new FileReader(csvDescriptor)); + String [] nextLine; + //reads one line at a time + while ((nextLine = reader.readNext()) != null) + { + entrycount++; + //Create new + String projectName = nextLine[0]; + String positionPath = nextLine[1]; + String piantePath = nextLine[2]; + + + try { + //NB raggruppa per file + Map.Entry> posSets = clusterizeFiles(positionPath, baseDir).entrySet().stream().findFirst().get(); + Map> pianteSets = clusterizeFiles(piantePath, baseDir); + + // Sometimes they are the same + if(positionPath.equals(piantePath)) + pianteSets.remove(posSets.getKey()); + + log.debug("Entry {} pos Size {} piante {} ",projectName,posSets.getValue().size(),pianteSets.size()); + + Concessione c = createMock(projectName, pianteSets, posSets.getValue(), client, storage); + + publishedCount++; + if (c.getReport().getStatus().equals(ValidationReport.ValidationStatus.PASSED)) + successcount++; + pushed.add(c); + }catch(Throwable t){ + System.err.println("Problematic entry "+projectName); + t.printStackTrace(System.err); + } + + } + + System.out.println("Done "+publishedCount+" [SUCCESS : "+successcount+"] \t OUT OF :"+entrycount+" entries"); + + pushed.forEach(c -> { + try{ + System.out.println(c.getNome()+"\t"+c.getMongo_id()+"\t"+c.getReport().getStatus()); + }catch(Throwable t){ + System.out.println(c.getNome()+"\t"+c.getMongo_id()+"\t PROBLEMATIC, NO REPORT"); + } + }); + } + + + + private static Map> clusterizeFiles(String basePath,File packageFolder) throws IOException { + log.debug("Clusterizing "+basePath); + + HashMap> toReturn = new HashMap<>(); + File baseDir=new File(packageFolder,basePath); + for(File shp:baseDir.listFiles((dir,name)->{return name.endsWith(".shp");})) { + String basename=shp.getName().substring(0,shp.getName().lastIndexOf(".")); + List fileset=new ArrayList<>(); + for (File shpSet : baseDir.listFiles((dir, name) -> {return name.startsWith(basename);})) + fileset.add(shpSet); + log.debug("SHP {} Set size {} ",basename,fileset.size()); + toReturn.put(basename,fileset); + } + return toReturn; + } + + private static Concessione createMock(String baseName,Map> piante, List pos, + MongoConcessioni client, StorageUtils storage) throws Exception { + + Concessione c=TestModel.prepareConcessione(piante.size(), 2); + c.setNome("Mock for "+baseName); + c= client.createNew(c); + String mongoId=c.getMongo_id(); + + // TEST DATA, DO NOT CARE + client.registerFileSet(mongoId, FileSets.prepareRequest(storage, + Concessione.Paths.RELAZIONE,new File ("src/test/resources/concessioni/relazione.pdf"))); + + client.registerFileSet(mongoId, FileSets.prepareRequest(storage, + Concessione.Paths.imgByIndex(0),new File("src/test/resources/concessioni/immagine.png"))); + + // POSIZIONAMENTO + + client.registerFileSet(mongoId, FileSets.prepareRequest(storage, + Concessione.Paths.POSIZIONAMENTO,pos.toArray(new File[pos.size()]))); + + Map.Entry>[] entries= piante.entrySet().toArray(new Map.Entry[0]); + for( int i= 0; i< piante.size();i++) + client.registerFileSet(mongoId, FileSets.prepareRequest(storage, + Concessione.Paths.piantaByIndex(i),entries[i].getValue().toArray(new File[0]))); + + c=client.publish(mongoId); + + System.out.println("@@@ Concessione "+c.getNome()+"\t STATUS : "+ c.getReport().getStatus()); + + return c; + } + +} diff --git a/geoportal-client/src/test/resources/concessioni/Concessione.json b/geoportal-client/src/test/resources/concessioni/Concessione.json new file mode 100644 index 0000000..612b021 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/Concessione.json @@ -0,0 +1,747 @@ +{ + "authors": [ + "Some one", + "Some, oneelse" + ], + "centroidLat": 43.0, + "centroidLong": 9.0, + "contributore": "Contrib 1", + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "creationUser": "NO NAME", + "dataFineProgetto": [ + 2020, + 11, + 26, + 13, + 14, + 17, + 6000000 + ], + "dataInizioProgetto": [ + 2020, + 11, + 26, + 13, + 14, + 16, + 994000000 + ], + "descrizioneContenuto": "It contains this and that", + "editore": "Editore", + "folderId": "5f14252f-55df-4c9a-94d9-9ad190efbe6a", + "fontiFinanziamento": [ + "Big pharma", + "Pentagon" + ], + "genericContent": [], + "id": 8, + "immaginiRappresentative": [ + { + "actualContent": [ + { + "associated": null, + "id": 187, + "link": "https://data.dev.d4science.org/shub/E_VGFQSExUR1BmdzVHNjI5ZjFJeXE1TUFpWEkxUGllelIyWVZndndKZ3pBaGNPWlNGczdFaUNtMHVZaEd0dXc2Rw==", + "mimetype": "image/png", + "storageID": "6765deab-b157-494f-83e2-299032e1904c", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 0", + "format": "TIFF", + "id": 67, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 0", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 188, + "link": "https://data.dev.d4science.org/shub/E_NDVyQnBiMFJ1VDliYS83eEJvYS9vb1JrZ2tQN2orY3ZXNUJia25McFFtNlMrSS91NHNBN3pPVUNzTGN2aE9JOQ==", + "mimetype": "image/png", + "storageID": "4b4fb9f9-b96d-485d-80c9-713fad8a5c9b", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 1", + "format": "TIFF", + "id": 68, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 1", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 189, + "link": "https://data.dev.d4science.org/shub/E_YWw0TXVKa29KZFJOeDMwVlRDc29xMW0zSExYdGRJdmxZWEtJSGRVZ3ZBRVEwTnc1cEdMNG1iVVRBN2JDVG9WYQ==", + "mimetype": "image/png", + "storageID": "2690d6bc-2833-4797-81bb-1c1865f34f34", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 2", + "format": "TIFF", + "id": 69, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 2", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 190, + "link": "https://data.dev.d4science.org/shub/E_TU9ha0lGMTRhTDl6S2IzWDNmUWROVXZjZzJiSmNrS1BjZ3gycUJvVEI2b0ZDaGxuK1dUWmlsNW1DZE1ESUJPbA==", + "mimetype": "image/png", + "storageID": "244b0fe0-8f53-469a-9c5e-ac63e37d4622", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 3", + "format": "TIFF", + "id": 70, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 3", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 191, + "link": "https://data.dev.d4science.org/shub/E_NXZYZnV3RlFTYzRWOTBOMWt0cmFtVUtTU20xWTBQb3daRVV6RU8zTDc5RU9USnZVL3Y1VmpRNHNaajMyelVTMQ==", + "mimetype": "image/png", + "storageID": "6fab2082-08af-4899-9730-462b011b517d", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 4", + "format": "TIFF", + "id": 71, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 4", + "type": "UploadedImage" + } + ], + "introduzione": "This is my project", + "lastUpdateTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "lastUpdateUser": "NO NAME", + "licenzaID": "CC-BY", + "nome": "Italia, forse", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "pianteFineScavo": [ + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 196, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 193, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 195, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 194, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 192, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 72, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 198, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 197, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 200, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 199, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 201, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 73, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 202, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 204, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 205, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 203, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 206, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 74, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 209, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 211, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 207, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 208, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 210, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 75, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + } + ], + "policy": "OPEN", + "posizionamentoScavo": { + "abstractSection": "Posizionamento topografico georeferenziato dell’area interessata dalle indagini", + "actualContent": [ + { + "associated": null, + "id": 214, + "link": "https://data.dev.d4science.org/shub/E_UjJoQkw2a0VlR3djQnVYMlNaME40VkdLL3pxV21DNmRrWXVZUlFhMk53aXJORVJmM29pcHpPdVc4aHZLUTRwcg==", + "mimetype": "text/plain", + "storageID": "fac45dad-4840-4fda-b613-0bfd831d8720", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 213, + "link": "https://data.dev.d4science.org/shub/E_T0lsR09LbVdqMExWT1ZwZWpZSW4zUXBqZlV2bCt6d3hMbnc5UDBvRW45eENONzB4cXNtZ216cXZFNWVzdjU0eg==", + "mimetype": "text/plain", + "storageID": "d599e90f-26e0-4b27-b85e-bedd286ff2d7", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 215, + "link": "https://data.dev.d4science.org/shub/E_U2NhMXUvMzRycE9YYkRmbHphdC82QlJJN2FEeVd0Y1FEQmxwSjNmcWRTUDZoZHhVQ1VPdjRMVVdOVDcxNTh5Yw==", + "mimetype": "application/x-shapefile", + "storageID": "f1080875-7f01-4658-9758-9388262ad12c", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 216, + "link": "https://data.dev.d4science.org/shub/E_RlBWRW5lUG9nbncxSC9ZQlRURmFSeTQ3Q0ZqdDhOK2luV01uWjUxMTZ4OHREa29US2t3K21RblpjaXFMTWtSSA==", + "mimetype": "application/x-shapefile", + "storageID": "6b7eeb40-0cd1-4fe5-97b5-b7091e6b7531", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 212, + "link": "https://data.dev.d4science.org/shub/E_aHQxcDhoRGN0QXdXZTkwUmtKRlJsUFVqWjM4STY3U0JkVGU1L3l1a2t5WkhFWlc4blpoa0QxaDVRaHZCOXR2Nw==", + "mimetype": "application/x-dbf", + "storageID": "9af11436-2241-4a03-a95b-96849272bc25", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 76, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "OPEN", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse posizionamento scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + "recordType": "CONCESSIONE", + "relazioneScavo": { + "abstractSection": "simple abstract section", + "actualContent": [ + { + "associated": null, + "id": 217, + "link": "https://data.dev.d4science.org/shub/E_a0JGWWNsY0tFc29CVC8xUW1ROUJhMnVaaWwxNk5TTk5TRlgvQW1tbkpLdDBuNFU3Rkg2VmlFVW53TEUzaEM4aA==", + "mimetype": "application/pdf", + "storageID": "e0cd721a-89e4-437d-9fba-2bfc197cb4c1", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 77, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "Italia, forse relazione di scavo", + "type": "RelazioneScavo" + }, + "responsabile": "Someone", + "risorseCorrelate": [], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolareCopyright": "Chiedilo in giro", + "titolareLicenza": "Qualcun altro", + "titolari": [ + "Some one", + "Some, oneelse" + ], + "version": "1.0.0" +} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/ConcessioniList.json b/geoportal-client/src/test/resources/concessioni/ConcessioniList.json new file mode 100644 index 0000000..8258a73 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/ConcessioniList.json @@ -0,0 +1 @@ +[{"mongo_id":"612fabe58b7a9ee012cf797b","id":0,"recordType":"CONCESSIONE","version":"1.0.0","licenzaID":"CC-BY","policy":"OPEN","nome":"Concessione : publish test","folderId":"5c312758-0233-461f-9b9c-2bf89402f488","lastUpdateTime":"2021-09-01T18:35:49.003","lastUpdateUser":null,"creationTime":"2021-09-01T18:35:49.003","creationUser":null,"report":{"objectName":"Publish report","errorMessages":[],"warningMessages":[],"children":[],"status":"PASSED"},"introduzione":"This is my MONGO project","descrizioneContenuto":"It contains this and that","authors":["Some one","Some, oneelse"],"contributore":"Contrib 1","titolari":["Some one","Some, oneelse"],"responsabile":"Someone","editore":"Editore","fontiFinanziamento":["Big pharma","Pentagon"],"soggetto":["Research Excavation","Archeology"],"risorseCorrelate":null,"dataInizioProgetto":"2021-09-01T18:35:43.733","dataFineProgetto":"2021-09-01T18:35:43.733","titolareLicenza":["Qualcun altro"],"titolareCopyright":["Chiedilo in giro"],"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"centroidLat":43.0,"centroidLong":9.0,"relazioneScavo":{"mongo_id":"612fabf68b7a9ee012cf7980","id":0,"policy":"OPEN","licenseID":"CC-BY","titolo":"Concessione : publish test relazione di scavo","creationTime":"2021-09-01T18:35:49.003","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/pdf","storageID":"60cf254f-bded-4a81-ad4e-4c5f21fdc007","link":"https://data.dev.d4science.org/shub/E_OEVWcE1VUE9SMmlJV0tqaWd2SWx5UkFNRFRqdkRKZEJFNFVGb1B4WDlBQ0Y3WGptL1J3Z1pXU3VWb0dnbEdTUw=="}],"abstractIta":"simple abstract section","abstractEng":null,"responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},"immaginiRappresentative":[{"mongo_id":"612fac548b7a9ee012cf7995","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 0","creationTime":"2021-09-01T18:35:43.733","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"18acfb57-6b59-4695-82c7-90d355e5153d","link":"https://data.dev.d4science.org/shub/E_aEN5a2FjaXMwWmhPRjdPUjhtR2NiOHhEb0ZLMDlvS2M3YytiQklxQ3AyWVpwOTBBTmhVK0hzZGNHUHpuV2ttRQ=="}],"didascalia":"You can see my image number 0","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},{"mongo_id":"612fac648b7a9ee012cf799a","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 1","creationTime":"2021-09-01T18:35:43.733","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"ad6b709f-752b-431c-88f9-9e21d8febc56","link":"https://data.dev.d4science.org/shub/E_TkdpUXFKcWpybFFIWW5KUkl4dW5ZcklNdlE4WiszZ3d1enRweGYzaGk2UVF5WG5TcW1RQ2VORUswenFqMGpWUw=="}],"didascalia":"You can see my image number 1","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]}],"posizionamentoScavo":{"mongo_id":"612fac198b7a9ee012cf7988","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test posizionamento scavo","creationTime":"2021-09-01T18:35:49.003","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"4a420c43-0a5f-49cc-a5da-671446fffff9","link":"https://data.dev.d4science.org/shub/E_OFNvWkFsVTNHVTFCZlZCbXFMekxqcnEwWkVYcG9QdDMyNUZIcVdBS3pSUHBMdFBnWjVlYndjMlVSZFRpd0pUWg=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"07d2b7bb-fd7f-4517-aae9-fd4936e38911","link":"https://data.dev.d4science.org/shub/E_TlhEZUNKOTVIVEQ5aXl2SUg0elRuRnRsdlhpdjdUdU9jQkJhbWEreEhibjYrYldDd0REeWh1SWlYbklFajZkag=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/612fabe58b7a9ee012cf797b/612fac198b7a9ee012cf7988/pos","fileNames":["pos.shp","pos.shx"],"workspace":"gna_conc_612fabe58b7a9ee012cf797b","store":"pos_store","featureType":"pos"}],"layerUUID":null,"layerID":null,"layerName":"pos","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_612fabe58b7a9ee012cf797b/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_612fabe58b7a9ee012cf797b:pos&styles=&bbox=8.620919,40.629750,8.621179,40.630258&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_612fabe58b7a9ee012cf797b","abstractSection":"Posizionamento topografico georeferenziato dell’area interessata dalle indagini","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":40.630257904721645,"maxLong":8.621178639172953,"minLat":40.62975046683799,"minLong":8.62091913167495},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"},"pianteFineScavo":[{"mongo_id":"612fac3e8b7a9ee012cf7990","id":0,"policy":"RESTRICTED","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test pianta fine scavo","creationTime":"2021-09-01T18:35:49.003","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"cd64f679-308c-44ae-90f7-fc3d0963422d","link":"https://data.dev.d4science.org/shub/E_Q2FhV3pTTnZuTnIwQzRRdUVSUmtTTGlwTVpRQ3hHanNmQnJvMHA0aHhxaHdPd1V4L0pqUnJpeXN4NGZHUS9NMA=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"5a6c5577-6b83-4c90-acc2-9ae81ce0ffc6","link":"https://data.dev.d4science.org/shub/E_TXQzcXdDb3RlcGp0VlpYK3hTeHdUdGxiNWlveE5KYy9kVy90ajMrUG9nYTRmejFwREtPU0xqZC9Gbncrclcxbg=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/612fabe58b7a9ee012cf797b/612fac3e8b7a9ee012cf7990/pianta","fileNames":["pianta.shp","pianta.shx"],"workspace":"gna_conc_612fabe58b7a9ee012cf797b","store":"pianta_store","featureType":"pianta"}],"layerUUID":null,"layerID":null,"layerName":"pianta","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_612fabe58b7a9ee012cf797b/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_612fabe58b7a9ee012cf797b:pianta&styles=&bbox=647502.891667,4470387.347517,647527.486073,4470417.525434&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_612fabe58b7a9ee012cf797b","abstractSection":"Planimetria georeferenziata dell'area indagata al termine delle attività","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":4470417.525433567,"maxLong":647527.4860734959,"minLat":4470387.347516773,"minLong":647502.8916670183},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"}],"genericContent":[]},{"mongo_id":"612fac7d8b7a9ee012cf799d","id":0,"recordType":"CONCESSIONE","version":"1.0.0","licenzaID":"CC-BY","policy":"OPEN","nome":"Concessione : publish test","folderId":"2e797746-bd79-4dca-9e34-db32b2fc981f","lastUpdateTime":"2021-09-01T18:38:21.679","lastUpdateUser":null,"creationTime":"2021-09-01T18:38:21.679","creationUser":null,"report":{"objectName":"Publish report","errorMessages":[],"warningMessages":[],"children":[],"status":"PASSED"},"introduzione":"This is my MONGO project","descrizioneContenuto":"It contains this and that","authors":["Some one","Some, oneelse"],"contributore":"Contrib 1","titolari":["Some one","Some, oneelse"],"responsabile":"Someone","editore":"Editore","fontiFinanziamento":["Big pharma","Pentagon"],"soggetto":["Research Excavation","Archeology"],"risorseCorrelate":null,"dataInizioProgetto":"2021-09-01T18:38:20.695","dataFineProgetto":"2021-09-01T18:38:20.695","titolareLicenza":["Qualcun altro"],"titolareCopyright":["Chiedilo in giro"],"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"centroidLat":43.0,"centroidLong":9.0,"relazioneScavo":{"mongo_id":"612fac8e8b7a9ee012cf79a2","id":0,"policy":"OPEN","licenseID":"CC-BY","titolo":"Concessione : publish test relazione di scavo","creationTime":"2021-09-01T18:38:21.679","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/pdf","storageID":"7222cf6a-0e9e-40ec-99db-d108fe9842c5","link":"https://data.dev.d4science.org/shub/E_cnVaTWdPM2NscW9Ka1RUd0toMDRVbGRRK2hhSGhPWnIxYlk4L0tCS1FqNkZpb01vRHcwTVEyLzNZZUR0MGN4RQ=="}],"abstractIta":"simple abstract section","abstractEng":null,"responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},"immaginiRappresentative":[{"mongo_id":"612face28b7a9ee012cf79b7","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 0","creationTime":"2021-09-01T18:38:20.695","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"eb5a69f2-dc04-4d19-a2f5-0885a754c449","link":"https://data.dev.d4science.org/shub/E_VDlXY2Y1STBuWTc3SXREMis2OHZIMnF5NDdUYmkzWVJTT2JxS05HYi8rbnB1bUFhZFF4T0p5ZStNZGxyR2Rmdg=="}],"didascalia":"You can see my image number 0","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},{"mongo_id":"612facf28b7a9ee012cf79bc","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 1","creationTime":"2021-09-01T18:38:20.695","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"d8c1da0b-4575-4457-9bc4-0a7c2c93fdf6","link":"https://data.dev.d4science.org/shub/E_cG9lTXl4a3dTRnp4azRWZG1rNHh1Q1dIYUQ4MjljaDZRUUEyVUdoN3czSWdJWUZlNTVFUzFRaXJhOEsxaTB2aw=="}],"didascalia":"You can see my image number 1","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]}],"posizionamentoScavo":{"mongo_id":"612facae8b7a9ee012cf79aa","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test posizionamento scavo","creationTime":"2021-09-01T18:38:21.679","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"7152d15f-7e60-4095-bef8-5936981f9f67","link":"https://data.dev.d4science.org/shub/E_MzV5QllFaGdVZUpuN0ZkejVyT0F5dFc2SnpGaG85c09TaVR0RCtpS2J1SUNBZVFhS2RpZnFnakp3VkJHa2hEOQ=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"043b47d3-7aa9-400d-b4dc-dc103fc78117","link":"https://data.dev.d4science.org/shub/E_QWVQNEhFUFZLbDJubVptaWVsZWRxUlc5cStLdC9UaGI4c3puQkdlWFpHTUVXcFVjR3UzSS9ieHQ1dGJrYnp5VQ=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/612fac7d8b7a9ee012cf799d/612facae8b7a9ee012cf79aa/pos","fileNames":["pos.shp","pos.shx"],"workspace":"gna_conc_612fac7d8b7a9ee012cf799d","store":"pos_store","featureType":"pos"}],"layerUUID":null,"layerID":null,"layerName":"pos","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_612fac7d8b7a9ee012cf799d/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_612fac7d8b7a9ee012cf799d:pos&styles=&bbox=8.620919,40.629750,8.621179,40.630258&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_612fac7d8b7a9ee012cf799d","abstractSection":"Posizionamento topografico georeferenziato dell’area interessata dalle indagini","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":40.630257904721645,"maxLong":8.621178639172953,"minLat":40.62975046683799,"minLong":8.62091913167495},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"},"pianteFineScavo":[{"mongo_id":"612facd38b7a9ee012cf79b2","id":0,"policy":"RESTRICTED","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test pianta fine scavo","creationTime":"2021-09-01T18:38:21.679","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"2b01a062-e801-4f28-9fbc-0f6f783ecfdc","link":"https://data.dev.d4science.org/shub/E_eXRPd3ZiY0VhVWRTMkRrTnB3Y0d1WlN1Y3NRb2M4azMzMmViVUFnUkpDTFphdGZ5RmhYTXZUUXZLbXpjK2hFVg=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"74e48df4-a266-4e66-a687-0659c2f6cddc","link":"https://data.dev.d4science.org/shub/E_Mml5V0tobXU1RjlRNU1rWGNJa2o5SThKeWJwSWQ5TGN2QVJEUnJ2RkNreWR2aFkrdzJrdEdYL0kwNitDSE8ySg=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/612fac7d8b7a9ee012cf799d/612facd38b7a9ee012cf79b2/pianta","fileNames":["pianta.shp","pianta.shx"],"workspace":"gna_conc_612fac7d8b7a9ee012cf799d","store":"pianta_store","featureType":"pianta"}],"layerUUID":null,"layerID":null,"layerName":"pianta","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_612fac7d8b7a9ee012cf799d/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_612fac7d8b7a9ee012cf799d:pianta&styles=&bbox=647502.891667,4470387.347517,647527.486073,4470417.525434&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_612fac7d8b7a9ee012cf799d","abstractSection":"Planimetria georeferenziata dell'area indagata al termine delle attività","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":4470417.525433567,"maxLong":647527.4860734959,"minLat":4470387.347516773,"minLong":647502.8916670183},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"}],"genericContent":[]},{"mongo_id":"6130a2808b7a9eef830a4401","id":0,"recordType":"CONCESSIONE","version":"1.0.0","licenzaID":"CC-BY","policy":"OPEN","nome":"Concessione : publish test","folderId":"36d9744b-739b-4560-9eeb-7e9ff3c64753","lastUpdateTime":"2021-09-02T12:08:00.057","lastUpdateUser":null,"creationTime":"2021-09-02T12:08:00.057","creationUser":null,"report":{"objectName":"Publish report","errorMessages":[],"warningMessages":[],"children":[],"status":"PASSED"},"introduzione":"This is my MONGO project","descrizioneContenuto":"It contains this and that","authors":["Some one","Some, oneelse"],"contributore":"Contrib 1","titolari":["Some one","Some, oneelse"],"responsabile":"Someone","editore":"Editore","fontiFinanziamento":["Big pharma","Pentagon"],"soggetto":["Research Excavation","Archeology"],"risorseCorrelate":null,"dataInizioProgetto":"2021-09-02T12:07:59.643","dataFineProgetto":"2021-09-02T12:07:59.643","titolareLicenza":["Qualcun altro"],"titolareCopyright":["Chiedilo in giro"],"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"centroidLat":43.0,"centroidLong":9.0,"relazioneScavo":{"mongo_id":"6130a2908b7a9eef830a4406","id":0,"policy":"OPEN","licenseID":"CC-BY","titolo":"Concessione : publish test relazione di scavo","creationTime":"2021-09-02T12:08:00.057","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/pdf","storageID":"da28aaa3-1ff2-4ca1-845b-d818c764cf3e","link":"https://data.dev.d4science.org/shub/E_bXdDOWJjaEExNzJlQ2wxaG9PNjRoSlU5eTBYdTUvTE1QRjRETnRBSDJXbzMyZXpXQkhqMVNyYXZiSWlCWXdwcg=="}],"abstractIta":"simple abstract section","abstractEng":null,"responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},"immaginiRappresentative":[{"mongo_id":"6130a2e48b7a9eef830a441b","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 0","creationTime":"2021-09-02T12:07:59.644","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"f5c859e1-0cfb-482a-ba63-b8e38d24c3eb","link":"https://data.dev.d4science.org/shub/E_UUxNU3NXS1BDUXBWblFEYW4rSzQxd3p6ci80SW5YdksvbGdoQmxwYTVDcUhZZllGcjlNeU1rMzVYd3FZcEFDcw=="}],"didascalia":"You can see my image number 0","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},{"mongo_id":"6130a2f98b7a9eef830a4420","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 1","creationTime":"2021-09-02T12:07:59.644","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"0ee98859-7a38-4f39-8955-20fe1f2665a1","link":"https://data.dev.d4science.org/shub/E_aXdBNUlSemY2dzFWL2NyVWp6OTBZMTRlRVVNNE5vSUpIckhIVXlESGJySUVPR2owUjV0TlI0S241VGZab0RVeQ=="}],"didascalia":"You can see my image number 1","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]}],"posizionamentoScavo":{"mongo_id":"6130a2b18b7a9eef830a440e","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test posizionamento scavo","creationTime":"2021-09-02T12:08:00.057","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"b4a99b19-ec8f-4660-8c32-e58b2789f323","link":"https://data.dev.d4science.org/shub/E_R05ENnJ2Vnd1SnJ3WDBiRHBqOVZQQUNqbGNxb2lHMzF2ZXN5b0U0UFBNQUVIMFJmRlgzdTVtQlVjZ1R0Q0o1MA=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"240d2794-2005-463d-92e7-ac98d14de136","link":"https://data.dev.d4science.org/shub/E_RnBNRDNaT1NnRmRIY25QUlN4b3VhaVBOdTY3c3UvNkFIejhnT0R1ZE9ZRWJxUTRxcGg5Q2huN3c3azUxV2NwaQ=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/6130a2808b7a9eef830a4401/6130a2b18b7a9eef830a440e/pos","fileNames":["pos.shp","pos.shx"],"workspace":"gna_conc_6130a2808b7a9eef830a4401","store":"pos_store","featureType":"pos"}],"layerUUID":null,"layerID":null,"layerName":"pos","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_6130a2808b7a9eef830a4401/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_6130a2808b7a9eef830a4401:pos&styles=&bbox=8.620919,40.629750,8.621179,40.630258&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_6130a2808b7a9eef830a4401","abstractSection":"Posizionamento topografico georeferenziato dell’area interessata dalle indagini","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":40.630257904721645,"maxLong":8.621178639172953,"minLat":40.62975046683799,"minLong":8.62091913167495},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"},"pianteFineScavo":[{"mongo_id":"6130a2d48b7a9eef830a4416","id":0,"policy":"RESTRICTED","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test pianta fine scavo","creationTime":"2021-09-02T12:08:00.057","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"715e3380-4d13-453c-adef-66ba17e019be","link":"https://data.dev.d4science.org/shub/E_ZjQ0N2UvZmFHMFQ2VzkxMjUrcDNBNEdtVDRVdG9abnhFR0YrYmR0WWlBbzkrM2V2QWs5MnR0QjNDSFRnUUFQNg=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"88532c9e-7e24-4424-b8f7-ca0f79e7683d","link":"https://data.dev.d4science.org/shub/E_dGUxWmw3d1dNaURxSnRaYzZqWkJMS255ZkdYb1FnbjVXc0srT1pyUkRWOWdob1c5MlJzTjMrMFI3aDBmaDVjNA=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/6130a2808b7a9eef830a4401/6130a2d48b7a9eef830a4416/pianta","fileNames":["pianta.shp","pianta.shx"],"workspace":"gna_conc_6130a2808b7a9eef830a4401","store":"pianta_store","featureType":"pianta"}],"layerUUID":null,"layerID":null,"layerName":"pianta","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_6130a2808b7a9eef830a4401/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_6130a2808b7a9eef830a4401:pianta&styles=&bbox=647502.891667,4470387.347517,647527.486073,4470417.525434&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_6130a2808b7a9eef830a4401","abstractSection":"Planimetria georeferenziata dell'area indagata al termine delle attività","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":4470417.525433567,"maxLong":647527.4860734959,"minLat":4470387.347516773,"minLong":647502.8916670183},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"}],"genericContent":[]},{"mongo_id":"6130a3198b7a9eef830a4423","id":0,"recordType":"CONCESSIONE","version":"1.0.0","licenzaID":"CC-BY","policy":"OPEN","nome":"Concessione : publish test","folderId":"19286a89-b678-40fd-98f0-93f266a543cb","lastUpdateTime":"2021-09-02T12:10:33.629","lastUpdateUser":null,"creationTime":"2021-09-02T12:10:33.629","creationUser":null,"report":{"objectName":"Publish report","errorMessages":[],"warningMessages":[],"children":[],"status":"PASSED"},"introduzione":"This is my MONGO project","descrizioneContenuto":"It contains this and that","authors":["Some one","Some, oneelse"],"contributore":"Contrib 1","titolari":["Some one","Some, oneelse"],"responsabile":"Someone","editore":"Editore","fontiFinanziamento":["Big pharma","Pentagon"],"soggetto":["Research Excavation","Archeology"],"risorseCorrelate":null,"dataInizioProgetto":"2021-09-02T12:10:28.119","dataFineProgetto":"2021-09-02T12:10:28.119","titolareLicenza":["Qualcun altro"],"titolareCopyright":["Chiedilo in giro"],"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"centroidLat":43.0,"centroidLong":9.0,"relazioneScavo":{"mongo_id":"6130a32f8b7a9eef830a4428","id":0,"policy":"OPEN","licenseID":"CC-BY","titolo":"Concessione : publish test relazione di scavo","creationTime":"2021-09-02T12:10:33.629","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/pdf","storageID":"0af0d4af-1134-4b3c-bf18-bdf4d37b0366","link":"https://data.dev.d4science.org/shub/E_UWVHenRLelp2SWJtRFFoSU1PaHZPRHFZaExMM0kyQWtJYXZWaXR5NDIxdHIyWWFNb3dOaG04R0cwaGFJL3lOVA=="}],"abstractIta":"simple abstract section","abstractEng":null,"responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},"immaginiRappresentative":[{"mongo_id":"6130a37f8b7a9eef830a443d","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 0","creationTime":"2021-09-02T12:10:28.119","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"7c544d13-e226-4202-9c06-a850755ca625","link":"https://data.dev.d4science.org/shub/E_VXBBWU9RN2Vpc0NHVGxaenhrRVQzcFRkYnlScFFKY1RiekhQRTVraE92dHU1RVpTTlc4bHFxREVvaGppS1RHeg=="}],"didascalia":"You can see my image number 0","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]},{"mongo_id":"6130a3948b7a9eef830a4442","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"My image number 1","creationTime":"2021-09-02T12:10:28.119","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"image/png","storageID":"8c4b30af-7dcd-4cfd-85d2-8560316715d9","link":"https://data.dev.d4science.org/shub/E_YkFIdERidys5aDBRbkJmMGpmWVdCM0FVdEo2WGNXS010aTE3bklodGFMTmJucDhwckU3L0g3Um5VTEh5NS9qQg=="}],"didascalia":"You can see my image number 1","format":"TIFF","responsabili":["Some one","Some, oneelse"],"soggetto":["Research Excavation","Archeology"]}],"posizionamentoScavo":{"mongo_id":"6130a34c8b7a9eef830a4430","id":0,"policy":"OPEN","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test posizionamento scavo","creationTime":"2021-09-02T12:10:33.629","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"a1da6f0e-3670-43bf-84d9-1a61dac8f912","link":"https://data.dev.d4science.org/shub/E_Skw5MXJTVkZnS2ZRNWdQUWxQR2VLWVNqNUg0L3QzeitFdWdlOFdtazlkZ1ZOR0ZqcW5WYmJtSkpsN2Z3M2x1dw=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"64c4733d-3a94-48b6-aa04-5115573b9d9e","link":"https://data.dev.d4science.org/shub/E_ZTdzTG4zRUlpbFRMSFlIR1lNdUExcVVzaWQ4VmkzbkxoempINnk3MzZtcE9lZlhKVGErcC9PMDVGWU1vK003cg=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/6130a3198b7a9eef830a4423/6130a34c8b7a9eef830a4430/pos","fileNames":["pos.shp","pos.shx"],"workspace":"gna_conc_6130a3198b7a9eef830a4423","store":"pos_store","featureType":"pos"}],"layerUUID":null,"layerID":null,"layerName":"pos","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_6130a3198b7a9eef830a4423/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_6130a3198b7a9eef830a4423:pos&styles=&bbox=8.620919,40.629750,8.621179,40.630258&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_6130a3198b7a9eef830a4423","abstractSection":"Posizionamento topografico georeferenziato dell’area interessata dalle indagini","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":40.630257904721645,"maxLong":8.621178639172953,"minLat":40.62975046683799,"minLong":8.62091913167495},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"},"pianteFineScavo":[{"mongo_id":"6130a3708b7a9eef830a4438","id":0,"policy":"RESTRICTED","licenseID":"CC-BY-4.0","titolo":"Concessione : publish test pianta fine scavo","creationTime":"2021-09-02T12:10:33.629","actualContent":[{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"27830c55-d762-4cfc-911b-99b254fba366","link":"https://data.dev.d4science.org/shub/E_U2ZqSExFRno4aEhXdC93cEo2elU2ZjRWVWU2TGRsb3RYMS8rYUNyMDVnUU1ZOSt6UUJxRTF0K0pKeGNjZ3RwZA=="},{"type":"WorkspaceContent","id":0,"mimetype":"application/x-shapefile","storageID":"689cd4a9-535d-4d8a-b473-175f7f772d0e","link":"https://data.dev.d4science.org/shub/E_Y2JTbjFHT3pmZzhzSW9Fa2NCN0RmeVBpU04xY2tWdzA1eUdyQVVNN2l6c1M5M3dManBzY3NVZWRsQ0w5b3VBeA=="},{"type":"GeoServerContent","id":0,"geoserverHostName":"geoserver-218.dev.d4science.org","geoserverPath":"/srv/geoserver_data/GNA/6130a3198b7a9eef830a4423/6130a3708b7a9eef830a4438/pianta","fileNames":["pianta.shp","pianta.shx"],"workspace":"gna_conc_6130a3198b7a9eef830a4423","store":"pianta_store","featureType":"pianta"}],"layerUUID":null,"layerID":null,"layerName":"pianta","wmsLink":"https://geoserver-218.dev.d4science.org/geoserver/gna_conc_6130a3198b7a9eef830a4423/wms?service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_6130a3198b7a9eef830a4423:pianta&styles=&bbox=647502.891667,4470387.347517,647527.486073,4470417.525434&srs=EPSG:4326&format=application/openlayers&width=400&height=400","workspace":"gna_conc_6130a3198b7a9eef830a4423","abstractSection":"Planimetria georeferenziata dell'area indagata al termine delle attività","topicCategory":"Society","subTopic":"Archeology","bbox":{"maxLat":4470417.525433567,"maxLong":647527.4860734959,"minLat":4470387.347516773,"minLong":647502.8916670183},"paroleChiaveLibere":["Robba","Stuff"],"paroleChiaveICCD":["vattelapesca","somthing something"],"valutazioneQualita":"Secondo me si","metodoRaccoltaDati":"Fattobbene","scalaAcquisizione":"1:10000","authors":["Some one","Some, oneelse"],"responsabile":"Someone"}],"genericContent":[]}] diff --git a/geoportal-client/src/test/resources/concessioni/DATASET_GNA_01.csv b/geoportal-client/src/test/resources/concessioni/DATASET_GNA_01.csv new file mode 100644 index 0000000..6ab4b49 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/DATASET_GNA_01.csv @@ -0,0 +1,6 @@ +"GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite" +"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo" +"GNA_Monte Postale_2019_rev_CdL_FPDC","GNA_Monte Postale_2019_rev_CdL_FPDC/GNA_topografia_Monte Postale","GNA_Monte Postale_2019_rev_CdL_FPDC/GNA_topografia_Monte Postale" +"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo" +"GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite" +"GNA_Appia Antica V miglio_2019_rev_CdL","GNA_Appia Antica V miglio_2019_rev_CdL/GNA_topografia_Appia Antica V miglio","GNA_Appia Antica V miglio_2019_rev_CdL/GNA_topografia_Appia Antica V miglio" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/DATASET_GNA_02.csv b/geoportal-client/src/test/resources/concessioni/DATASET_GNA_02.csv new file mode 100644 index 0000000..33fec22 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/DATASET_GNA_02.csv @@ -0,0 +1,5 @@ +"GNA_Bostel di Rotzo_Rev_FPDC","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo" +"GNA_Braida Murada_Rev_FPDC","GNA_Braida Murada_Rev_FPDC/GNA_topografia_Braida Murada","GNA_Braida Murada_Rev_FPDC/GNA_topografia_Braida Murada" +"GNA_Egnazia_Rev_FPDC","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo" +"GNA_Ferrandina_Rev_FPDC","GNA_Ferrandina_Rev_FPDC/GNA_topografia_Ferrandina","GNA_Ferrandina_Rev_FPDC/GNA_topografia_Ferrandina" +"GNA_Timpone della Motta_Rev_FPDC","GNA_Timpone della Motta_Rev_FPDC/GNA_topografia_Timpone della Motta","GNA_Timpone della Motta_Rev_FPDC/GNA_topografia_Timpone della Motta" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/concessioni04-03.csv b/geoportal-client/src/test/resources/concessioni/concessioni04-03.csv new file mode 100644 index 0000000..0b58aa6 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/concessioni04-03.csv @@ -0,0 +1,18 @@ +"Acquacadda_Nuxis_2019_def","Acquacadda_Nuxis_2019_def/D_posizionamento saggi_NUXIS 2019","Acquacadda_Nuxis_2019_def/E_piante fine scavo vettoriali_NUXIS 2019" +"Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)","Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)/GNA_SAN BASILIO_2019/GNA_topografia_San Basilio/POSIZIONAMENTO SAGGI E AREE DELLE INDAGINI","Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)/GNA_SAN BASILIO_2019/GNA_topografia_San Basilio/PLANIMETRIE SAGGI AREE DELLE INDAGINI" +"Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)","Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)/S.Basilio-UniVe_GNA/D.GNA_Posizionamento_San Basilio","Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)/S.Basilio-UniVe_GNA/E-F.GNA_Pianta fine scavo_San Basilio/GNA_Pianta di fine scavo_San Basilio" +"C_F_GNA_Oscurusciuto_2020","C_F_GNA_Oscurusciuto_2020/D-E-F_GNA_topografia_Oscurusciuto/Posizionamento_limiti_saggi_indagati_Oscurusciuto_vettoriale","C_F_GNA_Oscurusciuto_2020/D-E-F_GNA_topografia_Oscurusciuto/Pianta_fine_scavo_Oscurusciuto_2019_vettoriale" +"Cerchiara-Damale_rev","Cerchiara-Damale_rev/D. Posizionamento saggio","Cerchiara-Damale_rev/D. Posizionamento saggio/E. Pianta fine scavo_vettoriale" +"Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)","Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)/IVa_E_Tav.1_Shape Posizionamento area scavo","Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)/IVa_E_Tav.1_Shape Posizionamento area scavo" +"Cervia Vecchia_rev","Cervia Vecchia_rev/D_GNA_topografia_Cervia","Cervia Vecchia_rev/D_GNA_topografia_Cervia" +"Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019","Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019/Civitavecchia (RM)_Ficoncella_documentazione fine scavo 2019_IVa/IVa_D_E_F_topografia_Aquae_Tauri/Posizionamento saggi 2019","Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019/Civitavecchia (RM)_Ficoncella_documentazione fine scavo 2019_IVa/IVa_D_E_F_topografia_Aquae_Tauri/Pianta di fine scavo 2019" +"Frascineto Timpone delle Fave_rev","Frascineto Timpone delle Fave_rev/D. Posizionamento dell'area","Frascineto Timpone delle Fave_rev/D. Posizionamento dell'area" +"GNA_AQUILEIAcomellimoro_2019_rev","GNA_AQUILEIAcomellimoro_2019_rev/GNA_topografia_AQUILEIAcomellimoro_2019/D.Posizionamento_AQU19COM","GNA_AQUILEIAcomellimoro_2019_rev/GNA_topografia_AQUILEIAcomellimoro_2019/E.Planimetria generale_AQU19COM" +"GNA_Poggio Pimperiale_Poggibonsi","GNA_Poggio Pimperiale_Poggibonsi/5591449/GNA_topografia_PoggioImperiale/POSIZIONAMENTO","GNA_Poggio Pimperiale_Poggibonsi/5591449/GNA_topografia_PoggioImperiale/PLANIMETRIE SAGGI/SHAPE A22" +"Jesolo (Ve) - Loc. San Mauro e Torre del Caligo","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/GNA_topografia_Jesolo/GIS_shp","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/GNA_topografia_Jesolo/GIS_shp" +"Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)","Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)/IVa_D_E_F_GNA_topografia_Via Nomentum-Eretum","Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)/IVa_D_E_F_GNA_topografia_Via Nomentum-Eretum" +"Quarto d'Altino -Loc. Fornace","Quarto d'Altino -Loc. Fornace/Cupitò/GNA_Altino_posizionamento_shp","Quarto d'Altino -Loc. Fornace/Cupitò/GNA_Altino_posizionamento_shp" +"Tolfa (RM)_Bufalareccia_documentazione IVa 2019","Tolfa (RM)_Bufalareccia_documentazione IVa 2019/IVa_D_GNA_posizionamento dei limiti in formato vectoriale_Bufalareccia 2019","Tolfa (RM)_Bufalareccia_documentazione IVa 2019/IVa_E_GNA_pianta di fine scavo multipolygon_Bufalareccia 2019" +"UNIME_ LAINO_REV","UNIME_ LAINO_REV/D. Limiti saggi/laino 2019 limiti saggi","UNIME_ LAINO_REV/D. Limiti saggi/laino 2019 limiti saggi" +"UNIME_TORTORA_rev","UNIME_TORTORA_rev/tortora 2019 limiti saggi","UNIME_TORTORA_rev/tortora 2019 pianta fine scavo" +"UsiniTomestighes","UsiniTomestighes/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/D_GNA_Posizionamento_limiti_aree_indagate","UsiniTomestighes/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/E_GNA_Piante_fine_scavo_Tomestighes/Area A_est" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/concessioni04-03_filtered.csv b/geoportal-client/src/test/resources/concessioni/concessioni04-03_filtered.csv new file mode 100644 index 0000000..0b58aa6 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/concessioni04-03_filtered.csv @@ -0,0 +1,18 @@ +"Acquacadda_Nuxis_2019_def","Acquacadda_Nuxis_2019_def/D_posizionamento saggi_NUXIS 2019","Acquacadda_Nuxis_2019_def/E_piante fine scavo vettoriali_NUXIS 2019" +"Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)","Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)/GNA_SAN BASILIO_2019/GNA_topografia_San Basilio/POSIZIONAMENTO SAGGI E AREE DELLE INDAGINI","Ariano nel Polisine (Ro) - Loc. San Basilio ( Unvi. di Padova)/GNA_SAN BASILIO_2019/GNA_topografia_San Basilio/PLANIMETRIE SAGGI AREE DELLE INDAGINI" +"Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)","Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)/S.Basilio-UniVe_GNA/D.GNA_Posizionamento_San Basilio","Ariano Polesine (Ro) - Loc. San Basilio (Uni. di Venezia)/S.Basilio-UniVe_GNA/E-F.GNA_Pianta fine scavo_San Basilio/GNA_Pianta di fine scavo_San Basilio" +"C_F_GNA_Oscurusciuto_2020","C_F_GNA_Oscurusciuto_2020/D-E-F_GNA_topografia_Oscurusciuto/Posizionamento_limiti_saggi_indagati_Oscurusciuto_vettoriale","C_F_GNA_Oscurusciuto_2020/D-E-F_GNA_topografia_Oscurusciuto/Pianta_fine_scavo_Oscurusciuto_2019_vettoriale" +"Cerchiara-Damale_rev","Cerchiara-Damale_rev/D. Posizionamento saggio","Cerchiara-Damale_rev/D. Posizionamento saggio/E. Pianta fine scavo_vettoriale" +"Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)","Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)/IVa_E_Tav.1_Shape Posizionamento area scavo","Cerveteri (RM)_loc. Monte Abatone_documentazione fine scavo 2019 (paragr. IVa)/IVa_E_Tav.1_Shape Posizionamento area scavo" +"Cervia Vecchia_rev","Cervia Vecchia_rev/D_GNA_topografia_Cervia","Cervia Vecchia_rev/D_GNA_topografia_Cervia" +"Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019","Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019/Civitavecchia (RM)_Ficoncella_documentazione fine scavo 2019_IVa/IVa_D_E_F_topografia_Aquae_Tauri/Posizionamento saggi 2019","Civitavecchia (RM)_loc. Ficoncella_Aquae Tauri_docum. IVa_2019/Civitavecchia (RM)_Ficoncella_documentazione fine scavo 2019_IVa/IVa_D_E_F_topografia_Aquae_Tauri/Pianta di fine scavo 2019" +"Frascineto Timpone delle Fave_rev","Frascineto Timpone delle Fave_rev/D. Posizionamento dell'area","Frascineto Timpone delle Fave_rev/D. Posizionamento dell'area" +"GNA_AQUILEIAcomellimoro_2019_rev","GNA_AQUILEIAcomellimoro_2019_rev/GNA_topografia_AQUILEIAcomellimoro_2019/D.Posizionamento_AQU19COM","GNA_AQUILEIAcomellimoro_2019_rev/GNA_topografia_AQUILEIAcomellimoro_2019/E.Planimetria generale_AQU19COM" +"GNA_Poggio Pimperiale_Poggibonsi","GNA_Poggio Pimperiale_Poggibonsi/5591449/GNA_topografia_PoggioImperiale/POSIZIONAMENTO","GNA_Poggio Pimperiale_Poggibonsi/5591449/GNA_topografia_PoggioImperiale/PLANIMETRIE SAGGI/SHAPE A22" +"Jesolo (Ve) - Loc. San Mauro e Torre del Caligo","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/GNA_topografia_Jesolo/GIS_shp","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/GNA_topografia_Jesolo/GIS_shp" +"Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)","Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)/IVa_D_E_F_GNA_topografia_Via Nomentum-Eretum","Monterotondo (RM)_loc. Tor Mancina_documentazione fine scavo 2019 (paragr. IVa)/IVa_D_E_F_GNA_topografia_Via Nomentum-Eretum" +"Quarto d'Altino -Loc. Fornace","Quarto d'Altino -Loc. Fornace/Cupitò/GNA_Altino_posizionamento_shp","Quarto d'Altino -Loc. Fornace/Cupitò/GNA_Altino_posizionamento_shp" +"Tolfa (RM)_Bufalareccia_documentazione IVa 2019","Tolfa (RM)_Bufalareccia_documentazione IVa 2019/IVa_D_GNA_posizionamento dei limiti in formato vectoriale_Bufalareccia 2019","Tolfa (RM)_Bufalareccia_documentazione IVa 2019/IVa_E_GNA_pianta di fine scavo multipolygon_Bufalareccia 2019" +"UNIME_ LAINO_REV","UNIME_ LAINO_REV/D. Limiti saggi/laino 2019 limiti saggi","UNIME_ LAINO_REV/D. Limiti saggi/laino 2019 limiti saggi" +"UNIME_TORTORA_rev","UNIME_TORTORA_rev/tortora 2019 limiti saggi","UNIME_TORTORA_rev/tortora 2019 pianta fine scavo" +"UsiniTomestighes","UsiniTomestighes/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/D_GNA_Posizionamento_limiti_aree_indagate","UsiniTomestighes/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/E_GNA_Piante_fine_scavo_Tomestighes/Area A_est" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/concessioni_23_04.csv b/geoportal-client/src/test/resources/concessioni/concessioni_23_04.csv new file mode 100644 index 0000000..00af136 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/concessioni_23_04.csv @@ -0,0 +1,4 @@ +"GNA_Incoronata_2019_Rennes","/GNA_Incoronata_2019_Rennes/GNA_topografia_Incoronata/D","/GNA_Incoronata_2019_Rennes/GNA_topografia_Incoronata/E/SHP" +"MONTE MANNU_2019","/MONTE MANNU_2019/GNA_topografia_Monte_Mannu/D_Limiti dei saggi","/MONTE MANNU_2019/GNA_topografia_Monte_Mannu/E_Pianta di fine scavo" +"Santa Rosa Poviglio_2019","/Santa Rosa Poviglio_2019/GNA_Topografia_Poviglio/Posizionamento","/Santa Rosa Poviglio_2019/GNA_Topografia_Poviglio/Planimetrie" +"Usini Tomestighes_integrata","/Usini Tomestighes_integrata/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/GNA_Tomestighes_2019/D_GNA_Posizionamento_limiti_aree_indagate","/Usini Tomestighes_integrata/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/GNA_Tomestighes_2019/E_GNA_Piante_fine_scavo_Tomestighes/Area A est" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/filters/all.json b/geoportal-client/src/test/resources/concessioni/filters/all.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/filters/all.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/filters/legacyid.json b/geoportal-client/src/test/resources/concessioni/filters/legacyid.json new file mode 100644 index 0000000..610e687 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/filters/legacyid.json @@ -0,0 +1,3 @@ +{ + "id" : {$gt : 0} +} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/filters/missingCentroid.json b/geoportal-client/src/test/resources/concessioni/filters/missingCentroid.json new file mode 100644 index 0000000..ab28660 --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/filters/missingCentroid.json @@ -0,0 +1,4 @@ +{ + "centroidLat" : 0 + +} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/filters/nonFabio.json b/geoportal-client/src/test/resources/concessioni/filters/nonFabio.json new file mode 100644 index 0000000..b40d2fe --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/filters/nonFabio.json @@ -0,0 +1,3 @@ +{ + "creationUser" : {$ne : "fabio.sinibaldi"} +} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/filters/validated.json b/geoportal-client/src/test/resources/concessioni/filters/validated.json new file mode 100644 index 0000000..f05a79f --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/filters/validated.json @@ -0,0 +1,3 @@ +{ + "report.status": {$eq : "PASSED"} +} \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/immagine.png b/geoportal-client/src/test/resources/concessioni/immagine.png new file mode 100644 index 0000000000000000000000000000000000000000..e777369853edae17f7cc934fa693e0fa9d82f6f9 GIT binary patch literal 5879 zcmcJTcQD-Vx5q_Le$k(3!+315{VK$ zdha&M+PnFF=g!=jd+(pWnKREk=e%d0`ONvuoag<#qKyo+sK{B#iHL}(wBhRah=_=7 z2zxkyl(4q-gL8<87~8bfRUi1}?7Y=S{xa=~I7&Q}Y#uUPH(aj~t?7GG=wsq-{Lu24 zMJVyddPRZ*Bo0KCK9*7n5%SkP@9FRsf)u^#@`d*JFy}VShg02t2YgDO>A0)gT$FWw zeY#hE?RSoCt(VUuvMwkkAv_z*-e9l&v+cU1R|DEIN7yh&WT^?-{8hW4o_$d$mR*FO z<)_c>ZIb`i8q(;1ftH_CDvp={JSbXN-_C8Y4gUP)?LAvCAqAXOMS?W{DiUOZHh&8| zp0EBh1&oSUi)CXa#WW^MzQ@~=kfhTbb!B;p)4>2LKy&6>w{ES@Swq7%yc9&aR*NBi z1OZN?Gf6zU7z1=fG?x9Cw zVauxI+TPH|{P@={U|64JZ6;`3h^NUHrAbDl=d?+e;!HYRc=DCKZs=@#kP(&rV71-? z1Oq68KpTpBgX$F)J!aKW_kfVy*{1wzKDoD@`Nvy2u4ojcIQ#aV_-eA^AJlB?AcW=~RA*KCZUeFYSADbH z&!e71&Cnm>PI|K@69{a8+`NPndMxQu%~TOsR=kf!^!LFX?%I68Wy$-4rv<(!eU#2ddXU(!L789;{(qhX47_+=h- z`Bjp(!*gwI#o{i+8$6IPQSzJSqTzO~@V)AT>~|&->1sAJ!MK$eoejWX{!V|WWAQWb zIx4a?KL&DJ-_nVP` zUuV*MCNS%b|5O3gMPpZvQA%5-%<{9_%XhaBA%!|S9X+2?B8tEBVR`!EdDab1#~v+MaXqJyiXkS&aILR`$X*oJdZNv&>Q90u}FPZLsQ@ z%RH+n2Qz4gZOAF(F8LAj;DURbnytu$CCYAZO9pCt!2p(Fv7D~uC3Idt`Qi8oerW0- zpp%qu=%OJWNz@j!zd7W{rHSe{p8q{}&OEhghn85;tuPTPLf14)KJ5IoVkyy@wpw#rmPgNGaX`fNvVWTHHcS@ z(WFa*bl*RF$?GD02p3Fg*Hf7q%(IzBS4h6-PyJS6M;oj2=h?lmpMAFCn-wBJVE^qy zn`xO}d^Zmv*FM@M!A-2E`3>4$s8u`gPBq`B58q zeFCBVcJcRbVkRc0^}1L!Fr$`zgv+S;svM(4Kf|!I1|&_eK!+iP;fwKbHrIMx{57D+ z2W6EMi28d_;X3DGh|OB`iWA|hB<^`2=Z&>t;A}0`SXGV`Xjw`KSa(joztv?{CBSe4 zS+-qd+_EaIII^SKr{IdGfQjK*Vj%wZVqcOz`n^M@89uvL$&ql`6I~>|$M)uQfWCD$ z6l+qU9qbo;*?u+;`O5Gp0K6XjdS(Mco;jf~$Mbduw8YV_Kk+~=imA-c zkrvl6ZeTv3lja!0VaW>t10y@Db&OE2l4y)e8y4zBj8H1gzYfRi+2G_MtuszxG7mdr zti+R+OySzZFa`Dcm6PTA(x-sr&rM5|I+(`_B50$>Sm#?$Ni+ywF@4P{>kJ<=AT)nV0dpQwFkpa!_%m`*kAZXl{PEt5|k zf*W%VHd~ye4|ZG6(qRUy%(k+oVTvo-cX;DpsIdS7C68vKnqiyIwct@ zeGx(Hkt&9FEM%SM0fty~MLay*`!?o*Em6YFd{u<-EY<}h#yvy>Jx_IZ=GJT_ekZ=14YA^ ztJP97lx!>^2>0LpAtqJMRkGE`ehuyM2ktHHiCLRS7-N*Y?&O~VJLlY&%(JmqoVq-C zwo#*Wm`(YobK)0)iAD8THFKYX3vnA)J9einI>i@EB}obh!n{&4(srtnsm{-a7V6Jr zM6^B3;;L4xxOhn}n}H@Zcmceyn`~@sT*r^+Z~f{MByK4_sU{wGk+N(EuPrD2@~&H4 z!xnBIgYzehck+BYvPh9Kg@S&?93S9;DeKN>iI#2cl<8ZljO5N3U%eAo$~#j%qv$nX zc#6z-ZiJSKMFW*Ss88k=@kQOiLt`VSCc%&7KXllJ{i$nc|2)R*Zp(6W^mZ~KI+qP| zU)75g%L$pI-6)#8s4?PC=n$QC&$l!uR5D0>j#d}E-5hxAINT7OkKJ$Hrc?8Fnrup* z4Jj~LFnv8(ZTXGoNilBc(~AB!#kKk$tnxAwyFnjfeUzwpiX$=J$qx%fAU$jYKg%!( z2gaHhR8@S(QbXIG+9TbuJ>z1DlNpg_iRTEb%z17U<@;I9N6F{C-{yUnL{K?x-;%br z<6fg}hI=cQO72|cpl3tmZFH}4pjxvXRko1rZvmy^HeH^rtmeVi{&uw*kj>0@46SCFGK*7|v zoddt}9-Ii1?EK_Uf7jZZUKcwbc&6WTZ++*jPUa)G0zcY=wx84Q3s$~jeqbOTC zk8UKGOIN8f$I;&)6gn9|bemN; zP0RjTpZ#_VKH1oR(T1uR@T?;vOC5 zHV$ifhMq%P7B=MF#X>y8hxTD3@zhi2i*t|z!4xrcXrhPHd=Biy;shke=TV{{3Me~} zh-IezUZ+gH{^WT82ot7-)SNFH^K01Ao_rP#b$GH_gu;breHVbxB#;)^@A}ByEBl3Z zVMQewJ16QyEY;-$uX%@}ei|es3z_43ZVTFl2Lby(0mtB~B9tu@6# zN;V}>lNw(XBl&@l!!=()8|tH;;(0b3gv2|0Yve&_;+b>v^|w)>7#C%6(WPjcS}| zhVDz{?`?D6@!De`CoR=y6I09fOKjdD{vAEj?I~Lqv^*SHgV_l4S+O){>k zhe@}y2a9U`GSIl#cXh~d833)DN47N1XO!<410{>$nZcqF=Rmftx%-(|tC#A?Cs+Gz zDOJl^knIlf>B)!p!%iyCZ&0<|xlg!i!PU&5@iPf-8C{lMA2Lo`fuZSj7hQl`d+V<_Q!R4!{{m-@Wpj z6$V{Qu~BOoWT0zG7xss^3hK#eVX(PRY`|JKHXnU1INt5veZ`X_T=4K#zIPpZW}g^OVENNN_VJFa?M5|EH|b(8iR8J7^A)HHCY5IpE?Y)Y2w-7lXU`J94Bt=l{@(94ot&W)FOOM zrROl#1LW{z>0{jMka`h6Pz?7|d?re14~vR#ljusqROgR83Z}1ti^icUEYv#r+Xc22 z47Jkt1sQs(ocYj0+3e2Ndf^Mcy@@#9D@<%rQrv45Y`}A+&=nJtPl1lawYr&MV~yAc zmKY;glj~0lldFqne~?`rN`P zQics?u?Y()IvVm8;KM`pXQe3pJJ=6f^BD&rzUwO>tEp5s zd|UF#=My(rB6&CRPg3vppU@)1i-0vf=7dkbPZYyxOv$O`eV)(VVV!6vdK+i5cbB^D zywkib`S$e_Tf@KZg?g!t;f2m6307^9D+Rqg7={0QpUFJ2%Mb1Syy*!JfQ8W^@BR`! zk>#Q24uzYN(u9Ie19DKf+h;d#o>M;>7to#5sREaBEvgE}t<>w6m-}DnrGM%#;GLa% zE8eKASiJDlp}j!z(!_ZtSlHHHI&P)-ecF(BlQAzc&7&la+MjA|zfi*CZt9X8y0nb} zZY)^MoLYjl%e30hO_hv&S1k3+yQBuI5Zc;EtR2I9p+un#a9SY-g+z>auuB=x@Ss#g z+sHIPNxz@wB+6ra_;$W(r`W?&ek9U@+E&}am1!T~rr%#f#S*;oBKy28U(7QZeP2Sv z9@H+$QQx{HYUj&)e^9{E*-(^gqH@XfAopdQX6u9qfM?NV>M$@&Z#^c|R_vNKJnGAn zr0`dt-f0YaFJhu0#+X?jQz@5D3YW$BR9l zl#T-X3gq6$#jz>Q_T#_lF^72+L_Jle~zdF{Q@6OAIKb4RML^-xgU*v$lGQmHKfhbJT7xKh9%9+HV*aG{q=7xY;i803%J@-ueR+P$=?AGw;C+`)SjQ$FdmFgPs%=0TCh*6zv;Jg*aM zJLJS>21Y=)8~!J$3!cB^2qeSz|KGT7_t0&3$TdqUImgB!eyjPY(iy-*K}ukB>pm@> zNbbyo4WE|3@UU;(sLz5sq+}E1eMwyjK$}Qg!$7?fW*z!pH9#0Y literal 0 HcmV?d00001 diff --git a/geoportal-client/src/test/resources/concessioni/immagine2.png b/geoportal-client/src/test/resources/concessioni/immagine2.png new file mode 100644 index 0000000000000000000000000000000000000000..e777369853edae17f7cc934fa693e0fa9d82f6f9 GIT binary patch literal 5879 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 literal 0 HcmV?d00001 diff --git a/geoportal-client/src/test/resources/concessioni/invio_08_05.csv b/geoportal-client/src/test/resources/concessioni/invio_08_05.csv new file mode 100644 index 0000000..8dc9e6a --- /dev/null +++ b/geoportal-client/src/test/resources/concessioni/invio_08_05.csv @@ -0,0 +1,7 @@ +"Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019","Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019/Gis Sorgenti della Nova","Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019/Gis Sorgenti della Nova/Piante di fase" +"GNA_Aquileia_ExPasqualis_ok","GNA_Aquileia_ExPasqualis_ok/GNA_topografia_Aquileia ex Pasqualis_2019","GNA_Aquileia_ExPasqualis_ok/GNA_topografia_Aquileia ex Pasqualis_2019" +"Jesolo (Ve) - Loc. San Mauro e Torre del Caligo","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/topografia_modificata","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/topografia_modificata" +"Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va","Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va/CONSEGNA_WGS84","Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va/CONSEGNA_WGS84" +"Montecompatri_Tenuta_Castiglione_doc_IVa_2019","Montecompatri_Tenuta_Castiglione_doc_IVa_2019/GNA_topografia_Gabii_Louvre/POSIZIONAMENTO","Montecompatri_Tenuta_Castiglione_doc_IVa_2019/GNA_topografia_Gabii_Louvre/PIANTA FINE SCAVO" +"SAN_CASCIANO_BAGNI","SAN_CASCIANO_BAGNI/POSIZIONAMENTO_EPSG_4326","SAN_CASCIANO_BAGNI" +"Tarquinia_Civita_GNA_2019","Tarquinia_Civita_GNA_2019/Va_D__Topografia_Tarquinia/Posizionamento","Tarquinia_Civita_GNA_2019/Va_D__Topografia_Tarquinia/Posizionamento" \ No newline at end of file diff --git a/geoportal-client/src/test/resources/concessioni/pianta.shp b/geoportal-client/src/test/resources/concessioni/pianta.shp new file mode 100644 index 0000000000000000000000000000000000000000..24ed2bdd3d25b6c8965fc22bc6ee127cde266d34 GIT binary patch literal 96444 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 T@Xw2L6Z^4K`n?uobe;JZkG~-u literal 0 HcmV?d00001 diff --git a/geoportal-client/src/test/resources/concessioni/pos.dbf b/geoportal-client/src/test/resources/concessioni/pos.dbf new file mode 100644 index 0000000000000000000000000000000000000000..eff02b72637ff9a84083e2bd39192053e7087ab2 GIT binary patch literal 461 zcmZRsVBu$BU|>jOhz63FATtFn<_uzS!MPBIUt(@5R0u2|fF{qYV59&8QTcg^K(4Mr vaAJCTX1;+H(e0WJ;|g&7Lmc8U%&G8??s0Qp$8 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 + 4.0.0 + org.gcube.application + geoportal-common + 1.0.7-SNAPSHOT + Geoportal Common + + + + org.gcube.application.cms + gcube-cms-suite + 1.0.0-SNAPSHOT + + + + https://code-repo.d4science.org/gCubeSystem + 1.0 + + + + + + + org.gcube.distribution + gcube-bom + 2.0.1 + pom + import + + + + + + scm:git:${gitBaseUrl}/${project.artifactId}.git + scm:git:${gitBaseUrl}/${project.artifactId}.git + ${gitBaseUrl}/${project.artifactId}.git + + + + + + + + org.projectlombok + lombok + 1.14.8 + + + + + org.gcube.common + authorization-client + + + + + + junit + junit + 4.12 + test + + + + + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.8.8 + + + + + org.gcube.contentmanagement + storage-manager-core + [2.0.0, 3.0.0-SNAPSHOT) + + + org.gcube.contentmanagement + storage-manager-wrapper + [2.0.0, 3.0.0-SNAPSHOT) + + + + + ch.qos.logback + logback-classic + test + + + + + + \ No newline at end of file diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/faults/JsonParseException.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/faults/JsonParseException.java new file mode 100644 index 0000000..f45f8a1 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/faults/JsonParseException.java @@ -0,0 +1,37 @@ +package org.gcube.application.geoportal.common.faults; + +public class JsonParseException extends Exception{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + public JsonParseException() { + super(); + // TODO Auto-generated constructor stub + } + + public JsonParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public JsonParseException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public JsonParseException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public JsonParseException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/BasicJSONObject.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/BasicJSONObject.java new file mode 100644 index 0000000..3ba4e81 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/BasicJSONObject.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model; + +public class BasicJSONObject { + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AbstractRelazione.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AbstractRelazione.java new file mode 100644 index 0000000..d5cab38 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AbstractRelazione.java @@ -0,0 +1,16 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) +public class AbstractRelazione extends AssociatedContent{ + + private String abstractIta; + private String abstractEng; + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AccessPolicy.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AccessPolicy.java new file mode 100644 index 0000000..f17f659 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AccessPolicy.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.legacy; + +public enum AccessPolicy { + + OPEN,RESTRICTED,EMBARGOED; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AssociatedContent.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AssociatedContent.java new file mode 100644 index 0000000..627d244 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/AssociatedContent.java @@ -0,0 +1,132 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; + +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.utils.CollectionsUtils; + +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter + +public abstract class AssociatedContent { + + private String mongo_id; + private long id; + + private AccessPolicy policy; + private String licenseID; + private String titolo; + private LocalDateTime creationTime; + + + public ValidationReport validateForInsertion() { + ValidationReport toReturn=new ValidationReport("Associated Content"); + toReturn.checkMandatory(policy, "Politica di accesso"); + toReturn.checkMandatory(licenseID, "Licenza"); + toReturn.checkMandatory(titolo,"Titolo"); + toReturn.checkMandatory(creationTime, "Creation time"); + return toReturn; + } + + + + @XmlElements({ + @XmlElement(type=GeoServerContent.class), + @XmlElement(type=WorkspaceContent.class), + }) + + private List actualContent=new ArrayList<>(); + + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; +// result = prime * result + ((actualContent == null) ? 0 : actualContent.hashCode()); + result = prime * result + CollectionsUtils.hashCode(actualContent); + + result = prime * result + ((creationTime == null) ? 0 : creationTime.hashCode()); + result = prime * result + (int) (id ^ (id >>> 32)); + result = prime * result + ((licenseID == null) ? 0 : licenseID.hashCode()); + result = prime * result + ((policy == null) ? 0 : policy.hashCode()); + result = prime * result + ((titolo == null) ? 0 : titolo.hashCode()); + return result; + } + + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssociatedContent other = (AssociatedContent) obj; +// if (actualContent == null) { +// if (other.actualContent != null) +// return false; +// } else if (!actualContent.equals(other.actualContent)) +// return false; + if(!CollectionsUtils.equalsCollections(actualContent, other.actualContent)) return false; + + if (creationTime == null) { + if (other.creationTime != null) + return false; + } else if (!creationTime.equals(other.creationTime)) + return false; + if (id != other.id) + return false; + if (licenseID == null) { + if (other.licenseID != null) + return false; + } else if (!licenseID.equals(other.licenseID)) + return false; + if (policy != other.policy) + return false; + if (titolo == null) { + if (other.titolo != null) + return false; + } else if (!titolo.equals(other.titolo)) + return false; + return true; + } + + + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AssociatedContent [id="); + builder.append(id); + builder.append(", policy="); + builder.append(policy); + builder.append(", licenseID="); + builder.append(licenseID); + builder.append(", titolo="); + builder.append(titolo); + builder.append(", creationTime="); + builder.append(creationTime); + + builder.append(", actualContent="); + builder.append(actualContent); + builder.append("]"); + return builder.toString(); + } + + + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/BBOX.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/BBOX.java new file mode 100644 index 0000000..aa88773 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/BBOX.java @@ -0,0 +1,36 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.io.Serializable; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@RequiredArgsConstructor +@Getter +@NoArgsConstructor +@EqualsAndHashCode +@ToString +public class BBOX implements Serializable{ + + /** + * + */ + private static final long serialVersionUID = -159414992596542946L; + + public static BBOX WORLD_EXTENT=new BBOX(90d,180d,-90d,-180d); + + @NonNull + private Double maxLat; + @NonNull + private Double maxLong; + @NonNull + private Double minLat; + @NonNull + private Double minLong; + + +} \ No newline at end of file diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Concessione.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Concessione.java new file mode 100644 index 0000000..f664862 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Concessione.java @@ -0,0 +1,512 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.xml.bind.annotation.XmlRootElement; + +import org.gcube.application.geoportal.common.model.legacy.report.Check; +import org.gcube.application.geoportal.common.model.legacy.report.ConstraintCheck; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.gcube.application.geoportal.common.utils.CollectionsUtils; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) +@XmlRootElement +public class Concessione extends Record{ + + + //Introduzione (descrizione del progetto) + private String introduzione; + + //Descrizione del contenuto + private String descrizioneContenuto; + + //Autori + private List authors; + + //Soggetto che materialmente invia i dati. + private String contributore; + + //Indicare il nome del titolare/i dei dati contenuti nel dataset e/o per sue specifiche parti. + private List titolari; + + private String responsabile; + private String editore; + + + private List fontiFinanziamento; + + //Research Excavation; Archaeology (valori di default) + + private List soggetto; + + + //Referenze bibliografiche, DOI (se esistenti) di risorse correlate all’indagine in oggetto + private List risorseCorrelate; + + + private LocalDateTime dataInizioProgetto; + private LocalDateTime dataFineProgetto; + + private List titolareLicenza; + private List titolareCopyright; + + + + private List paroleChiaveLibere; + + private List paroleChiaveICCD; + + + private Double centroidLat; + private Double centroidLong; + + private RelazioneScavo relazioneScavo; + private AssociatedContent abstractRelazione; + + private List immaginiRappresentative=new ArrayList(); + + private LayerConcessione posizionamentoScavo; + + private List pianteFineScavo=new ArrayList<>(); + + private List genericContent=new ArrayList<>(); + + + public Concessione() { + super(); + setRecordType(RecordType.CONCESSIONE); + } + + + public static class Paths{ + public final static String RELAZIONE="relazione"; + public final static String POSIZIONAMENTO="posizionamentoScavo"; + public final static String PIANTE="piante"; + public final static String IMMAGINI="immagini"; + public final static String ABSTRACT_RELAZIONE="abstract_relazione"; + public static final String MONGO_ID="mongo_id"; + + public final static String piantaByIndex(int index) {return makeByIndex(PIANTE,index);}; + public final static String imgByIndex(int index) {return makeByIndex(IMMAGINI,index);}; + + public final static String piantaById(String id) {return makeByStringField(PIANTE, MONGO_ID, id);}; + public final static String imgById(String id) {return makeByStringField(IMMAGINI, MONGO_ID, id);}; + + + + private static String makeByIndex(String base,int value) {return String.format("%1$s[%2$d]",base,value);} + private static String makeByStringField(String base,String field,String value) + {return String.format("%1$s.%2$s:\"%3$s\"",base,field,value);} + } + + @Override + public AssociatedContent getContentByPath(String path) { + switch(path) { + case Paths.RELAZIONE : return getRelazioneScavo(); + case Paths.POSIZIONAMENTO : return getPosizionamentoScavo(); + case Paths.ABSTRACT_RELAZIONE: return getAbstractRelazione(); + } + if(path.matches("\\w+\\[\\d+\\]")) { + // Array section + String field=path.substring(0,path.lastIndexOf("[")); + Integer index=Integer.parseInt(path.substring(path.lastIndexOf("[")+1,path.lastIndexOf("]"))); + List list=null; + switch (field) { + case Paths.IMMAGINI : list = immaginiRappresentative; break; + case Paths.PIANTE : list = pianteFineScavo; break; + } + return getByIndex(list,index); + + } + if(path.matches("\\w+\\.\\w+\\s*\\:\\s*\\\"\\w+\\\"")) { + // Map section + String field=path.substring(0,path.lastIndexOf(".")); + String subField=path.substring(path.indexOf(".")+1,path.lastIndexOf(":")).trim(); + String value=path.substring(path.indexOf("\"")+1,path.lastIndexOf("\"")); + List list=null; + switch (field) { + case Paths.IMMAGINI : list = immaginiRappresentative; break; + case Paths.PIANTE : list = pianteFineScavo; break; + } + return getByFieldValue(list, subField, value); + } + return null; + } + + private static AssociatedContent getByIndex(List list, int index) { + if(list == null )return null; + return list.get(index); + } + +// NB only mongo_id is supported in this impl. + private static AssociatedContent getByFieldValue(List list, String field, Object value) { + for(AssociatedContent c: list) { + switch(field) { + case Paths.MONGO_ID : { + if(c.getMongo_id()!=null&&c.getMongo_id().equals(value)) + return c; + } + } + } + return null; + } + + + @Override + public void setAtPath(AssociatedContent toSet, String path) { + switch(path) { + case Paths.RELAZIONE : { + setRelazioneScavo((RelazioneScavo) toSet); + break; + } + case Paths.POSIZIONAMENTO : { + setPosizionamentoScavo((LayerConcessione) toSet); + break; + } + case Paths.ABSTRACT_RELAZIONE:{ + setAbstractRelazione((AbstractRelazione)toSet); + break; + } + case Paths.PIANTE : { + if(pianteFineScavo==null)pianteFineScavo=new ArrayList(); + pianteFineScavo.add((LayerConcessione) toSet); + break; + } + case Paths.IMMAGINI: { + if(immaginiRappresentative==null)immaginiRappresentative=new ArrayList(); + pianteFineScavo.add((LayerConcessione) toSet); + break; + } + + //TODO MATCH if()case Paths.PIANTa : return + //TODO MATCH if()case Paths.Img : return + } + + } + + @Override + public ValidationReport validate() { + ValidationReport validator= super.validate(); + + validator.setObjectName("Concessione"); + + setPolicy(AccessPolicy.OPEN); + + + + validator.checkMandatory(authors, "Lista Autori"); + if(validator.checkMandatory(centroidLat, "Latitudine")) + if(centroidLat>90||centroidLat<-90) validator.addMessage(ValidationStatus.ERROR, "Latitudine non valida : "+centroidLat); + + if(validator.checkMandatory(centroidLong, "Longitudine")) + if(centroidLong>180||centroidLong<-180) validator.addMessage(ValidationStatus.ERROR, "Longitudine non valida : "+centroidLong); + + validator.checkMandatory(contributore, "Contributore"); + if(validator.checkMandatory(dataFineProgetto, "Data Fine Progetto") && + validator.checkMandatory(dataInizioProgetto, "Data Inizio Progetto")) + if(dataFineProgetto.isBefore(dataInizioProgetto)) validator.addMessage(ValidationStatus.ERROR, "Data Fine Progetto non può esser prima di Data Inizio Progetto."); + + validator.checkMandatory(descrizioneContenuto, "Descrizione contenuto"); + validator.checkMandatory(editore, "Editore"); + validator.checkMandatory(fontiFinanziamento, "Fonti Finanaziamento"); + validator.checkMandatory(introduzione, "Introduzione"); + validator.checkMandatory(paroleChiaveICCD, "Parole chiave ICCD"); + validator.checkMandatory(paroleChiaveLibere, "Parole chiave libere"); + + validator.checkMandatory(responsabile,"Responsabile"); + + + validator.checkMandatory(titolareCopyright, "Titolare Copyright"); + validator.checkMandatory(titolareLicenza,"Titolare licenza"); + validator.checkMandatory(titolari, "Titolari"); + + + + if(validator.checkMandatory(relazioneScavo, "Relazione scavo")) { + validator.addChild(relazioneScavo.validateForInsertion()); + } + + if(validator.checkMandatory(abstractRelazione,"Abstract Relazione")){ + validator.addChild(abstractRelazione.validateForInsertion()); + } + +// if(immaginiRappresentative!=null) +// +// for(UploadedImage img : immaginiRappresentative) { +// validator.setDefault(img.getSoggetto(),getSoggetto()); +// validator.setDefault(img.getCreationTime(),getCreationTime()); +// validator.setDefault(img.getPolicy(), getPolicy()); +// validator.setDefault(img.getLicenzaID(), getLicenzaID()); +// +// validator.addChild(img.validateForInsertion()); +// } +// + + if(validator.checkMandatory(posizionamentoScavo, "Posizionamento scavo")) { + + ValidationReport posReport=posizionamentoScavo.validateForInsertion(); + posReport.setObjectName("Posizionamento scavo"); + validator.addChild(posReport); + } + + + if(genericContent!=null) + for(OtherContent content:genericContent) + validator.addChild(content.validateForInsertion()); + + if(validator.checkMandatory(pianteFineScavo,"Piante fine scavo")) + for(LayerConcessione l:pianteFineScavo) { + validator.addChild(l.validateForInsertion()); + } + + + + + + return validator; + } + + + @Override + public void setDefaults() { + super.setDefaults(); + setSoggetto(ConstraintCheck.defaultFor(soggetto, Arrays.asList(new String[] {"Research Excavation","Archaeology"})) + .addChecks(Check.collectionSizeMin(2)).evaluate()); + + setDescrizioneContenuto(ConstraintCheck.defaultFor(getDescrizioneContenuto(), + "Relazione di fine scavo e relativo abstract; selezione di immagini rappresentative;" + + " posizionamento topografico dell'area indagata, pianta di fine scavo.").evaluate()); + + + setSoggetto(ConstraintCheck.defaultFor(soggetto, Arrays.asList(new String[] {"Research Excavation","Archaeology"})) + .addChecks(Check.collectionSizeMin(2)).evaluate()); + + setLicenzaID(ConstraintCheck.defaultFor(getLicenzaID(), "CC0-1.0").evaluate()); + + + + if(relazioneScavo!=null) { + relazioneScavo.setTitolo(ConstraintCheck.defaultFor(relazioneScavo.getTitolo(),getNome()+" relazione di scavo").evaluate()); + relazioneScavo.setSoggetto(ConstraintCheck.defaultFor(relazioneScavo.getSoggetto(),getSoggetto()).evaluate()); + relazioneScavo.setCreationTime(ConstraintCheck.defaultFor(relazioneScavo.getCreationTime(),getCreationTime()).evaluate()); + relazioneScavo.setLicenseID(ConstraintCheck.defaultFor(getLicenzaID(), "CC-BY-4.0").evaluate()); + relazioneScavo.setPolicy(getPolicy()); + } + + if(abstractRelazione!=null) { + abstractRelazione.setTitolo(ConstraintCheck.defaultFor(abstractRelazione.getTitolo(),getNome()+" abstract relazione di scavo").evaluate()); + abstractRelazione.setCreationTime(ConstraintCheck.defaultFor(abstractRelazione.getCreationTime(),getCreationTime()).evaluate()); + abstractRelazione.setLicenseID(ConstraintCheck.defaultFor(getLicenzaID(), "CC-BY-4.0").evaluate()); + abstractRelazione.setPolicy(getPolicy()); + } + + + if(immaginiRappresentative!=null) + for(UploadedImage img : immaginiRappresentative) { + img.setSoggetto(ConstraintCheck.defaultFor(img.getSoggetto(),getSoggetto()).evaluate()); + img.setCreationTime(ConstraintCheck.defaultFor(img.getCreationTime(),getCreationTime()).evaluate()); + img.setPolicy(ConstraintCheck.defaultFor(img.getPolicy(),getPolicy()).evaluate()); + img.setLicenseID(ConstraintCheck.defaultFor(img.getLicenseID(),"CC-BY-4.0").evaluate()); + } + + + if(posizionamentoScavo!=null) { + posizionamentoScavo.setTitolo(ConstraintCheck.defaultFor(posizionamentoScavo.getTitolo(), getNome()+" posizionamento scavo").evaluate()); + posizionamentoScavo.setAbstractSection( + ConstraintCheck.defaultFor(posizionamentoScavo.getAbstractSection(),"Posizionamento topografico georeferenziato dell’area interessata dalle indagini").evaluate()); + + posizionamentoScavo.setTopicCategory(ConstraintCheck.defaultFor(posizionamentoScavo.getTopicCategory(), "Society").evaluate()); + posizionamentoScavo.setSubTopic(ConstraintCheck.defaultFor(posizionamentoScavo.getSubTopic(), "Archeology").evaluate()); + + posizionamentoScavo.setParoleChiaveLibere(ConstraintCheck.defaultFor(posizionamentoScavo.getParoleChiaveLibere(),getParoleChiaveLibere()).evaluate()); + posizionamentoScavo.setParoleChiaveICCD(ConstraintCheck.defaultFor(posizionamentoScavo.getParoleChiaveICCD(),getParoleChiaveICCD()).evaluate()); + + //TODO Evaluate + posizionamentoScavo.setBbox(ConstraintCheck.defaultFor(posizionamentoScavo.getBbox(), BBOX.WORLD_EXTENT).evaluate()); + + posizionamentoScavo.setPolicy(ConstraintCheck.defaultFor(posizionamentoScavo.getPolicy(),getPolicy()).evaluate());; + posizionamentoScavo.setLicenseID(ConstraintCheck.defaultFor(posizionamentoScavo.getLicenseID(), "CC-BY-4.0").evaluate()); + posizionamentoScavo.setResponsabile(getResponsabile()); + posizionamentoScavo.setCreationTime(ConstraintCheck.defaultFor(posizionamentoScavo.getCreationTime(), getCreationTime()).evaluate()); + + + } + + + + if(pianteFineScavo!=null) + for(LayerConcessione l:pianteFineScavo) { + + l.setTitolo(ConstraintCheck.defaultFor(l.getTitolo(), getNome()+" pianta fine scavo").evaluate()); + l.setAbstractSection( + ConstraintCheck.defaultFor(l.getAbstractSection(),"Planimetria georeferenziata dell'area indagata al termine delle attività").evaluate()); + + l.setTopicCategory(ConstraintCheck.defaultFor(l.getTopicCategory(), "Society").evaluate()); + l.setSubTopic(ConstraintCheck.defaultFor(l.getSubTopic(), "Archeology").evaluate()); + + l.setParoleChiaveLibere(ConstraintCheck.defaultFor(l.getParoleChiaveLibere(),getParoleChiaveLibere()).evaluate()); + l.setParoleChiaveICCD(ConstraintCheck.defaultFor(l.getParoleChiaveICCD(),getParoleChiaveICCD()).evaluate()); + + + l.setBbox(ConstraintCheck.defaultFor(l.getBbox(), BBOX.WORLD_EXTENT).evaluate()); + + l.setPolicy(ConstraintCheck.defaultFor(l.getPolicy(),getPolicy()).evaluate());; + l.setLicenseID(ConstraintCheck.defaultFor(l.getLicenseID(), "CC-BY-4.0").evaluate()); + l.setResponsabile(getResponsabile()); + l.setCreationTime(ConstraintCheck.defaultFor(l.getCreationTime(), getCreationTime()).evaluate()); + } + + + + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; +// result = prime * result + ((authors == null) ? 0 : authors.hashCode()); + result = prime * result + CollectionsUtils.hashCode(authors); +// result = prime * result + ((fontiFinanaziamento == null) ? 0 : fontiFinanaziamento.hashCode()); + result = prime * result + CollectionsUtils.hashCode(fontiFinanziamento); +// result = prime * result + ((immaginiRappresentative == null) ? 0 : immaginiRappresentative.hashCode()); + result = prime * result + CollectionsUtils.hashCode(immaginiRappresentative); +// result = prime * result + ((paroleChiaveICCD == null) ? 0 : paroleChiaveICCD.hashCode()); + result = prime * result + CollectionsUtils.hashCode(paroleChiaveICCD); +// result = prime * result + ((paroleChiaveLibere == null) ? 0 : paroleChiaveLibere.hashCode()); + result = prime * result + CollectionsUtils.hashCode(paroleChiaveLibere); +// result = prime * result + ((pianteFineScavo == null) ? 0 : pianteFineScavo.hashCode()); + result = prime * result + CollectionsUtils.hashCode(pianteFineScavo); +// result = prime * result + ((risorseCorrelate == null) ? 0 : risorseCorrelate.hashCode()); + result = prime * result + CollectionsUtils.hashCode(risorseCorrelate); +// result = prime * result + ((soggetto == null) ? 0 : soggetto.hashCode()); + result = prime * result + CollectionsUtils.hashCode(soggetto); +// result = prime * result + ((titolari == null) ? 0 : titolari.hashCode()); + result = prime * result + CollectionsUtils.hashCode(titolari); + + result = prime * result + ((centroidLat == null) ? 0 : centroidLat.hashCode()); + result = prime * result + ((centroidLong == null) ? 0 : centroidLong.hashCode()); + result = prime * result + ((contributore == null) ? 0 : contributore.hashCode()); + result = prime * result + ((dataFineProgetto == null) ? 0 : dataFineProgetto.hashCode()); + result = prime * result + ((dataInizioProgetto == null) ? 0 : dataInizioProgetto.hashCode()); + result = prime * result + ((descrizioneContenuto == null) ? 0 : descrizioneContenuto.hashCode()); + result = prime * result + ((editore == null) ? 0 : editore.hashCode()); + result = prime * result + ((genericContent == null) ? 0 : genericContent.hashCode()); + result = prime * result + ((introduzione == null) ? 0 : introduzione.hashCode()); + + result = prime * result + ((posizionamentoScavo == null) ? 0 : posizionamentoScavo.hashCode()); + result = prime * result + ((relazioneScavo == null) ? 0 : relazioneScavo.hashCode()); + result = prime * result + ((responsabile == null) ? 0 : responsabile.hashCode()); + result = prime * result + ((titolareCopyright == null) ? 0 : titolareCopyright.hashCode()); + result = prime * result + ((titolareLicenza == null) ? 0 : titolareLicenza.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Concessione other = (Concessione) obj; + + if(!CollectionsUtils.equalsCollections(authors, other.authors)) return false; + if (!CollectionsUtils.equalsCollections(fontiFinanziamento, other.fontiFinanziamento)) return false; + if (!CollectionsUtils.equalsCollections(immaginiRappresentative, other.immaginiRappresentative)) return false; + if (!CollectionsUtils.equalsCollections(paroleChiaveICCD, other.paroleChiaveICCD)) return false; + if (!CollectionsUtils.equalsCollections(paroleChiaveLibere, other.paroleChiaveLibere)) return false; + if (!CollectionsUtils.equalsCollections(pianteFineScavo, other.pianteFineScavo)) return false; + if (!CollectionsUtils.equalsCollections(risorseCorrelate, other.risorseCorrelate)) return false; + if (!CollectionsUtils.equalsCollections(soggetto, other.soggetto)) return false; + if (!CollectionsUtils.equalsCollections(titolari, other.titolari)) return false; + + if (centroidLat == null) { + if (other.centroidLat != null) + return false; + } else if (!centroidLat.equals(other.centroidLat)) + return false; + if (centroidLong == null) { + if (other.centroidLong != null) + return false; + } else if (!centroidLong.equals(other.centroidLong)) + return false; + if (contributore == null) { + if (other.contributore != null) + return false; + } else if (!contributore.equals(other.contributore)) + return false; + if (dataFineProgetto == null) { + if (other.dataFineProgetto != null) + return false; + } else if (!dataFineProgetto.equals(other.dataFineProgetto)) + return false; + if (dataInizioProgetto == null) { + if (other.dataInizioProgetto != null) + return false; + } else if (!dataInizioProgetto.equals(other.dataInizioProgetto)) + return false; + if (descrizioneContenuto == null) { + if (other.descrizioneContenuto != null) + return false; + } else if (!descrizioneContenuto.equals(other.descrizioneContenuto)) + return false; + if (editore == null) { + if (other.editore != null) + return false; + } else if (!editore.equals(other.editore)) + return false; + if (genericContent == null) { + if (other.genericContent != null) + return false; + } else if (!genericContent.equals(other.genericContent)) + return false; + if (introduzione == null) { + if (other.introduzione != null) + return false; + } else if (!introduzione.equals(other.introduzione)) + return false; + + if (posizionamentoScavo == null) { + if (other.posizionamentoScavo != null) + return false; + } else if (!posizionamentoScavo.equals(other.posizionamentoScavo)) + return false; + if (relazioneScavo == null) { + if (other.relazioneScavo != null) + return false; + } else if (!relazioneScavo.equals(other.relazioneScavo)) + return false; + if (responsabile == null) { + if (other.responsabile != null) + return false; + } else if (!responsabile.equals(other.responsabile)) + return false; + if (titolareCopyright == null) { + if (other.titolareCopyright != null) + return false; + } else if (!titolareCopyright.equals(other.titolareCopyright)) + return false; + if (titolareLicenza == null) { + if (other.titolareLicenza != null) + return false; + } else if (!titolareLicenza.equals(other.titolareLicenza)) + return false; + + return true; + } + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/GeoServerContent.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/GeoServerContent.java new file mode 100644 index 0000000..88fd57a --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/GeoServerContent.java @@ -0,0 +1,28 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.util.ArrayList; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) + +@EqualsAndHashCode(callSuper=true) +public class GeoServerContent extends PersistedContent{ + + //GeoServer Details + private String geoserverHostName; + private String geoserverPath; + + private List fileNames=new ArrayList(); + + + private String workspace; + private String store; + private String featureType; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/InputStreamDescriptor.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/InputStreamDescriptor.java new file mode 100644 index 0000000..2b04ec7 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/InputStreamDescriptor.java @@ -0,0 +1,21 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.io.InputStream; + +import lombok.Data; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; + +@Data +@Getter +@Setter +public class InputStreamDescriptor{ + + @NonNull + private InputStream stream; + @NonNull + private String filename; + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/LayerConcessione.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/LayerConcessione.java new file mode 100644 index 0000000..fa3362a --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/LayerConcessione.java @@ -0,0 +1,152 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.util.List; + +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.utils.CollectionsUtils; + +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter + +public class LayerConcessione extends SDILayerDescriptor{ + + + //meta + private String layerUUID; + private Long layerID; + + //layer + private String layerName; + private String wmsLink; + private String workspace; + + + //1.Identificazione + private String abstractSection; + + //2.Classificazione + private String topicCategory; + + //3.Keyword + private String subTopic; + + //4. Delimitazione geographica + private BBOX bbox; + + //5. Temporal + + private List paroleChiaveLibere; + + private List paroleChiaveICCD; + + //6. Quality + private String valutazioneQualita; + + private String metodoRaccoltaDati; + + private String scalaAcquisizione; + + //8. Responsabili + private List authors; + + private String responsabile; + + + + + @Override + public ValidationReport validateForInsertion() { + ValidationReport toReturn=super.validateForInsertion(); + toReturn.setObjectName("Layer Concessione"); + toReturn.checkMandatory(abstractSection, "Abstract"); + toReturn.checkMandatory(subTopic, "Categoria (Topic)"); + //TODO +// toReturn.checkMandatory(bbox, "Bounding Box"); + + toReturn.checkMandatory(valutazioneQualita, "Valutazione della qualita"); + toReturn.checkMandatory(metodoRaccoltaDati, "Metodo raccolta dati"); + toReturn.checkMandatory(scalaAcquisizione, "Scala acquisizione"); + toReturn.checkMandatory(authors, "Autori"); + return toReturn; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((abstractSection == null) ? 0 : abstractSection.hashCode()); +// result = prime * result + ((authors == null) ? 0 : authors.hashCode()); + result = prime * result + CollectionsUtils.hashCode(authors); + + + result = prime * result + ((bbox == null) ? 0 : bbox.hashCode()); + result = prime * result + ((metodoRaccoltaDati == null) ? 0 : metodoRaccoltaDati.hashCode()); + result = prime * result + ((scalaAcquisizione == null) ? 0 : scalaAcquisizione.hashCode()); + result = prime * result + ((subTopic == null) ? 0 : subTopic.hashCode()); + result = prime * result + ((topicCategory == null) ? 0 : topicCategory.hashCode()); + result = prime * result + ((valutazioneQualita == null) ? 0 : valutazioneQualita.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + LayerConcessione other = (LayerConcessione) obj; + if (abstractSection == null) { + if (other.abstractSection != null) + return false; + } else if (!abstractSection.equals(other.abstractSection)) + return false; +// if (authors == null) { +// if (other.authors != null) +// return false; +// } else if (!authors.equals(other.authors)) +// return false; + if(!CollectionsUtils.equalsCollections(authors, other.authors)) return false; + + + if (bbox == null) { + if (other.bbox != null) + return false; + } else if (!bbox.equals(other.bbox)) + return false; + if (metodoRaccoltaDati == null) { + if (other.metodoRaccoltaDati != null) + return false; + } else if (!metodoRaccoltaDati.equals(other.metodoRaccoltaDati)) + return false; + if (scalaAcquisizione == null) { + if (other.scalaAcquisizione != null) + return false; + } else if (!scalaAcquisizione.equals(other.scalaAcquisizione)) + return false; + if (subTopic == null) { + if (other.subTopic != null) + return false; + } else if (!subTopic.equals(other.subTopic)) + return false; + if (topicCategory == null) { + if (other.topicCategory != null) + return false; + } else if (!topicCategory.equals(other.topicCategory)) + return false; + if (valutazioneQualita == null) { + if (other.valutazioneQualita != null) + return false; + } else if (!valutazioneQualita.equals(other.valutazioneQualita)) + return false; + return true; + } + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/OtherContent.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/OtherContent.java new file mode 100644 index 0000000..7aeda2f --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/OtherContent.java @@ -0,0 +1,15 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) + +public class OtherContent extends AssociatedContent { + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/PersistedContent.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/PersistedContent.java new file mode 100644 index 0000000..9905d66 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/PersistedContent.java @@ -0,0 +1,36 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @Type(value = GeoServerContent.class), + @Type(value = WorkspaceContent.class), + }) +public abstract class PersistedContent { + + //Generic Info + private long id; + + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersistedContent [id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Record.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Record.java new file mode 100644 index 0000000..4efecac --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/Record.java @@ -0,0 +1,74 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.time.LocalDateTime; + +import org.gcube.application.geoportal.common.model.legacy.report.ConstraintCheck; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode + +public abstract class Record { + + private String mongo_id; + + //Generic Info + private long id; + + private RecordType recordType; + private String version; + private String licenzaID; + + private AccessPolicy policy; + //Nome del progetto + private String nome; + + + //Storage Info + private String folderId; + + //Accounting + private LocalDateTime lastUpdateTime; + private String lastUpdateUser; + private LocalDateTime creationTime; + private String creationUser; + + + + private ValidationReport report; + + public ValidationReport validate() { + + setDefaults(); + ValidationReport validator=new ValidationReport("Core metadata"); + validator.checkMandatory(getRecordType(), "Record Type"); + + validator.checkMandatory(getNome(), "Nome"); + setReport(validator); + + return getReport(); + } + + public void setDefaults() { + LocalDateTime now=LocalDateTime.now(); + + setVersion(ConstraintCheck.defaultFor(getVersion(),"1.0.0").evaluate()); + setPolicy(ConstraintCheck.defaultFor(getPolicy(), AccessPolicy.OPEN).evaluate()); + + setLastUpdateTime(ConstraintCheck.defaultFor(getLastUpdateTime(),now).evaluate()); + setCreationTime(ConstraintCheck.defaultFor(getCreationTime(),now).evaluate()); + setLicenzaID(ConstraintCheck.defaultFor(getLicenzaID(),"CC-BY").evaluate()); + } + + + public abstract AssociatedContent getContentByPath(String path); + public abstract void setAtPath(AssociatedContent toSet,String path); + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RecordType.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RecordType.java new file mode 100644 index 0000000..267cc3f --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RecordType.java @@ -0,0 +1,6 @@ +package org.gcube.application.geoportal.common.model.legacy; + +public enum RecordType { + + CONCESSIONE,MOSI,MOPR +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RelazioneScavo.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RelazioneScavo.java new file mode 100644 index 0000000..6cc5e88 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/RelazioneScavo.java @@ -0,0 +1,60 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.util.List; + +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.utils.CollectionsUtils; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) +public class RelazioneScavo extends AssociatedContent { + + + + + private List responsabili; + private List soggetto; + + + @Override + public ValidationReport validateForInsertion() { + + ValidationReport toReturn=super.validateForInsertion(); + toReturn.setObjectName("Relazione Scavo"); + toReturn.checkMandatory(responsabili, "Responsabili"); + toReturn.checkMandatory(soggetto, "Soggetto"); + return toReturn; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + CollectionsUtils.hashCode(responsabili); + result = prime * result + CollectionsUtils.hashCode(soggetto); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + RelazioneScavo other = (RelazioneScavo) obj; + + if(!CollectionsUtils.equalsCollections(responsabili, other.responsabili)) return false; + + if(!CollectionsUtils.equalsCollections(soggetto, other.soggetto)) return false; + + return true; + } + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/SDILayerDescriptor.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/SDILayerDescriptor.java new file mode 100644 index 0000000..c3af358 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/SDILayerDescriptor.java @@ -0,0 +1,25 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter +public abstract class SDILayerDescriptor extends AssociatedContent{ + + + public abstract Long getLayerID(); + public abstract void setLayerID(Long layerID); + public abstract String getLayerUUID(); + public abstract void setLayerUUID(String layerUUID); + public abstract String getLayerName(); + public abstract void setLayerName(String layerName); + public abstract String getWmsLink(); + public abstract void setWmsLink(String wmsLink); + + public abstract void setWorkspace(String workspace); + public abstract String getWorkspace(); + public abstract BBOX getBbox(); + public abstract void setBbox(BBOX toSet); +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/UploadedImage.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/UploadedImage.java new file mode 100644 index 0000000..3b5b413 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/UploadedImage.java @@ -0,0 +1,73 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import java.util.List; + +import org.gcube.application.geoportal.common.utils.CollectionsUtils; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) + +public class UploadedImage extends AssociatedContent { + + + private String didascalia; + private String format; + + private List responsabili; + private List soggetto; + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((didascalia == null) ? 0 : didascalia.hashCode()); + result = prime * result + ((format == null) ? 0 : format.hashCode()); +// result = prime * result + ((responsabili == null) ? 0 : responsabili.hashCode()); + result = prime * result + CollectionsUtils.hashCode(responsabili); +// result = prime * result + ((soggetto == null) ? 0 : soggetto.hashCode()); + result = prime * result + CollectionsUtils.hashCode(soggetto); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + UploadedImage other = (UploadedImage) obj; + if (didascalia == null) { + if (other.didascalia != null) + return false; + } else if (!didascalia.equals(other.didascalia)) + return false; + if (format == null) { + if (other.format != null) + return false; + } else if (!format.equals(other.format)) + return false; +// if (responsabili == null) { +// if (other.responsabili != null) +// return false; +// } else if (!responsabili.equals(other.responsabili)) +// return false; + if(!CollectionsUtils.equalsCollections(responsabili, other.responsabili)) return false; + +// if (soggetto == null) { +// if (other.soggetto != null) +// return false; +// } else if (!soggetto.equals(other.soggetto)) +// return false; + if(!CollectionsUtils.equalsCollections(soggetto, other.soggetto)) return false; + + return true; + } + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/WorkspaceContent.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/WorkspaceContent.java new file mode 100644 index 0000000..5229d2b --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/WorkspaceContent.java @@ -0,0 +1,19 @@ +package org.gcube.application.geoportal.common.model.legacy; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper=true) + +@EqualsAndHashCode(callSuper=true) +public class WorkspaceContent extends PersistedContent{ + + private String mimetype; + private String storageID; + private String link; + private String name; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Check.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Check.java new file mode 100644 index 0000000..d5e2e27 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Check.java @@ -0,0 +1,56 @@ +package org.gcube.application.geoportal.common.model.legacy.report; + +import java.util.Collection; + +public interface Check{ + + public static Check collectionSizeMax(final int upperBound) { + return new Check() { + int size=0; + @Override + public boolean isOk(Object toCheck) { + int size=((Collection)toCheck).size(); + return size<=upperBound; + } + @Override + public String getMessage() { + return "Numero elementi : "+size+" [max : "+upperBound+"]"; + } + }; + } + + public static Check collectionSizeMin(final int lowerBound) { + return new Check() { + int size=0; + @Override + public boolean isOk(Object toCheck) { + int size=((Collection)toCheck).size(); + return size>=lowerBound; + } + @Override + public String getMessage() { + return "Numero elementi : "+size+" [min : "+lowerBound+"]"; + } + }; + } + + public static Check collectionSize(final int lowerBound, final int upperBound) { + return new Check() { + int size=0; + + @Override + public boolean isOk(Object toCheck) { + size=((Collection)toCheck).size(); + return size<=upperBound&&size>=lowerBound; + } + + @Override + public String getMessage() { + return "Numero elementi : "+size+" [min : "+lowerBound+" max : "+upperBound+"]"; + } + }; + } + + public boolean isOk(Object toCheck); + public String getMessage(); +} \ No newline at end of file diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Checks.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Checks.java new file mode 100644 index 0000000..e5d57ae --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/Checks.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.legacy.report; + +public class Checks { + +// public static final Check + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/ConstraintCheck.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/ConstraintCheck.java new file mode 100644 index 0000000..558d108 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/legacy/report/ConstraintCheck.java @@ -0,0 +1,103 @@ +package org.gcube.application.geoportal.common.model.legacy.report; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ConstraintCheck { + + public static ConstraintCheck defaultFor(T toCheck,T defaultValue){ + return new ConstraintCheck(toCheck).withDefault(defaultValue); + } + + + private T theObject; + private String fieldLabel; + private String message; + private boolean error=false; + private boolean mandatory; + private T theDefault; + + private List additionalChecks=new ArrayList(); + + public ConstraintCheck(T theObject,Check...checks){ + this.theObject=theObject; + for(Check c:checks) + additionalChecks.add(c); + check(); + + } + public ConstraintCheck withDefault(T theDefault){ + this.theDefault=theDefault; + return this; + } + public ConstraintCheck addChecks(Check...checks){ + for(Check c:checks) + additionalChecks.add(c); + return this; + } + public ConstraintCheck(T theObject,String fieldLabel,Check...checks){ + this.theObject=theObject; + this.fieldLabel=fieldLabel; + for(Check c:checks) + additionalChecks.add(c); + check(); + } + + public T evaluate() { + check(); + T result=theObject; + if(isError()) { + result=theDefault; + if(theDefault!=null && theDefault instanceof Collection) { + Collection defaultCollection=(Collection) theDefault; + ArrayList target=new ArrayList(defaultCollection); + result=(T) target; + } + } + return result; + + } + + private void check() { + if(theObject==null) { + this.setMessage(fieldLabel+" è un campo obbligatorio."); + this.setError(true); + }else + if((theObject instanceof String) && ((String) theObject).isEmpty()) { + this.setMessage(fieldLabel+" non può essere vuoto."); + this.setError(true); + }else + if((theObject instanceof Collection)) { + Collection toCheckCollection=(Collection) theObject; + if(toCheckCollection.isEmpty()) { + this.setMessage("La lista "+fieldLabel+" non può essere vuota."); + this.setError(true); + } + + + Boolean containsError=false; + Object[] array=toCheckCollection.toArray(new Object[toCheckCollection.size()]); + for(int i=0;i errorMessages=new ArrayList(); + private List warningMessages=new ArrayList(); + private List children=new ArrayList(); + + + public ValidationStatus getStatus() { + if(!errorMessages.isEmpty()) return ValidationStatus.ERROR; + + boolean isWarning=(!warningMessages.isEmpty()); + for(ValidationReport obj : children) { + ValidationStatus status=obj.getStatus(); + if(status.equals(ValidationStatus.ERROR)) return ValidationStatus.ERROR; + if(status.equals(ValidationStatus.WARNING)) isWarning=true; + } + if(isWarning) return ValidationStatus.WARNING; + return ValidationStatus.PASSED; + } + + public void addChild(ValidationReport obj) { + children.add(obj); + } + + public void addMessage(ValidationStatus status,String message) { + switch (status) { + case ERROR: + errorMessages.add(message); + break; + case WARNING : + warningMessages.add(message); + default: + break; + } + } + + + + + public boolean checkMandatory(T toCheck,String name,Check...checks) { + ConstraintCheck check=new ConstraintCheck(toCheck,name,checks); + if(check.isError()) + this.addMessage(ValidationStatus.ERROR, check.getMessage()); + return !check.isError(); + } + + + public ValidationReport(String objectName) { + super(); + this.objectName = objectName; + } + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/DefaultCompiler.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/DefaultCompiler.java new file mode 100644 index 0000000..019f08a --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/DefaultCompiler.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.profile; + +public class DefaultCompiler { + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Field.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Field.java new file mode 100644 index 0000000..dd59a37 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Field.java @@ -0,0 +1,11 @@ +package org.gcube.application.geoportal.common.model.profile; + +import lombok.Getter; + +@Getter +public class Field { + + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/FieldMapping.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/FieldMapping.java new file mode 100644 index 0000000..be913ee --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/FieldMapping.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.profile; + +public class FieldMapping { + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IndexDefinition.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IndexDefinition.java new file mode 100644 index 0000000..67ee4dd --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IndexDefinition.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.profile; + +public class IndexDefinition { + + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IsoMapper.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IsoMapper.java new file mode 100644 index 0000000..1cbd4b8 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/IsoMapper.java @@ -0,0 +1,7 @@ +package org.gcube.application.geoportal.common.model.profile; + +public class IsoMapper { + + private String className; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Profile.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Profile.java new file mode 100644 index 0000000..258c5ab --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Profile.java @@ -0,0 +1,24 @@ +package org.gcube.application.geoportal.common.model.profile; + +import java.util.List; + +import javax.xml.bind.annotation.XmlRootElement; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@XmlRootElement +public class Profile{ + + private String name; + private String id; + private List fields; + + private List defaultCompilers; + private List validators; + private IsoMapper isoMapper; + private List centroidsMapping; + private List indexes; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Validator.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Validator.java new file mode 100644 index 0000000..d161f5d --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/profile/Validator.java @@ -0,0 +1,5 @@ +package org.gcube.application.geoportal.common.model.profile; + +public class Validator { + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Centroid.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Centroid.java new file mode 100644 index 0000000..ee752e9 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Centroid.java @@ -0,0 +1,9 @@ +package org.gcube.application.geoportal.common.model.project; + +public class Centroid { + + private Double x; + private Double y; + private Double z; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Project.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Project.java new file mode 100644 index 0000000..c1801f8 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Project.java @@ -0,0 +1,43 @@ +package org.gcube.application.geoportal.common.model.project; + +import org.gcube.application.geoportal.common.model.BasicJSONObject; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class Project { + + +/** + * Project{ + _id: + profile_id: + publication :{ + creation_time: + creation_user: + last_update_time: + last_update_user: + version : + license : + policy : + status : VALID, + PUBLISHED,INVALID + document : {.....} + centroid : { + x: + y: + z:} + } + + * + */ + + private String _id; + private String profile_id; + private Status status; + private Object document; + private Centroid centroid; + private PublicationDetails publication; + + private String json; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/PublicationDetails.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/PublicationDetails.java new file mode 100644 index 0000000..3656a12 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/PublicationDetails.java @@ -0,0 +1,22 @@ +package org.gcube.application.geoportal.common.model.project; + +import java.time.LocalDateTime; + +import lombok.Data; + +@Data +public class PublicationDetails { + + public static enum Policy{ + OPEN,RESTRICTED,EMBARGOED; + } + + private LocalDateTime creation_time; + private String creation_user; + private LocalDateTime last_update_time; + private String last_update_user; + private String version; + private String license; + private String policy; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Status.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Status.java new file mode 100644 index 0000000..0783b0a --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/Status.java @@ -0,0 +1,11 @@ +package org.gcube.application.geoportal.common.model.project; + +import java.util.List; + +public class Status { + + + private StatusPhase phase; + private List messages; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StatusPhase.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StatusPhase.java new file mode 100644 index 0000000..522532c --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StatusPhase.java @@ -0,0 +1,13 @@ +package org.gcube.application.geoportal.common.model.project; + +public enum StatusPhase { + + DRAFT, + UNDER_VALIDATION, + INVALID, + VALID, + UNDER_PUBLICATION, + PUBLICATION_ERROR, + PUBLISHED + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StoredFile.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StoredFile.java new file mode 100644 index 0000000..1309362 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/project/StoredFile.java @@ -0,0 +1,4 @@ +package org.gcube.application.geoportal.common.model.project; + + + diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/AddSectionToConcessioneRequest.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/AddSectionToConcessioneRequest.java new file mode 100644 index 0000000..2f18109 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/AddSectionToConcessioneRequest.java @@ -0,0 +1,21 @@ +package org.gcube.application.geoportal.common.model.rest; + +import java.util.List; + +import javax.xml.bind.annotation.XmlRootElement; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.gcube.application.geoportal.common.rest.TempFile; + +@XmlRootElement +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AddSectionToConcessioneRequest { + + private String destinationPath; + private List streams; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/Configuration.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/Configuration.java new file mode 100644 index 0000000..029618a --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/Configuration.java @@ -0,0 +1,15 @@ +package org.gcube.application.geoportal.common.model.rest; + +import lombok.Data; + +@Data +public class Configuration { + + // Index (postgis + layer) Configuration + public PostgisIndexDescriptor index; + + + + // Mongo DB Configuration + // TBD +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/DatabaseConnection.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/DatabaseConnection.java new file mode 100644 index 0000000..f03d8bf --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/DatabaseConnection.java @@ -0,0 +1,16 @@ +package org.gcube.application.geoportal.common.model.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DatabaseConnection { + + private String user; + private String pwd; + private String url; + +} \ No newline at end of file diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/PostgisIndexDescriptor.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/PostgisIndexDescriptor.java new file mode 100644 index 0000000..9156f2f --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/PostgisIndexDescriptor.java @@ -0,0 +1,13 @@ +package org.gcube.application.geoportal.common.model.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; + + +@Data +@AllArgsConstructor +public class PostgisIndexDescriptor { + + public DatabaseConnection postgisDBIndex; + public String wmsLink; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/QueryRequest.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/QueryRequest.java new file mode 100644 index 0000000..ba054bc --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/rest/QueryRequest.java @@ -0,0 +1,28 @@ +package org.gcube.application.geoportal.common.model.rest; + +import lombok.Data; +import org.bson.Document; + +import java.util.List; + +@Data +public class QueryRequest { + + @Data + public static class PagedRequest{ + private int offset; + private int Limit; + } + + @Data + public static class OrderedRequest { + public static enum Direction {ASCENDING,DESCENDING} + private Direction direction; + private String json; + private List fields; + } + private Document filter; + private Document projection; + private OrderedRequest ordering; + private PagedRequest paging; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ConcessioniI.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ConcessioniI.java new file mode 100644 index 0000000..ee13b34 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ConcessioniI.java @@ -0,0 +1,10 @@ +package org.gcube.application.geoportal.common.rest; + +public interface ConcessioniI { + + + public String create(String toCreate) throws Exception; + public String readById(String id) throws Exception; + public String getAll() throws Exception; + public String addSection(String id,String updated) throws Exception; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java new file mode 100644 index 0000000..3ed42f4 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java @@ -0,0 +1,44 @@ +package org.gcube.application.geoportal.common.rest; + +public class InterfaceConstants { + + public static final String NAMESPACE="http://gcube-system.org/namespaces/data/sdi-service"; + + public static final String APPLICATION_BASE_PATH="geoportal-service"; + public static final String APPLICATION_PATH="/srv"; + public static final String SERVICE_CLASS="Application"; + public static final String SERVICE_NAME="GeoPortal"; + + public static final class Methods{ + public static final String PROFILES="profiles"; + public static final String SECTIONS="sections"; + public static final String PROJECTS="projects"; + + public static final String CONCESSIONI="concessioni"; + public static final String MONGO_CONCESSIONI="mongo-concessioni"; + + + public static final String PUBLISH_PATH="publish"; + public static final String REGISTER_FILES_PATH="registerFiles"; + public static final String DELETE_FILES_PATH="deleteFiles"; + public static final String CONFIGURATION_PATH="configuration"; + public static final String SEARCH_PATH="search"; + public static final String QUERY_PATH="query"; + + } + + public static final class Parameters{ + public static final String PROJECT_ID="project_id"; + public static final String SECTION_ID="section_id"; + public static final String PROFILE_ID="profile_id"; + +// //INVESTIGATE CAPABILITIES +// public static final String ORDER_BY="order_by"; +// public static final String LIMIT="limit"; +// public static final String OFFSET="offset"; + public static final String FORCE="force"; + + } + + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/MongoConcessioni.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/MongoConcessioni.java new file mode 100644 index 0000000..e7e107e --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/MongoConcessioni.java @@ -0,0 +1,32 @@ +package org.gcube.application.geoportal.common.rest; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.model.rest.Configuration; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; + +import java.util.Iterator; + +public interface MongoConcessioni { + + public Concessione createNew(Concessione c) throws Exception; + public void deleteById(String id) throws Exception; + public void deleteById(String id,Boolean force) throws Exception; + public Concessione getById(String id) throws Exception; + public Iterator getList()throws Exception; + public Concessione publish(String id) throws Exception; + public Concessione registerFileSet(String id, AddSectionToConcessioneRequest request) throws Exception; + public Concessione cleanFileSet(String id, String path) throws Exception; + public Concessione update(String id, String jsonUpdate) throws Exception; + public Concessione replace(Concessione replacement) throws Exception; + + public void unPublish(String id)throws Exception; + + + public Configuration getCurrentConfiguration()throws Exception; + public Iterator search(String filter)throws Exception; + public Iterator query(QueryRequest request) throws Exception; + public String queryForJSON(QueryRequest request) throws Exception; + + public Iterator queryForType(QueryRequest request,Class clazz) throws Exception; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ProjectsI.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ProjectsI.java new file mode 100644 index 0000000..6ff8a00 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/ProjectsI.java @@ -0,0 +1,19 @@ +package org.gcube.application.geoportal.common.rest; + +import org.gcube.application.geoportal.common.model.project.Project; + +import java.util.Iterator; + +public interface ProjectsI { + + + public Iterator getAll() throws Exception; + public Iterator getByProfile(String profileId) throws Exception; + public Project getById(String profileId,String id) throws Exception; + public Iterator getByFilter(String filter)throws Exception; + public Iterator getByFilter(String filter, String profileId)throws Exception; + public Project registrNew(String profileId, String jsonDocument)throws Exception; + public Project update(String profileId, String projectId,String jsonDocument) throws Exception; + public void deleteById(String profileId, String projectId)throws Exception; + public void deleteById(String profileId, String projectId, Boolean force)throws Exception; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/TempFile.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/TempFile.java new file mode 100644 index 0000000..3477b5c --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/TempFile.java @@ -0,0 +1,15 @@ +package org.gcube.application.geoportal.common.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TempFile { + + private String id; + private String filename; + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/CollectionsUtils.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/CollectionsUtils.java new file mode 100644 index 0000000..a4071a4 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/CollectionsUtils.java @@ -0,0 +1,44 @@ +package org.gcube.application.geoportal.common.utils; + +import java.util.Collection; +import java.util.Iterator; + +public class CollectionsUtils { + + public static boolean equalsCollections(Collection a, Collection b) { + + if(a==null&&b!=null) + return false; + if(a!=null&&b==null) + return false; + + if(a==null&&b==null) + return true; + + if(a.size()!=b.size()) + return false; + + Iterator itA=a.iterator(); + Iterator itB=a.iterator(); + while(itA.hasNext()) { + if(!itA.next().equals(itB.next())) + return false; + } + + return true; + } + + public static int hashCode(Collection a) { + if(a==null || a.isEmpty()) + return 0; + + final int prime = 31; + int result = 1; + + Iterator it=a.iterator(); + while(it.hasNext()) + result=prime*result+it.next().hashCode(); + return result; + } + +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/ContextUtils.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/ContextUtils.java new file mode 100644 index 0000000..9984053 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/ContextUtils.java @@ -0,0 +1,41 @@ +package org.gcube.application.geoportal.common.utils; + +import static org.gcube.common.authorization.client.Constants.authorizationService; + +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.scope.api.ScopeProvider; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ContextUtils { + + + public static String getCurrentScope(){ + try{ + String token=SecurityTokenProvider.instance.get(); + log.debug("Token is : "+token); + if(token==null) throw new Exception("Security Token is null"); + AuthorizationEntry entry = authorizationService().get(token); + return entry.getContext(); + }catch(Exception e ){ + log.debug("Unable to resolve token, checking scope provider..",e); + return ScopeProvider.instance.get(); + } + } + + + public static String getCurrentCaller(){ + try{ + String token=SecurityTokenProvider.instance.get(); + log.debug("Token is : "+token); + if(token==null) throw new Exception("Security Token is null"); + AuthorizationEntry entry = authorizationService().get(token); + return entry.getClientInfo().getId(); + }catch(Exception e ){ + log.debug("Unable to resolve token, checking scope provider..",e); + return "Unidentified data-transfer user"; + } + } +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/Files.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/Files.java new file mode 100644 index 0000000..b730201 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/Files.java @@ -0,0 +1,48 @@ +package org.gcube.application.geoportal.common.utils; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Paths; + +public class Files { + + public static File getFileFromResources(String fileName) { + + ClassLoader classLoader =Files.class.getClassLoader(); + + URL resource = classLoader.getResource(fileName); + if (resource == null) { + throw new IllegalArgumentException("file is not found!"); + } else { + return new File(resource.getFile()); + } + + } + + + public static String readFileAsString(String path, Charset encoding) + throws IOException + { + byte[] encoded = java.nio.file.Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + + + public static String getName(String path) { + + return path.substring((path.contains(File.separator)?path.lastIndexOf(File.separator)+1:0) + ,(path.contains(".")?path.lastIndexOf("."):path.length())); + + } + + public static String fixFilename(String toFix) { + if(toFix.contains(".")) { + String prefix=toFix.substring(toFix.lastIndexOf(".")); + toFix=toFix.substring(0,toFix.lastIndexOf(".")); + return toFix.toLowerCase().replaceAll("[\\*\\+\\/\\\\ \\[\\]\\(\\)\\.\\\"\\:\\;\\|]","_")+prefix; + } + return toFix.toLowerCase().replaceAll("[\\*\\+\\/\\\\ \\[\\]\\(\\)\\.\\\"\\:\\;\\|]","_"); + } +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/StorageUtils.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/StorageUtils.java new file mode 100644 index 0000000..84ecb52 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/utils/StorageUtils.java @@ -0,0 +1,55 @@ +package org.gcube.application.geoportal.common.utils; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.UUID; + +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.contentmanagement.blobstorage.service.IClient; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; +import org.gcube.contentmanager.storageclient.wrapper.AccessType; +import org.gcube.contentmanager.storageclient.wrapper.MemoryType; +import org.gcube.contentmanager.storageclient.wrapper.StorageClient; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class StorageUtils { + + public static final IClient getClient(){ + return new StorageClient(InterfaceConstants.SERVICE_CLASS, InterfaceConstants.SERVICE_NAME, ContextUtils.getCurrentCaller(), AccessType.SHARED, MemoryType.VOLATILE).getClient(); + } + + private IClient client; + public StorageUtils() { + client=getClient(); + } + + //return Id + public TempFile putOntoStorage(InputStream source,String filename) throws RemoteBackendException, FileNotFoundException{ + log.debug("Uploading source "+filename); + String id=client.put(true).LFile(source).RFile(getUniqueString()); + return new TempFile(id,filename); + } + + public static final boolean checkStorageId(String id){ + return getClient().getHttpUrl().RFile(id)!=null; + } + + public static final String getUrlById(String id){ + IClient client=getClient(); + log.debug("Id is "+id); + return client.getHttpUrl().RFile(id); + } + + public static final void removeById(String id){ + IClient client=getClient(); + client.remove().RFile(id); + } + + + public static final String getUniqueString(){ + return UUID.randomUUID().toString(); + } +} diff --git a/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/PathsTest.java b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/PathsTest.java new file mode 100644 index 0000000..01b3295 --- /dev/null +++ b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/PathsTest.java @@ -0,0 +1,67 @@ +package org.gcube.application.geoportal.common.model; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.junit.Test; + + +public class PathsTest { + + + @Test + public void embeddedPaths() { + Concessione c=TestModel.prepareConcessione(); + c=TestModel.setIds(c); + + + + LayerConcessione p=(LayerConcessione) c.getContentByPath(Paths.POSIZIONAMENTO); + assertEquals(c.getPosizionamentoScavo(), p); + + RelazioneScavo rel=(RelazioneScavo) c.getContentByPath(Paths.RELAZIONE); + assertEquals(c.getRelazioneScavo(), rel); + + for(int i=0;i0); + + } + + @Test + public void readConcessione() throws JsonProcessingException, IOException { + Concessione concessione=mapper.readerFor(Concessione.class).readValue( + Files.getFileFromResources("Concessione.json")); + System.out.println("Concessione is "+concessione.toString()); + + } + + @Test + public void generic() throws JsonProcessingException, IOException { + Concessione conc=TestModel.prepareConcessione(); + conc.validate(); + full(conc); + } + + + public void full(Object obj) throws JsonProcessingException, IOException { + String asString=mapper.writeValueAsString(obj); + Object other=mapper.readerFor(obj.getClass()).readValue(asString); + + } +} diff --git a/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java new file mode 100644 index 0000000..8bce974 --- /dev/null +++ b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/TestModel.java @@ -0,0 +1,130 @@ +package org.gcube.application.geoportal.common.model; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +import org.gcube.application.geoportal.common.model.legacy.*; + +public class TestModel { + + private static final String rnd() { + return UUID.randomUUID().toString().replace("-", "_"); + } + + + public static final Concessione setIds(Concessione c) { + c.setMongo_id(rnd()); + c.getRelazioneScavo().setMongo_id(rnd()); + c.getPosizionamentoScavo().setMongo_id(rnd()); + c.getPianteFineScavo().forEach((LayerConcessione l)->{l.setMongo_id(rnd());}); + c.getImmaginiRappresentative().forEach((UploadedImage i)->{i.setMongo_id(rnd());}); + return c; + } + + public static Concessione prepareEmptyConcessione() { + Concessione concessione=new Concessione(); + + // Generic fields + + // Concessione fields + + + + + concessione.setNome("MONGO Italia, forse, ma su "); + concessione.setIntroduzione("This is my MONGO project"); + concessione.setDescrizioneContenuto("It contains this and that"); + + concessione.setAuthors(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + + concessione.setContributore("Contrib 1"); + concessione.setTitolari(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + concessione.setResponsabile("Someone"); + concessione.setEditore("Editore"); + + concessione.setFontiFinanziamento(Arrays.asList(new String[] {"Big pharma","Pentagon"})); + + + concessione.setSoggetto(Arrays.asList(new String[] {"Research Excavation","Archeology"})); + + + concessione.setDataInizioProgetto(LocalDateTime.now()); + concessione.setDataFineProgetto(LocalDateTime.now()); + + concessione.setLicenzaID("CC-BY"); + + concessione.setTitolareLicenza(Arrays.asList(new String[] {"Qualcun altro"})); + concessione.setTitolareCopyright(Arrays.asList(new String[] {"Chiedilo in giro"})); + + concessione.setParoleChiaveLibere(Arrays.asList(new String[] {"Robba","Stuff"})); + concessione.setParoleChiaveICCD(Arrays.asList(new String[] {"vattelapesca","somthing something"})); + + + concessione.setCentroidLat(43.0); //N-S + concessione.setCentroidLong(9.0); //E-W + + return concessione; + } + + public static Concessione prepareConcessione() { + + Concessione concessione=prepareEmptyConcessione(); + + + + // Attachments + + // Relazione scavo + RelazioneScavo relScavo=new RelazioneScavo(); + + relScavo.setResponsabili(concessione.getAuthors()); + + // Abstract + AbstractRelazione abstractRel=new AbstractRelazione(); + abstractRel.setAbstractEng("This is the abstract"); + abstractRel.setAbstractIta("Abstract ita"); + concessione.setAbstractRelazione(abstractRel); + + + + concessione.setRelazioneScavo(relScavo); + //Immagini rappresentative + ArrayList imgs=new ArrayList<>(); + for(int i=0;i<5;i++) { + UploadedImage img=new UploadedImage(); + img.setTitolo("My image number "+i); + img.setDidascalia("You can see my image number "+i); + img.setFormat("TIFF"); + img.setCreationTime(LocalDateTime.now()); + img.setResponsabili(concessione.getAuthors()); + imgs.add(img); + + } + concessione.setImmaginiRappresentative(imgs); + //Posizionamento + LayerConcessione posizionamento=new LayerConcessione(); + posizionamento.setValutazioneQualita("Secondo me si"); + posizionamento.setMetodoRaccoltaDati("Fattobbene"); + posizionamento.setScalaAcquisizione("1:10000"); + posizionamento.setAuthors(concessione.getAuthors()); + concessione.setPosizionamentoScavo(posizionamento); + + // Piante fine scavo + ArrayList piante=new ArrayList(); + for(int i=0;i<4;i++) { + LayerConcessione pianta=new LayerConcessione(); + pianta.setValutazioneQualita("Secondo me si"); + pianta.setMetodoRaccoltaDati("Fattobbene"); + pianta.setScalaAcquisizione("1:10000"); + pianta.setAuthors(concessione.getAuthors()); + pianta.setPolicy(AccessPolicy.RESTRICTED); + piante.add(pianta); + } + concessione.setPianteFineScavo(piante); + + return concessione; + } + +} diff --git a/geoportal-common/src/test/resources/Concessione.json b/geoportal-common/src/test/resources/Concessione.json new file mode 100644 index 0000000..f17b3c3 --- /dev/null +++ b/geoportal-common/src/test/resources/Concessione.json @@ -0,0 +1,747 @@ +{ + "authors": [ + "Some one", + "Some, oneelse" + ], + "centroidLat": 43.0, + "centroidLong": 9.0, + "contributore": "Contrib 1", + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "creationUser": "NO NAME", + "dataFineProgetto": [ + 2020, + 11, + 26, + 13, + 14, + 17, + 6000000 + ], + "dataInizioProgetto": [ + 2020, + 11, + 26, + 13, + 14, + 16, + 994000000 + ], + "descrizioneContenuto": "It contains this and that", + "editore": "Editore", + "folderId": "5f14252f-55df-4c9a-94d9-9ad190efbe6a", + "fontiFinanziamento": [ + "Big pharma", + "Pentagon" + ], + "genericContent": [], + "id": 8, + "immaginiRappresentative": [ + { + "actualContent": [ + { + "associated": null, + "id": 187, + "link": "https://data.dev.d4science.org/shub/E_VGFQSExUR1BmdzVHNjI5ZjFJeXE1TUFpWEkxUGllelIyWVZndndKZ3pBaGNPWlNGczdFaUNtMHVZaEd0dXc2Rw==", + "mimetype": "image/png", + "storageID": "6765deab-b157-494f-83e2-299032e1904c", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 0", + "format": "TIFF", + "id": 67, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 0", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 188, + "link": "https://data.dev.d4science.org/shub/E_NDVyQnBiMFJ1VDliYS83eEJvYS9vb1JrZ2tQN2orY3ZXNUJia25McFFtNlMrSS91NHNBN3pPVUNzTGN2aE9JOQ==", + "mimetype": "image/png", + "storageID": "4b4fb9f9-b96d-485d-80c9-713fad8a5c9b", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 1", + "format": "TIFF", + "id": 68, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 1", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 189, + "link": "https://data.dev.d4science.org/shub/E_YWw0TXVKa29KZFJOeDMwVlRDc29xMW0zSExYdGRJdmxZWEtJSGRVZ3ZBRVEwTnc1cEdMNG1iVVRBN2JDVG9WYQ==", + "mimetype": "image/png", + "storageID": "2690d6bc-2833-4797-81bb-1c1865f34f34", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 2", + "format": "TIFF", + "id": 69, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 2", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 190, + "link": "https://data.dev.d4science.org/shub/E_TU9ha0lGMTRhTDl6S2IzWDNmUWROVXZjZzJiSmNrS1BjZ3gycUJvVEI2b0ZDaGxuK1dUWmlsNW1DZE1ESUJPbA==", + "mimetype": "image/png", + "storageID": "244b0fe0-8f53-469a-9c5e-ac63e37d4622", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 3", + "format": "TIFF", + "id": 70, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 3", + "type": "UploadedImage" + }, + { + "actualContent": [ + { + "associated": null, + "id": 191, + "link": "https://data.dev.d4science.org/shub/E_NXZYZnV3RlFTYzRWOTBOMWt0cmFtVUtTU20xWTBQb3daRVV6RU8zTDc5RU9USnZVL3Y1VmpRNHNaajMyelVTMQ==", + "mimetype": "image/png", + "storageID": "6fab2082-08af-4899-9730-462b011b517d", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 35, + 177000000 + ], + "didascalia": "You can see my image number 4", + "format": "TIFF", + "id": 71, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "My image number 4", + "type": "UploadedImage" + } + ], + "introduzione": "This is my project", + "lastUpdateTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "lastUpdateUser": "NO NAME", + "licenzaID": "CC-BY", + "nome": "Italia, forse", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "pianteFineScavo": [ + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 196, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 193, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 195, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 194, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 192, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 72, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 198, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 197, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 200, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 199, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 201, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 73, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 202, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 204, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 205, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 203, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 206, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 74, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + { + "abstractSection": "Planimetria georeferenziata dell'area indagata al termine delle attività", + "actualContent": [ + { + "associated": null, + "id": 209, + "link": "https://data.dev.d4science.org/shub/E_RFZ0V2NUaDB5bCt4bGo5MnByZXZoRW5rWnRSWXFoRzdnQmxqTHlYSlhCeTU5RHZ3S2Ywa0ZDUCtUOCtZK3VkdQ==", + "mimetype": "text/plain", + "storageID": "f0f1fff4-6ee4-47c5-92be-2fd2c1ab5687", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 211, + "link": "https://data.dev.d4science.org/shub/E_aTZqVlNzRlRVSUFVZXhBNVp2bDdYMDlRTnNwVlRGRXNqMlI3MkxTdXlrWVFJeDF0L3pLemtaTDBwWFA3TVJKUw==", + "mimetype": "application/x-shapefile", + "storageID": "debd7027-56fe-4cff-ae87-b0df94421035", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 207, + "link": "https://data.dev.d4science.org/shub/E_cFZ6MFgvR1gzWmx0d2hvRmVnVTBQTEszdG1seEN3d2tBcHNsYkFXVlhKRUtucHZKb24xakEwa1IwQWJCTjc3cA==", + "mimetype": "application/x-dbf", + "storageID": "2c542d5a-ca89-42bb-87c9-1bceb8f0f03b", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 208, + "link": "https://data.dev.d4science.org/shub/E_UWpOTUYvRTRvV2hCYS8wQ3BaSG5GdG00MXdyeVg2M3dnZk5ZSEd3a0RxdWs0YUJTalFMK1JJQ2x5dFlySTZ4Lw==", + "mimetype": "text/plain", + "storageID": "d44b073f-6828-4dfd-a1fc-1d5eed93abc5", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 210, + "link": "https://data.dev.d4science.org/shub/E_SXUrWi9OZW9MMXpTSzdraGhveXNpaVo3b0Fid1loc1BEdnlqVmdGaWd1U2czb1YxdDhPeHNBWmovV21xRmxXVw==", + "mimetype": "application/x-shapefile", + "storageID": "4e184a77-915b-4ebd-bcb8-08ac8138069b", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 75, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "RESTRICTED", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse pianta fine scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + } + ], + "policy": "OPEN", + "posizionamentoScavo": { + "abstractSection": "Posizionamento topografico georeferenziato dell’area interessata dalle indagini", + "actualContent": [ + { + "associated": null, + "id": 214, + "link": "https://data.dev.d4science.org/shub/E_UjJoQkw2a0VlR3djQnVYMlNaME40VkdLL3pxV21DNmRrWXVZUlFhMk53aXJORVJmM29pcHpPdVc4aHZLUTRwcg==", + "mimetype": "text/plain", + "storageID": "fac45dad-4840-4fda-b613-0bfd831d8720", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 213, + "link": "https://data.dev.d4science.org/shub/E_T0lsR09LbVdqMExWT1ZwZWpZSW4zUXBqZlV2bCt6d3hMbnc5UDBvRW45eENONzB4cXNtZ216cXZFNWVzdjU0eg==", + "mimetype": "text/plain", + "storageID": "d599e90f-26e0-4b27-b85e-bedd286ff2d7", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 215, + "link": "https://data.dev.d4science.org/shub/E_U2NhMXUvMzRycE9YYkRmbHphdC82QlJJN2FEeVd0Y1FEQmxwSjNmcWRTUDZoZHhVQ1VPdjRMVVdOVDcxNTh5Yw==", + "mimetype": "application/x-shapefile", + "storageID": "f1080875-7f01-4658-9758-9388262ad12c", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 216, + "link": "https://data.dev.d4science.org/shub/E_RlBWRW5lUG9nbncxSC9ZQlRURmFSeTQ3Q0ZqdDhOK2luV01uWjUxMTZ4OHREa29US2t3K21RblpjaXFMTWtSSA==", + "mimetype": "application/x-shapefile", + "storageID": "6b7eeb40-0cd1-4fe5-97b5-b7091e6b7531", + "type": "WorkspaceContent" + }, + { + "associated": null, + "id": 212, + "link": "https://data.dev.d4science.org/shub/E_aHQxcDhoRGN0QXdXZTkwUmtKRlJsUFVqWjM4STY3U0JkVGU1L3l1a2t5WkhFWlc4blpoa0QxaDVRaHZCOXR2Nw==", + "mimetype": "application/x-dbf", + "storageID": "9af11436-2241-4a03-a95b-96849272bc25", + "type": "WorkspaceContent" + } + ], + "authors": [ + "Some one", + "Some, oneelse" + ], + "bbox": { + "maxLat": 90.0, + "maxLong": 180.0, + "minLat": -90.0, + "minLong": -180.0 + }, + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 76, + "layerID": null, + "layerName": null, + "layerUUID": null, + "licenseID": "CC-BY", + "metodoRaccoltaDati": "Fattobbene", + "paroleChiaveICCD": [ + "vattelapesca", + "somthing something" + ], + "paroleChiaveLibere": [ + "Robba", + "Stuff" + ], + "policy": "OPEN", + "record": null, + "responsabile": "Someone", + "scalaAcquisizione": "1:10000", + "subTopic": "Archeology", + "titolo": "Italia, forse posizionamento scavo", + "topicCategory": "Society", + "type": "LayerConcessione", + "valutazioneQualita": "Secondo me si", + "wmsLink": null + }, + "recordType": "CONCESSIONE", + "relazioneScavo": { + "abstractSection": "simple abstract section", + "actualContent": [ + { + "associated": null, + "id": 217, + "link": "https://data.dev.d4science.org/shub/E_a0JGWWNsY0tFc29CVC8xUW1ROUJhMnVaaWwxNk5TTk5TRlgvQW1tbkpLdDBuNFU3Rkg2VmlFVW53TEUzaEM4aA==", + "mimetype": "application/pdf", + "storageID": "e0cd721a-89e4-437d-9fba-2bfc197cb4c1", + "type": "WorkspaceContent" + } + ], + "creationTime": [ + 2020, + 11, + 26, + 13, + 14, + 37, + 342000000 + ], + "id": 77, + "licenseID": "CC-BY", + "policy": "OPEN", + "record": null, + "responsabili": [ + "Some one", + "Some, oneelse" + ], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolo": "Italia, forse relazione di scavo", + "type": "RelazioneScavo" + }, + "responsabile": "Someone", + "risorseCorrelate": [], + "soggetto": [ + "Research Excavation", + "Archeology" + ], + "titolareCopyright": ["Chiedilo in giro"], + "titolareLicenza": ["Qualcun altro"], + "titolari": [ + "Some one", + "Some, oneelse" + ], + "version": "1.0.0" +} \ No newline at end of file diff --git a/geoportal-common/src/test/resources/ProfileConcessioni.json b/geoportal-common/src/test/resources/ProfileConcessioni.json new file mode 100644 index 0000000..ed42208 --- /dev/null +++ b/geoportal-common/src/test/resources/ProfileConcessioni.json @@ -0,0 +1,29 @@ +{ + "name" : "Concessioni Profile", + "_id" : "", + "fields" :[ + { "name" : "introduzione", "label" : "Introduzione", "type" : "STRING"}, + { "label" : "Descrizione Contenuto", "type" : "STRING"}, + { "label" : "Autori", "type" : "STRING", "cardinality" : "MULTIPLE"}, + { "label" : "Contributore", "type" : "STRING"}, + { "label" : "Titolari", "type" : "STRING", "cardinality" : "MULTIPLE"}, + + { "name":"relazioneScavo", "label" : "Relazione di Scavo", "type" : "DOCUMENT"}, + { "name":"posizionameno", + "label" : "Posizionamento Scavo", + "type" : "DOCUMENT", + "fields":[ + { "label" : "Descrizione Contenuto", "type" : "STRING"}, + { "label" : "Autori", "type" : "STRING", "cardinality" : "MULTIPLE"}, + { "label" : "Contributore", "type" : "STRING"} + ] + } + ], + + "validators" : [ + {"name":"Validator Concessioni", + "type":"JAVA", + "qName":"org.gcube.application.concessioni.Validator"} + ] + +} \ No newline at end of file diff --git a/geoportal-service/CHANGELOG.md b/geoportal-service/CHANGELOG.md new file mode 100644 index 0000000..e0c69ed --- /dev/null +++ b/geoportal-service/CHANGELOG.md @@ -0,0 +1,36 @@ +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# Changelog for org.gcube.application.geoportal-service + +## [v1.0.6-SNAPSHOT] 2021-09-20 +Refactored repositories + + +## [v1.0.5-SNAPSHOT] 2021-07-23 +Upgrade to gcube-smartgears-bom 2.1.0 +Fix register postgis table layer +Added PostgisIndexRecordManager + +## [v1.0.4] 2020-11-11 +Mongo integration with Concessione +Project interface +TempFile management +WorkspaceContent and publication for Concessioni-over-mongo + +## [v1.0.3] 2020-11-11 +Fixed HTTP method + +## [v1.0.2] 2020-11-11 +Delete method +Excluded upper bound release gCube 5 + + +## [v1.0.1] 2020-11-11 + +Project interface + +## [v1.0.0] 2020-11-11 + +First release + + diff --git a/geoportal-service/FUNDING.md b/geoportal-service/FUNDING.md new file mode 100644 index 0000000..9e48b94 --- /dev/null +++ b/geoportal-service/FUNDING.md @@ -0,0 +1,26 @@ +# Acknowledgments + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - [DILIGENT](https://cordis.europa.eu/project/id/004260) (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - [D4Science](https://cordis.europa.eu/project/id/212488) (grant no. 212488); + - [D4Science-II](https://cordis.europa.eu/project/id/239019) (grant no.239019); + - [ENVRI](https://cordis.europa.eu/project/id/283465) (grant no. 283465); + - [iMarine](https://cordis.europa.eu/project/id/283644) (grant no. 283644); + - [EUBrazilOpenBio](https://cordis.europa.eu/project/id/288754) (grant no. 288754). +- the H2020 research and innovation programme + - [SoBigData](https://cordis.europa.eu/project/id/654024) (grant no. 654024); + - [PARTHENOS](https://cordis.europa.eu/project/id/654119) (grant no. 654119); + - [EGI-Engage](https://cordis.europa.eu/project/id/654142) (grant no. 654142); + - [ENVRI PLUS](https://cordis.europa.eu/project/id/654182) (grant no. 654182); + - [BlueBRIDGE](https://cordis.europa.eu/project/id/675680) (grant no. 675680); + - [PerformFISH](https://cordis.europa.eu/project/id/727610) (grant no. 727610); + - [AGINFRA PLUS](https://cordis.europa.eu/project/id/731001) (grant no. 731001); + - [DESIRA](https://cordis.europa.eu/project/id/818194) (grant no. 818194); + - [ARIADNEplus](https://cordis.europa.eu/project/id/823914) (grant no. 823914); + - [RISIS 2](https://cordis.europa.eu/project/id/824091) (grant no. 824091); + - [EOSC-Pillar](https://cordis.europa.eu/project/id/857650) (grant no. 857650); + - [Blue Cloud](https://cordis.europa.eu/project/id/862409) (grant no. 862409); + - [SoBigData-PlusPlus](https://cordis.europa.eu/project/id/871042) (grant no. 871042); diff --git a/geoportal-service/LICENSE.md b/geoportal-service/LICENSE.md new file mode 100644 index 0000000..3af0507 --- /dev/null +++ b/geoportal-service/LICENSE.md @@ -0,0 +1,312 @@ +# European Union Public Licence V. 1.1 + + +EUPL © the European Community 2007 + + +This European Union Public Licence (the “EUPL”) applies to the Work or Software +(as defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Original Work is provided under the terms of this Licence when the Licensor +(as defined below) has placed the following notice immediately following the +copyright notice for the Original Work: + +Licensed under the EUPL V.1.1 + +or has expressed by any other mean his willingness to license under the EUPL. + + + +## 1. Definitions + +In this Licence, the following terms have the following meaning: + +- The Licence: this Licence. + +- The Original Work or the Software: the software distributed and/or + communicated by the Licensor under this Licence, available as Source Code and + also as Executable Code as the case may be. + +- Derivative Works: the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not + define the extent of modification or dependence on the Original Work required + in order to classify a work as a Derivative Work; this extent is determined by + copyright law applicable in the country mentioned in Article 15. + +- The Work: the Original Work and/or its Derivative Works. + +- The Source Code: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- The Executable Code: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- The Licensor: the natural or legal person that distributes and/or communicates + the Work under the Licence. + +- Contributor(s): any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- The Licensee or “You”: any natural or legal person who makes any usage of the + Software under the terms of the Licence. + +- Distribution and/or Communication: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, on-line or off-line, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + + + +## 2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, +sub-licensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, reproduce the Work, modify +- the Original Work, and make Derivative Works based upon the Work, communicate +- to the public, including the right to make available or display the Work or +- copies thereof to the public and perform publicly, as the case may be, the +- Work, distribute the Work or copies thereof, lend and rent the Work or copies +- thereof, sub-license rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + + + +## 3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute and/or communicate the Work. + + + +## 4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Original Work or Software, of the exhaustion of those rights or of other +applicable limitations thereto. + + + +## 5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: the Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes and/or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes and/or communicates copies of the +Original Works or Derivative Works based upon the Original Work, this +Distribution and/or Communication will be done under the terms of this Licence +or of a later version of this Licence unless the Original Work is expressly +distributed only under this version of the Licence. The Licensee (becoming +Licensor) cannot offer or impose any additional terms or conditions on the Work +or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes and/or Communicates Derivative +Works or copies thereof based upon both the Original Work and another work +licensed under a Compatible Licence, this Distribution and/or Communication can +be done under the terms of this Compatible Licence. For the sake of this clause, +“Compatible Licence” refers to the licences listed in the appendix attached to +this Licence. Should the Licensee’s obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing and/or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available for +as long as the Licensee continues to distribute and/or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + + + +## 6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + + + +## 7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +contributors. It is not a finished work and may therefore contain defects or +“bugs” inherent to this type of software development. + +For the above reason, the Work is provided under the Licence on an “as is” basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + + + +## 8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + + + +## 9. Additional agreements + +While distributing the Original Work or Derivative Works, You may choose to +conclude an additional agreement to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or services +consistent with this Licence. However, in accepting such obligations, You may +act only on your own behalf and on your sole responsibility, not on behalf of +the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred +by, or claims asserted against such Contributor by the fact You have accepted +any such warranty or additional liability. + + + +## 10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon “I agree” +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution and/or Communication by You of the Work or copies thereof. + + + +## 11. Information to the public + +In case of any Distribution and/or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + + + +## 12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + + + +## 13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work licensed hereunder. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed and/or reformed so as necessary to make +it valid and enforceable. + +The European Commission may publish other linguistic versions and/or new +versions of this Licence, so far this is required and reasonable, without +reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + + + +## 14. Jurisdiction + +Any litigation resulting from the interpretation of this License, arising +between the European Commission, as a Licensor, and any Licensee, will be +subject to the jurisdiction of the Court of Justice of the European Communities, +as laid down in article 238 of the Treaty establishing the European Community. + +Any litigation arising between Parties, other than the European Commission, and +resulting from the interpretation of this License, will be subject to the +exclusive jurisdiction of the competent court where the Licensor resides or +conducts its primary business. + + + +## 15. Applicable Law + +This Licence shall be governed by the law of the European Union country where +the Licensor resides or has his registered office. + +This licence shall be governed by the Belgian law if: + +- a litigation arises between the European Commission, as a Licensor, and any +- Licensee; the Licensor, other than the European Commission, has no residence +- or registered office inside a European Union country. + + + +## Appendix + + + +“Compatible Licences” according to article 5 EUPL are: + + +- GNU General Public License (GNU GPL) v. 2 + +- Open Software License (OSL) v. 2.1, v. 3.0 + +- Common Public License v. 1.0 + +- Eclipse Public License v. 1.0 + +- Cecill v. 2.0 + diff --git a/geoportal-service/README.md b/geoportal-service/README.md new file mode 100644 index 0000000..d20b2c8 --- /dev/null +++ b/geoportal-service/README.md @@ -0,0 +1,53 @@ +GeoPortal - Service +-------------------------------------------------- + +GeoPortal - Service is the main component operating a gCube GeoPortal. It is a SmartGears web application based on top of gCube SDI + +## Built with +* [gCube SDI] (https://gcube.wiki.gcube-system.org/gcube/) - The gCube SDI +* [gCube SmartGears] (https://gcube.wiki.gcube-system.org/gcube/SmartGears) - The gCube SmartGears framework +* [OpenJDK](https://openjdk.java.net/) - The JDK used +* [JAX-RS](https://github.com/eclipse-ee4j/jaxrs-api) - Java™ API for RESTful Web Services +* [Jersey](https://jersey.github.io/) - JAX-RS runtime +* [Maven](https://maven.apache.org/) - Dependency Management + +## Documentation + +Documentation can be found [here](https://gcube.wiki.gcube-system.org/gcube/GeoPortal). + +## Change log + +See [CHANGELOG.md](CHANGELOG.md). + +## License + +This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LICENSE.md) file for details. + +## About the gCube Framework +This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an +open-source software toolkit used for building and operating Hybrid Data +Infrastructures enabling the dynamic deployment of Virtual Research Environments +by favouring the realisation of reuse oriented policies. + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - DILIGENT (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - D4Science (grant no. 212488); + - D4Science-II (grant no.239019); + - ENVRI (grant no. 283465); + - iMarine(grant no. 283644); + - EUBrazilOpenBio (grant no. 288754). +- the H2020 research and innovation programme + - SoBigData (grant no. 654024); + - PARTHENOS (grant no. 654119); + - EGIEngage (grant no. 654142); + - ENVRIplus (grant no. 654182); + - BlueBRIDGE (grant no. 675680); + - PerformFish (grant no. 727610); + - AGINFRAplus (grant no. 731001); + - DESIRA (grant no. 818194); + - ARIADNEplus (grant no. 823914); + - RISIS2 (grant no. 824091); + diff --git a/geoportal-service/pom.xml b/geoportal-service/pom.xml new file mode 100644 index 0000000..afde409 --- /dev/null +++ b/geoportal-service/pom.xml @@ -0,0 +1,230 @@ + + 4.0.0 + org.gcube.application + geoportal-service + 1.0.6-SNAPSHOT + Geoportal Service + war + + + org.gcube.application.cms + gcube-cms-suite + 1.0.0-SNAPSHOT + + + + https://code-repo.d4science.org/gCubeSystem + 1.0 + + + + + + + scm:git:${gitBaseUrl}/${project.artifactId}.git + scm:git:${gitBaseUrl}/${project.artifactId}.git + ${gitBaseUrl}/${project.artifactId}.git + + + + + + + + org.gcube.distribution + gcube-smartgears-bom + 2.1.0 + pom + import + + + + + + + + + + org.gcube.core + common-smartgears + + + + + + + + + + javax.servlet + javax.servlet-api + 3.0.1 + + + + javax.ws.rs + javax.ws.rs-api + + + + org.glassfish.jersey.containers + jersey-container-servlet + + + org.javassist + javassist + + + + + + + + + org.gcube.application + geoportal-common + [1.0.0,2.0.0) + + + + + + org.gcube.spatial.data + gis-interface + [2.4.6,3.0.0) + + + + + net.postgis + postgis-jdbc + 2.5.0 + + + + + org.gcube.spatial.data + gcube-geoserver-client + [1.0.0-SNAPSHOT,) + + + + + org.gcube.data.transfer + data-transfer-library + [1.2.1,2.0.0] + + + + + org.gcube.common + storagehub-client-library + [1.0.0,2.0.0) + + + + + + + org.mongodb + mongo-java-driver + 3.6.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.slf4j + slf4j-api + + + + org.projectlombok + lombok + 1.14.8 + + + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-simple + test + + + + + + junit + junit + 4.11 + test + + + org.apache.logging.log4j + log4j-slf4j18-impl + 2.13.3 + test + + + + + ch.qos.logback + logback-classic + test + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/AppManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/AppManager.java new file mode 100644 index 0000000..517218a --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/AppManager.java @@ -0,0 +1,18 @@ +package org.gcube.application.geoportal.service; + +public class AppManager { + +// implements ApplicationManager{ +//} +// +// @Override +// public void onInit() { +// ImplementationProvider.get().shutdown(); +// } +// +// @Override +// public void onShutdown() { +// ImplementationProvider.get().shutdown(); +// } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java new file mode 100644 index 0000000..7fb5902 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java @@ -0,0 +1,39 @@ +package org.gcube.application.geoportal.service; + +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.service.rest.ConcessioniOverMongo; +import org.gcube.application.geoportal.service.rest.Profiles; +import org.gcube.application.geoportal.service.rest.Projects; +import org.gcube.application.geoportal.service.rest.Sections; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.ApplicationPath; + +@ApplicationPath(InterfaceConstants.APPLICATION_PATH) +public class GeoPortalService extends ResourceConfig{ + + + + public GeoPortalService() { + super(); + //Register interrfaces +// registerClasses(Concessioni.class); + registerClasses(ConcessioniOverMongo.class); + registerClasses(Projects.class); + registerClasses(Sections.class); + registerClasses(Profiles.class); + + + + + + JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); + provider.setMapper(Serialization.mapper); + register(provider); + + } + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/InterfaceUtils.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/InterfaceUtils.java new file mode 100644 index 0000000..8155d1c --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/InterfaceUtils.java @@ -0,0 +1,5 @@ +package org.gcube.application.geoportal.service; + +public class InterfaceUtils { + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java new file mode 100644 index 0000000..8b46015 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java @@ -0,0 +1,15 @@ +package org.gcube.application.geoportal.service; + + +public class ServiceConstants { + + public static final String SE_GNA_DB_FLAG="GNA_DB"; + public static final String SE_GNA_DB_CATEGORY="Database"; + + public static final String MONGO_SE_PLATFORM="mongodb"; + public static final String MONGO_SE_GNA_FLAG="internal-db"; + + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java new file mode 100644 index 0000000..b1c069b --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java @@ -0,0 +1,63 @@ +package org.gcube.application.geoportal.service.engine; + +//import org.gcube.application.geoportal.managers.AbstractRecordManager; +//import org.gcube.application.geoportal.managers.EMFProvider; + +import lombok.Getter; +import lombok.Setter; +import lombok.Synchronized; +import org.gcube.application.geoportal.service.engine.providers.*; + +public class ImplementationProvider { + + private static ImplementationProvider instance=null; + + @Synchronized + public static ImplementationProvider get() { + if(instance==null) { + instance=new ImplementationProvider(); + } + return instance; + } + + + @Getter + @Setter + private MongoConnectionProvider mongoConnectionProvider=new MongoConnectionProvider(); + + @Getter + @Setter + private MongoClientProvider mongoClientProvider=new MongoClientProvider(); + + + @Getter + @Setter + private StorageClientProvider storageProvider=new StorageClientProvider(); + + @Getter + @Setter + private PostgisConnectionProvider dbProvider=new PostgisConnectionProvider(); + + +// @Getter +// @Setter +// private EMFProvider emfProvider=new ScopedEMFProvider(); + + + @Getter + @Setter + private StorageHubProvider sHubProvider=new StorageHubProvider(); + + public void shutdown() { + // Stop JPA +// AbstractRecordManager.shutdown(); + mongoConnectionProvider.shustdown(); + mongoClientProvider.shustdown(); + } + + public void startup() { +// AbstractRecordManager.setDefaultProvider(emfProvider); + mongoConnectionProvider.init(); + mongoClientProvider.init(); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java new file mode 100644 index 0000000..d1099f7 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java @@ -0,0 +1,410 @@ +package org.gcube.application.geoportal.service.engine; + +import it.geosolutions.geoserver.rest.GeoServerRESTPublisher; +import it.geosolutions.geoserver.rest.GeoServerRESTPublisher.UploadMethod; +import it.geosolutions.geoserver.rest.GeoServerRESTReader; +import it.geosolutions.geoserver.rest.decoder.RESTFeatureType; +import it.geosolutions.geoserver.rest.decoder.RESTLayer; +import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder; +import it.geosolutions.geoserver.rest.encoder.datastore.GSPostGISDatastoreEncoder; +import it.geosolutions.geoserver.rest.encoder.feature.GSFeatureTypeEncoder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.model.legacy.*; +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.data.transfer.library.DataTransferClient; +import org.gcube.data.transfer.library.TransferResult; +import org.gcube.data.transfer.library.faults.RemoteServiceException; +import org.gcube.data.transfer.model.Destination; +import org.gcube.data.transfer.model.DestinationClashPolicy; +import org.gcube.data.transfer.model.RemoteFileDescriptor; +import org.gcube.spatial.data.gis.GISInterface; +import org.gcube.spatial.data.gis.is.AbstractGeoServerDescriptor; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +public class SDIManager { + + static private final String EPSG_4326="EPSG:4326"; + static private final String WGS84_FULL="GEOGCS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", SPHEROID[\"WGS 84\", 6378137.0, 298.257223563, AUTHORITY[\"EPSG\",\"7030\"]],"+ + "AUTHORITY[\"EPSG\",\"6326\"]], PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]], UNIT[\"degree\", 0.017453292519943295],"+ + "AXIS[\"Geodetic longitude\", EAST], AXIS[\"Geodetic latitude\", NORTH], AUTHORITY[\"EPSG\",\"4326\"]]"; + + + public static final Pattern HOSTNAME_PATTERN=Pattern.compile("(?<=\\:\\/\\/)[^\\:]*"); + public static final Pattern PORT_PATTERN=Pattern.compile("(?<=\\:)[\\d]+"); + public static final Pattern DB_NAME_PATTERN=Pattern.compile("(?<=\\/)[^\\/]*(?=$)"); + + + private final GISInterface gis; + @Getter + private final DataTransferClient dtGeoServer; + private final String geoserverHostName; + + private final AbstractGeoServerDescriptor currentGeoserver; + + + public SDIManager() throws SDIInteractionException { + try{ + log.debug("Initializing GIS Interface.."); + gis=GISInterface.get(); + currentGeoserver=gis.getCurrentGeoServer(); + if(currentGeoserver==null) + throw new Exception("Unable to contact data transfer for geoserver "); + + log.debug("Found geoserver descriptor "+currentGeoserver); + geoserverHostName=new URL(currentGeoserver.getUrl()).getHost(); + + log.debug("Contacting Data Transfer from geoserver {} ",geoserverHostName); + dtGeoServer=DataTransferClient.getInstanceByEndpoint("http://"+geoserverHostName); + if(!currentGeoserver.getReader().existGeoserver()) + throw new Exception("Geoserver not reachable"); + }catch(Exception e) { + throw new SDIInteractionException("Unable to initialize SDI Manager",e); + } + } + + + public RemoteFileDescriptor getGeoServerRemoteFolder() throws RemoteServiceException { + return dtGeoServer.getWebClient().getInfo("geoserver/GNA"); + } + + public String createWorkspace(String toCreate) throws SDIInteractionException { + try { + if(!currentGeoserver.getReader().getWorkspaceNames().contains(toCreate)) { + log.debug("Creating workspace : "+toCreate); + if(!currentGeoserver.getPublisher().createWorkspace(toCreate)) + throw new SDIInteractionException("Unable to create workspace "+toCreate); + }else log.debug("Workspace "+toCreate+" exists."); + return toCreate; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create workspace "+toCreate,e); + } + } + + + // GEOSERVER-PERSISTENCE-ID / GNA / PROJECT-ID/ LAYER-ID /FILENAME(no extension)/... + + public GeoServerContent pushShapeLayerFileSet(SDILayerDescriptor currentElement,String workspace, String projectId) throws SDIInteractionException{ + try { +// String remoteFolder=null; +// String fileName=null; + + log.debug("Publishing "+currentElement+" files to geoserver @ "+geoserverHostName); + + GeoServerContent content=new GeoServerContent(); + content.setGeoserverHostName(geoserverHostName); + content.setWorkspace(workspace); + WorkspaceManager wsManager=new WorkspaceManager(); + + + + + currentElement.getActualContent().forEach((PersistedContent c)->{ + try { + if(c instanceof WorkspaceContent) { + WorkspaceContent wc=(WorkspaceContent) c; + FileContainer fc=wsManager.getFileById(wc.getStorageID()); + + String completeFilename=Files.fixFilename(fc.get().getName()); + String filename=completeFilename.contains(".")?completeFilename.substring(0, completeFilename.lastIndexOf(".")):completeFilename; + + + Destination destination=new Destination(completeFilename); + destination.setCreateSubfolders(true); + destination.setOnExistingFileName(DestinationClashPolicy.REWRITE); + destination.setOnExistingSubFolder(DestinationClashPolicy.APPEND); + + destination.setPersistenceId("geoserver"); + destination.setSubFolder("GNA/"+projectId+"/"+ + currentElement.getMongo_id()+"/"+filename); + + log.debug("Sending "+wc+" to "+destination); + TransferResult result=dtGeoServer.httpSource(fc.getPublicLink(), destination); + log.debug("Transferred "+result); + + + content.getFileNames().add(completeFilename); + + content.setGeoserverPath(result.getRemotePath().substring(0,result.getRemotePath().lastIndexOf("/"))); + } + }catch(Throwable t) { + log.warn("Unable to transfer Persisted content"+c,t); + } + + }); + + + + + + + if(content.getFileNames().isEmpty()) + throw new SDIInteractionException("No Persisted content found in "+currentElement); + + String completeFileName=content.getFileNames().get(0); + String filename=completeFileName.contains(".")?completeFileName.substring(0, completeFileName.lastIndexOf(".")):completeFileName; + + String remoteFolder=content.getGeoserverPath(); + + String toSetLayerName=filename; + int count=0; + GeoServerRESTReader gsReader=currentGeoserver.getReader(); + while(gsReader.getLayer(workspace,toSetLayerName)!=null){ + count++; + toSetLayerName=filename+"_"+count; + log.debug("layer for "+filename+" already existing, trying "+toSetLayerName); + } + + + String storeName=toSetLayerName+"_store"; + content.setStore(storeName); + content.setFeatureType(toSetLayerName); + + GeoServerRESTPublisher publisher=currentGeoserver.getPublisher(); + log.debug("Trying to create remote workspace : "+workspace); + createWorkspace(workspace); + + log.debug("Publishing remote folder "+remoteFolder); + + URL directoryPath=new URL("file:"+remoteFolder+"/"+filename+".shp"); + + + //TODO Evaluate SRS + + boolean published=publisher.publishShp( + workspace, + storeName, + null, + toSetLayerName, + // UploadMethod.FILE, // neeeds zip + UploadMethod.EXTERNAL, // needs shp + directoryPath.toURI(), + EPSG_4326, //SRS + ""); // default style + + if(!published) { + throw new SDIInteractionException("Unable to publish layer "+toSetLayerName+" under "+workspace+". Unknown Geoserver fault."); + } + + currentElement.setLayerName(toSetLayerName); + + RESTLayer l=gsReader.getLayer(workspace, toSetLayerName); + RESTFeatureType f= gsReader.getFeatureType(l); + /*http://geoserver1.dev.d4science.org/geoserver/gna_conc_18/wms? + service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_18:pos& + styles=&bbox=8.62091913167495,40.62975046683799,8.621178639172953,40.630257904721645& + width=392&height=768&srs=EPSG:4326&format=application/openlayers */ + + + currentElement.setWmsLink( + String.format("https://%1$s/geoserver/%2$s/wms?" + +"service=WMS&version=1.1.0&request=GetMap&layers=%2$s:%3$s&" + + "styles=&bbox=%4$f,%5$f,%6$f,%7$f&srs=%8$s&format=application/openlayers&width=%9$d&height=%10$d", + geoserverHostName, + workspace, + toSetLayerName, + f.getMinX(), + f.getMinY(), + f.getMaxX(), + f.getMaxY(), + EPSG_4326, + 400, + 400)); + + currentElement.setWorkspace(workspace); + currentElement.setBbox(new BBOX(f.getMaxY(), f.getMaxX(), f.getMinY(), f.getMinX())); + + + // TODO Metadata + return content; +// } catch (InvalidSourceException | SourceNotSetException | FailedTransferException | InitializationException +// | InvalidDestinationException | DestinationNotSetException e) { +// throw new SDIInteractionException("Unable to transfer fileSet for content "+currentElement,e); + } catch (SDIInteractionException e) { + throw e; + } catch (Throwable t) { + throw new SDIInteractionException("Unexpected internal fault while interacting with SDI.",t); + } + } + + private String createStore(GSPostGISDatastoreEncoder encoder, String workspace) throws SDIInteractionException { + String storeName=encoder.getName(); + try { + log.debug("Looking for datastore "+storeName+" under "+workspace); + + if(currentGeoserver.getReader().getDatastore(workspace,storeName)==null) + + if(!currentGeoserver.getDataStoreManager().create(workspace, encoder)) + throw new SDIInteractionException("Unable to create store "+storeName+" in "+workspace); + log.debug("Store "+storeName+" exists under "+workspace); + return storeName; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create store "+storeName,e); + } + } + + private String createStoreFromPostgisDB(String workspace, String storeName, DatabaseConnection connection) throws SDIInteractionException{ + String connectionUrl=connection.getUrl(); + + Matcher hostname=HOSTNAME_PATTERN.matcher(connectionUrl); + if (!hostname.find()) throw new SDIInteractionException("Unable to get Hostname from "+connection); + + Matcher port = PORT_PATTERN.matcher(connectionUrl); + if (!port.find()) throw new SDIInteractionException("Unable to get PORT from "+connection); + + Matcher db = DB_NAME_PATTERN.matcher(connectionUrl); + if (!db.find()) throw new SDIInteractionException("Unable to get DB from "+connection); + + + GSPostGISDatastoreEncoder encoder=new GSPostGISDatastoreEncoder(storeName); + encoder.setHost(hostname.group()); + encoder.setPort(Integer.parseInt(port.group())); + encoder.setDatabase(db.group()); + encoder.setSchema("public"); + encoder.setUser(connection.getUser()); + encoder.setPassword(connection.getPwd()); + encoder.setLooseBBox(true); + encoder.setDatabaseType("postgis"); + encoder.setEnabled(true); + encoder.setFetchSize(1000); + encoder.setValidateConnections(true); + + return createStore(encoder,workspace); + } + private String createStoreFromJNDIDB(String workspace,String storeName) throws SDIInteractionException { + //SET BY PROVISIONING + GSPostGISDatastoreEncoder encoder=new GSPostGISDatastoreEncoder(storeName); + encoder.setJndiReferenceName("java:comp/env/jdbc/postgres"); + encoder.setLooseBBox(true); + encoder.setDatabaseType("postgis"); + encoder.setEnabled(true); + encoder.setFetchSize(1000); + encoder.setValidateConnections(true); + return createStore(encoder,workspace); + } + + private String publishStyle(File sldFile,String name) throws SDIInteractionException { + try { + if(!currentGeoserver.getReader().existsStyle(name)) { + log.debug("Registering style "+name); + if(!currentGeoserver.getPublisher().publishStyle(sldFile, name)) + throw new SDIInteractionException("Unable to register style "+name); + }else log.debug("Style "+name+" already existing"); + return name; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create style "+name,e); + } + + } + + public String configureCentroidLayer(String name, String workspace, String storeName, PostgisTable table, DatabaseConnection connection) throws SDIInteractionException { + + GSFeatureTypeEncoder fte=new GSFeatureTypeEncoder(); + fte.setAbstract("Centroid layer for "+name); + fte.setEnabled(true); + fte.setNativeCRS(WGS84_FULL); + fte.setTitle(name); + fte.setName(name); + + + // GeoServer loads all fields +// fte.setAttribute(attrs); + + + fte.setLatLonBoundingBox(-180.0, -90.0, 180.0, 90.0, WGS84_FULL); + + String style="clustered_centroids"; + + GSLayerEncoder layerEncoder=new GSLayerEncoder(); + layerEncoder.setDefaultStyle(style); + layerEncoder.setEnabled(true); + layerEncoder.setQueryable(true); + try { + //Checking workspace + createWorkspace(workspace); + //Checking store + createStoreFromPostgisDB(workspace, storeName,connection); + //Checking layer + publishStyle(Files.getFileFromResources("styles/clustered_points.sld"),style); + + log.info("Creating layer in {} : {} with FTE {} , LE {}",workspace,storeName,fte,layerEncoder); + if(currentGeoserver.getReader().getLayer(workspace, name)==null) + if(!currentGeoserver.getPublisher().publishDBLayer(workspace, storeName, fte, layerEncoder)) + throw new SDIInteractionException("Unable to create layer "+name); + log.debug("layer "+name+" already exists"); + + + String link=String.format("https://%1$s/geoserver/%2$s/wms?" + +"service=WMS&version=1.1.0&request=GetMap&layers=%2$s:%3$s&" + + "styles=&bbox=%4$s,%5$s,%6$s,%7$s&srs=%8$s&format=application/openlayers&width=%9$d&height=%10$d", + geoserverHostName, + workspace, + name, + "-1563071.166172796", + "4789738.204048398", + "4334926.486925308", + "5828118.072551585", + EPSG_4326, + 400, + 400); + + return name; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create layer "+name,e); + } + + + } + + + public void deleteContent(GeoServerContent toDelete) throws IllegalArgumentException, MalformedURLException, RemoteServiceException { + log.info("Deleting geoserver layer "+toDelete); + + String geoserverHostName=toDelete.getGeoserverHostName(); + log.debug("Looking for geoserver {}",geoserverHostName); + + AbstractGeoServerDescriptor geoServerDescriptor=null; + + for(AbstractGeoServerDescriptor gs :gis.getCurrentCacheElements(false)){ + log.debug("Checking gs {}",gs); + if(new URL(gs.getUrl()).getHost().equals(geoserverHostName)) + geoServerDescriptor=gs; + } + if(geoServerDescriptor == null) throw new IllegalArgumentException("Unable to find geoserver "+geoserverHostName); + + GeoServerRESTPublisher publisher=geoServerDescriptor.getPublisher(); + + //delete layer + //delete store + log.debug("Removing DS {} : {} ",toDelete.getWorkspace(),toDelete.getStore()); + publisher.removeDatastore(toDelete.getWorkspace(), toDelete.getStore(), true); + + //delete WS if empty + GeoServerRESTReader reader=geoServerDescriptor.getReader(); + log.debug("Checking if WS {} is empty",toDelete.getWorkspace()); + if(reader.getDatastores(toDelete.getWorkspace()).isEmpty()) { + log.debug("Deleting emtpy workspace "+toDelete.getWorkspace()); + publisher.removeWorkspace(toDelete.getWorkspace(), true); + } + //delete file + + + // TODO REMOVE HARDCODED PATCH + String path=toDelete.getGeoserverPath().replace("/srv/geoserver_data","geoserver"); + log.info("Deleting files at {} [{}]",path,toDelete.getGeoserverPath()); +// path=toDelete.getGeoserverPath(); + dtGeoServer.getWebClient().delete(path); + } + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java new file mode 100644 index 0000000..e83f90d --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java @@ -0,0 +1,137 @@ +package org.gcube.application.geoportal.service.engine; + +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; +import org.gcube.common.storagehub.model.exceptions.StorageHubException; + +import javax.validation.constraints.NotNull; +import java.io.FileNotFoundException; +import java.io.InputStream; + +@Slf4j +public class WorkspaceManager { + + private static final String APP_FOLDER=".GNA_RECORDS"; + + + private StorageHubClient sgClient=null; + private FolderContainer appBase=null; + + @Getter + @Setter + @AllArgsConstructor + @RequiredArgsConstructor + public static class FolderOptions{ + @NotNull + private String folderName; + private String folderDescription; + private FolderContainer parent; + } + + @Getter + @Setter + @AllArgsConstructor + @RequiredArgsConstructor + public static class FileOptions{ + @NotNull + private String fileName; + @NonNull + private InputStream is; + + private String fileDescription; + private FolderContainer parent; + + } + + + public WorkspaceManager() throws ConfigurationException, StorageHubException { + sgClient= ImplementationProvider.get().getSHubProvider().getObject(); + appBase=getApplicationBaseFolder(sgClient); + } + + public FolderContainer createFolder(FolderOptions opts) throws StorageHubException { + if(opts.getParent()==null) + opts.setParent(appBase); + return createFolder(opts,sgClient); + } + + public FileContainer getFileById(String id) throws StorageHubException { + return sgClient.open(id).asFile(); + } + + public FolderContainer getFolderById(String id) throws StorageHubException { + return sgClient.open(id).asFolder(); + } + + public void removeFolderById(String id) throws StorageHubException { + sgClient.open(id).asFolder().delete(); + } + + public FolderContainer getSubFolder(FolderContainer parentFolder,String path) throws StorageHubException { + try{ + return parentFolder.openByRelativePath(path).asFolder(); + }catch(StorageHubException e) { + log.debug("Missing subPath "+path); + FolderContainer targetParent=parentFolder; + String targetName=path; + if(path.contains("/")) { + String parent=path.substring(0, path.lastIndexOf("/")); + log.debug("Checking intermediate "+parent); + targetParent=getSubFolder(parentFolder,parent); + targetName=path.substring(path.lastIndexOf("/")+1); + } + log.debug("Creating "+targetName); + return createFolder(new FolderOptions(targetName,"",targetParent),sgClient); + } + } + + + public WorkspaceContent storeToWS(FileOptions opts) throws FileNotFoundException, StorageHubException { + FileContainer item=createFile(opts,sgClient); + item=sgClient.open(item.getId()).asFile(); + + WorkspaceContent content=new WorkspaceContent(); + content.setLink(item.getPublicLink().toString()); + content.setMimetype(item.get().getContent().getMimeType()); + content.setStorageID(item.getId()); + return content; + + } + + public void deleteFromWS(WorkspaceContent toDelete) throws StorageHubException { + sgClient.open(toDelete.getStorageID()).asFile().forceDelete(); + } + + // STATIC SYNCH METHODS + + @Synchronized + public static FolderContainer getApplicationBaseFolder(StorageHubClient sgClient) throws StorageHubException { + FolderContainer vre=sgClient.openVREFolder(); + try { + return vre.openByRelativePath(APP_FOLDER).asFolder(); + }catch(StorageHubException e) { + log.debug("APP Fodler missing. Initializing.."); + FolderContainer toReturn= vre.newFolder(APP_FOLDER, "Base folder for GNA records"); + toReturn.setHidden(); + return toReturn; + } + } + + @Synchronized + private static FolderContainer createFolder(FolderOptions opts, StorageHubClient sgClient) throws StorageHubException { + opts.setFolderName(Files.fixFilename(opts.getFolderName())); + return opts.getParent().newFolder(opts.getFolderName(),opts.getFolderDescription()); + } + + @Synchronized + private static FileContainer createFile(FileOptions opts, StorageHubClient sgClient) throws StorageHubException { + opts.setFileName(Files.fixFilename(opts.getFileName())); + return opts.getParent().uploadFile(opts.getIs(), opts.getFileName(), opts.getFileDescription()); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/cache/Cache.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/cache/Cache.java new file mode 100644 index 0000000..158c59a --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/cache/Cache.java @@ -0,0 +1,9 @@ +package org.gcube.application.geoportal.service.engine.cache; + +public class Cache { + + public Cache() { + // TODO Auto-generated constructor stub + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java new file mode 100644 index 0000000..1bbfdbf --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -0,0 +1,433 @@ +package org.gcube.application.geoportal.service.engine.mongo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.mongodb.client.MongoDatabase; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.legacy.*; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.engine.SDIManager; +import org.gcube.application.geoportal.service.engine.WorkspaceManager; +import org.gcube.application.geoportal.service.engine.WorkspaceManager.FileOptions; +import org.gcube.application.geoportal.service.engine.WorkspaceManager.FolderOptions; +import org.gcube.application.geoportal.service.engine.postgis.PostgisIndex; +import org.gcube.application.geoportal.service.engine.providers.StorageClientProvider; +import org.gcube.application.geoportal.service.model.internal.faults.*; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.data.transfer.library.faults.RemoteServiceException; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Consumer; + +@Slf4j +public class ConcessioniMongoManager extends MongoManager{ + + + + public ConcessioniMongoManager() throws ConfigurationException { + super(); + } + private static final String collectionName="legacyConcessioni"; + private static final String DB_NAME="gna_dev"; + + + private MongoDatabase db=null; + + @Override + @Synchronized + protected MongoDatabase getDatabase() { + if(db==null) { + db=client.getDatabase(DB_NAME); + } + return db; + } + + protected static Document asDocument (Concessione c) throws JsonProcessingException { + Document toReturn=Document.parse(Serialization.write(c)); + if(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()) + toReturn.append(ID, asId(c.getMongo_id())); + return toReturn; + } + + protected static Concessione asConcessione (Document d) throws JsonProcessingException, IOException { + return Serialization.read(d.toJson(), Concessione.class); + } + + + // *** PUBLIC METHODS + + + public Concessione registerNew(Concessione toRegister) throws IOException { + log.trace("Registering {} ",toRegister); + toRegister.setDefaults(); + ObjectId id=insert(asDocument(toRegister), collectionName); + + Concessione toReturn=asConcessione(getById(id,collectionName)); + toReturn.setMongo_id(asString(id)); + + return asConcessione(replace(asDocument(toReturn),collectionName)); + } + + public Concessione replace(Concessione toRegister) throws IOException { + log.trace("Replacing {} ",toRegister); + toRegister.setDefaults(); + return asConcessione(replace(asDocument(toRegister),collectionName)); + } + + public Concessione update(String id,String json) throws IOException { + log.trace("Updating id {} with {} ",id,json); + Concessione toReturn=asConcessione(update(asId(id),asDoc(json),collectionName)); + log.debug("Refreshing defaults.."); + toReturn.setDefaults(); + return asConcessione(replace(asDocument(toReturn),collectionName)); + } + + + + public Iterable list(){ + LinkedBlockingQueue queue=new LinkedBlockingQueue(); + iterate(null,null, collectionName).forEach( + new Consumer() { + @Override + public void accept(Document d) { + try { + queue.put(asConcessione(d)); + }catch(Throwable t) { + log.error("Unable to read Document as concessione ",t); + log.debug("Document was "+d.toJson()); + } + } + }); + return queue; + } + + public Iterable search(Document filter){ + log.info("Searching concessione for filter {} ",filter); + LinkedBlockingQueue queue=new LinkedBlockingQueue(); + iterate(filter,null,collectionName).forEach( + (Consumer) (Document d)->{try{ + queue.put(asConcessione(d)); + }catch(Throwable t){log.warn("Unable to translate "+d);}}); + log.info("Returned {} elements ",queue.size()); + return queue; + } + + public Iterable query(QueryRequest queryRequest){ + log.info("Searching concessione for filter {} ",queryRequest); + LinkedBlockingQueue queue=new LinkedBlockingQueue(); + query(queryRequest,collectionName).forEach( + (Consumer) (Document d)->{try{ + queue.put(d); + }catch(Throwable t){log.warn("Unable to translate "+d);}}); + log.info("Returned {} elements ",queue.size()); + return queue; + } + + public Concessione getById(String id)throws IOException { + log.debug("Loading by ID "+id); + return asConcessione(getById(asId(id),collectionName)); + } + + public void deleteById(String id,boolean force) throws DeletionException { + log.debug("Deleting by ID {}, force {}",id,force); + try{ + Concessione concessione =unpublish(id); + try{ + // UNPUBLISH + + if (!concessione.getReport().getStatus().equals(ValidationStatus.PASSED)&&!force) + throw new DeletionException("Unable to unpublish "+concessione.getMongo_id()); + //clean WS + + concessione = removeContent(concessione); + + if (!concessione.getReport().getStatus().equals(ValidationStatus.PASSED)&&!force) + throw new DeletionException("Unable to unpublish "+concessione.getMongo_id()); + + delete(asId(id), collectionName); + }catch(DeletionException e) { + //storing updated - partially deleted + replace(asDocument(concessione), collectionName); + throw e; + } + }catch(Throwable t){ + throw new DeletionException("Unable to delete "+id,t); + } + } + + + + public Concessione unpublish(String id) throws DeletionException { + try{ + Concessione toReturn=asConcessione(getById(asId(id),collectionName)); + removeFromIndex(toReturn); + log.debug("Removed from centroids "+toReturn.getMongo_id()); + toReturn = unpublish(toReturn); + log.debug("Concessione after unpublishing is "+toReturn); + return asConcessione(replace(asDocument(toReturn),collectionName)); + }catch(Throwable t){ + throw new DeletionException("Unable to unpublish "+id,t); + } + } + + public Concessione publish(String id) throws JsonProcessingException, IOException, InvalidStateException{ + Concessione toReturn=asConcessione(getById(asId(id),collectionName)); + toReturn.setDefaults(); + toReturn.validate(); + + // MATERIALIZE LAYERS + toReturn=publish(toReturn); + // replace(asDocument(toReturn),collectionName); + + // CREATE INDEXES + toReturn=index(toReturn); + // replace(asDocument(toReturn),collectionName); + + return asConcessione(replace(asDocument(toReturn),collectionName)); + } + + + + + private static Concessione removeContent(Concessione concessione) throws DeletionException { + if(concessione.getFolderId()==null) { + log.debug("No content for " + concessione.getMongo_id()); + return concessione; + } + try { + log.debug("Removing content for " + concessione.getMongo_id()); + WorkspaceManager manager = new WorkspaceManager(); + manager.removeFolderById(concessione.getFolderId()); + + //Removing references from Object + concessione.setFolderId(null); + ArrayList list = new ArrayList<>(); + list.add(concessione.getPosizionamentoScavo()); + list.addAll(concessione.getPianteFineScavo()); + list.addAll(concessione.getImmaginiRappresentative()); + list.addAll(concessione.getGenericContent()); + for (AssociatedContent c : list) { + c.getActualContent().clear(); + } + return concessione; + }catch(Throwable t){ + throw new DeletionException("Unable to delete from WS ",t); + } + + } + + public Concessione unregisterFileset(String id, String toClearPath) throws Exception { + log.info("Clearing Fileset at {} for {} ",toClearPath,id); + try { + WorkspaceManager ws=new WorkspaceManager(); + Concessione c = getById(id); + AssociatedContent toClearContent=c.getContentByPath(toClearPath); + log.debug("Found content {} for path {}",toClearContent,toClearPath); + + //checking if published content + for(PersistedContent persisted : toClearContent.getActualContent()){ + if(persisted instanceof GeoServerContent) throw new Exception ("Cannot clear concessione "+id+" at "+toClearContent+", because it is published."); + } + + for(PersistedContent persisted : toClearContent.getActualContent()){ + if(persisted instanceof WorkspaceContent) ws.deleteFromWS((WorkspaceContent) persisted); + } + toClearContent.getActualContent().clear(); + + log.debug("Updating dafults for {} ",c); + c.setDefaults(); + return asConcessione(replace(asDocument(c),collectionName)); + + }catch(Exception e) { + throw new Exception("Unable to unregister files.",e); + } + } + + public Concessione persistContent(String id, String destinationPath, List files) throws Exception{ + log.info("Persisting {} files for path {} in concessione ",files.size(),destinationPath,id); + try{ + Concessione c = getById(id); + WorkspaceManager ws=new WorkspaceManager(); + //Check Init Base folder + FolderContainer baseFolder=null; + if(c.getFolderId()==null) { + String folderName=Files.fixFilename("mConcessione"+"_"+c.getNome()+"_"+Serialization.FULL_FORMATTER.format(LocalDateTime.now())); + log.info("Creating folder {} for Concessione ID {} ",folderName,id); + FolderContainer folder=ws.createFolder(new FolderOptions(folderName, "Base Folder for "+c.getNome(),null)); + c.setFolderId(folder.getId()); + } + + log.debug("Folder id is : "+c.getFolderId()); + baseFolder=ws.getFolderById(c.getFolderId()); + + AssociatedContent section=c.getContentByPath(destinationPath); + log.debug("Found section {} for path {}",section,destinationPath); + store(section,files,ws,baseFolder); + log.debug("Updating dafults for {} ",c); + c.setDefaults(); + return asConcessione(replace(asDocument(c),collectionName)); + }catch(Exception e) { + throw new Exception("Unable to save file.",e); + } + } + + private static Concessione index(Concessione record) { + log.info("Indexing {} ",record.getId()); + ValidationReport report= new ValidationReport("Index Report "); + PostgisIndex index; + try { + index = new PostgisIndex(); + index.registerCentroid(record); + report.addMessage(ValidationStatus.PASSED, "Registered centroid"); + } catch (SDIInteractionException | PublishException | SQLException | ConfigurationException e) { + log.error("Unable to index {} ",record,e); + report.addMessage(ValidationStatus.WARNING, "Internal error while indexing."); + } + return record; + } + + private static Concessione removeFromIndex(Concessione record) { + log.info("Removing from index {} ",record.getMongo_id()); + ValidationReport report= new ValidationReport("Remove From Index Report "); + PostgisIndex index; + try { + index = new PostgisIndex(); + index.removeCentroid(record); + report.addMessage(ValidationStatus.PASSED, "Removed centroid"); + } catch (SDIInteractionException | SQLException | ConfigurationException e) { + log.error("Unable to reove from index {} ",record,e); + report.addMessage(ValidationStatus.WARNING, "Internal error while removing from index."); + } + return record; + } + + + + private static Concessione publish(Concessione conc) { + + // CHECK CONDITION BY PROFILE + + + log.debug("Publishing "+conc.getNome()); + + ValidationReport report=new ValidationReport("Publish report"); + try { + SDIManager sdiManager=new SDIManager(); + ArrayList list=new ArrayList(); + + //Concessione + String workspace= sdiManager.createWorkspace("gna_conc_"+conc.getMongo_id()); + list.add(conc.getPosizionamentoScavo()); + list.addAll(conc.getPianteFineScavo()); + + for(AssociatedContent c:list) { + if(c instanceof LayerConcessione) { + try { + List p=c.getActualContent(); + + GeoServerContent geoserverPersisted=sdiManager.pushShapeLayerFileSet((SDILayerDescriptor)c, workspace, conc.getMongo_id()); + // geoserverPersisted.setAssociated(c); + + + p.add(geoserverPersisted); + c.setActualContent(p); + }catch(SDIInteractionException e) { + log.warn("Unable to publish layers.",e); + report.addMessage(ValidationStatus.WARNING, "Layer "+c.getTitolo()+" non pubblicato."); + } + report.addMessage(ValidationStatus.PASSED, "Pubblicato layer "+c.getTitolo()); + } + } + + + } catch (SDIInteractionException e1) { + report.addMessage(ValidationStatus.WARNING, "Unable to publish layers "+e1.getMessage()); + } + + conc.setReport(report); + return conc; + } + + private static final Concessione unpublish(Concessione concessione){ + ValidationReport report=new ValidationReport("Unpublish report"); + try{ + SDIManager sdi=new SDIManager(); + ArrayList list=new ArrayList(); + + list.add(concessione.getPosizionamentoScavo()); + list.addAll(concessione.getPianteFineScavo()); + for(AssociatedContent c:list) { + if(c instanceof LayerConcessione) { + List contents=c.getActualContent(); + List toRemove=new ArrayList<>(); + for(PersistedContent p:contents){ + if(p instanceof GeoServerContent){ + try { + sdi.deleteContent((GeoServerContent) p); + toRemove.add(p); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (RemoteServiceException e) { + e.printStackTrace(); + } + } + } + c.getActualContent().removeAll(toRemove); + } + } + }catch(SDIInteractionException e){ + report.addMessage(ValidationStatus.WARNING, "Unable to unpublish layers "+e.getMessage()); + } + concessione.setReport(report); + return concessione; + } + + + + private static final void store(AssociatedContent content,List files, WorkspaceManager ws, FolderContainer base) throws Exception { + FolderContainer sectionParent=null; + + if(content instanceof RelazioneScavo) + sectionParent = ws .createFolder(new FolderOptions( + "relazione","Relazione di scavo : "+content.getTitolo(),base)); + + else if (content instanceof UploadedImage) + sectionParent = ws .createFolder(new FolderOptions( + "imgs","Immagini rappresentative : "+content.getTitolo(),base)); + + else if (content instanceof SDILayerDescriptor) + //SDI Section + if(content instanceof LayerConcessione) + sectionParent = ws .createFolder(new FolderOptions( + content.getTitolo(),"Layer Concessione : "+content.getTitolo(),ws.getSubFolder(base,"layers"))); + else throw new Exception("Invalid SDI Content "+content); + else if (content instanceof OtherContent ) + sectionParent = ws .createFolder(new FolderOptions( + content.getTitolo(),"Relazione di scavo : "+content.getTitolo(),ws.getSubFolder(base,"other"))); + else throw new Exception("Invalid Content "+content); + + content.setActualContent(new ArrayList()); + StorageClientProvider storage=ImplementationProvider.get().getStorageProvider(); + for(TempFile f : files) { + WorkspaceContent wsContent=ws.storeToWS(new FileOptions(f.getFilename(), storage.open(f.getId()), "Imported via GeoPortal", sectionParent)); + log.debug("Registered "+wsContent+" for "+content); + content.getActualContent().add(wsContent); + } + content.setMongo_id(asString(new ObjectId())); + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java new file mode 100644 index 0000000..d5e0c61 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java @@ -0,0 +1,152 @@ +package org.gcube.application.geoportal.service.engine.mongo; + + +import com.mongodb.Block; +import com.mongodb.MongoClient; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.FindOneAndReplaceOptions; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; + +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Sorts.ascending; +import static com.mongodb.client.model.Sorts.descending; + +@Slf4j +public abstract class MongoManager { + + protected MongoClient client=null; + + protected static final String ID="_id"; + + protected static final ObjectId asId(String id) {return new ObjectId(id);} + protected static final String asString(ObjectId id) {return id.toHexString();} + + protected static final String asString(Document d) {return d.toJson();} + protected static final Document asDoc(String json) {return Document.parse(json);} + + public MongoManager() throws ConfigurationException { + client=ImplementationProvider.get().getMongoClientProvider().getObject(); + + log.info("Got Mongo Client at "+client.getConnectPoint()); + // NOT AUTHORIZED +// log.debug("Existing databases "+client.getDatabaseNames()); + } + +// private abstract MongoDatabase getDatabase() { +// return client.getDatabase("gna-db"); +// } + + // TODO check if existing DB + protected abstract MongoDatabase getDatabase(); + + //*********** PROJECTS + // NB BsonId + protected ObjectId insert(Document proj, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection collection = database.getCollection(collectionName); + + // Check if _id is present + ObjectId id=proj.getObjectId(ID); + if(id==null) { + proj.append(ID, new ObjectId()); + id=proj.getObjectId(ID); + } + + + collection.insertOne(Document.parse(proj.toJson())); + return id; + } + + public void delete(ObjectId id, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection collection = database.getCollection(collectionName); + collection.deleteOne(eq(ID,id)); + } + + + + public Document getById(ObjectId id,String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + return coll.find(new Document(ID,id)).first(); + } + + + public FindIterable iterate(Document filter, Document projection, String collectionName) { + log.debug("Iterate over {} ",collectionName); + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + + if(filter == null) filter=new Document(); + + log.debug("Applying Filter "+filter.toJson()); + + + if(projection != null ) { + log.debug("Applying projection "+projection.toJson()); + return coll.find(filter).projection(projection); + }else return coll.find(filter); + + } + + public FindIterable query(QueryRequest request, String collectionName){ + + FindIterable toReturn=iterate(request.getFilter(), request.getProjection(),collectionName); + + + + if(request.getOrdering()!=null){ + if(request.getOrdering().getDirection().equals(QueryRequest.OrderedRequest.Direction.ASCENDING)) + toReturn=toReturn.sort(ascending(request.getOrdering().getFields())); + else toReturn=toReturn.sort(descending(request.getOrdering().getFields())); + } + + //Paging + if(request.getPaging()!=null){ + QueryRequest.PagedRequest paging=request.getPaging(); + toReturn=toReturn.skip(paging.getOffset()).limit(paging.getLimit()); + } + + return toReturn; + } + + + public FindIterable iterateForClass(Document filter,String collectionName,Class clazz) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + if(filter==null) + return coll.find(clazz); + else + return coll.find(filter,clazz); + } + + public Document replace(Document toUpdate,String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + return coll.findOneAndReplace( + eq(ID,toUpdate.getObjectId(ID)), toUpdate,new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)); + + } + + public Document update(ObjectId id, Document updateSet, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + return coll.findOneAndUpdate( + eq(ID,id), + updateSet, + new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)); + } + + //********** PROFILES + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java new file mode 100644 index 0000000..55de3e1 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java @@ -0,0 +1,5 @@ +package org.gcube.application.geoportal.service.engine.mongo; + +public class ProfiledMongoManager { + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManager.java new file mode 100644 index 0000000..ed67b0b --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManager.java @@ -0,0 +1,194 @@ +package org.gcube.application.geoportal.service.engine.postgis; + +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.model.legacy.BBOX; +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.model.internal.faults.DataParsingException; +import org.gcube.application.geoportal.service.utils.ISUtils; + +import java.sql.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +public class PostgisDBManager implements PostgisDBManagerI { + + @Synchronized + public static PostgisDBManager get() throws SQLException, ConfigurationException { + if(config==null) { + log.debug("Looking for Default Configuration.. "); + + // TODO GENERIC + DatabaseConnection defaultConfiguration= + ISUtils.queryForDB("postgis", "Concessioni"); + + log.debug("Found configuration : "+defaultConfiguration); + config=defaultConfiguration; + } + return new PostgisDBManager(); + } + public static PostgisDBManagerI get(boolean autocommit) throws SQLException, ConfigurationException { + PostgisDBManager toReturn=get(); + toReturn.conn.setAutoCommit(autocommit); + return toReturn; + } + + private static DatabaseConnection config; + + + + + + + private static Connection getConnection() throws SQLException { + + Connection toReturn= DriverManager.getConnection(config.getUrl(),config.getUser(),config.getPwd()); + + //TODO configure behaviour + toReturn.setAutoCommit(false); + return toReturn; + } + + + + private Connection conn=null; + + + private PostgisDBManager() throws SQLException { + conn=getConnection(); + } + + @Override + public void create(PostgisTable toCreate) throws SQLException { + String createStmt=toCreate.getCreateStatement(); + log.debug("Executing create : "+createStmt); + conn.createStatement().executeUpdate(createStmt); + } + + + /* (non-Javadoc) + * @see org.gcube.application.geoportal.PostgisDBManagerI#commit() + */ + @Override + public void commit() throws SQLException { + conn.commit(); + } + +// /* (non-Javadoc) +// * @see org.gcube.application.geoportal.PostgisDBManagerI#evaluateBoundingBox(org.gcube.application.geoportal.model.PostgisTable) +// */ +// @Override +// public BBOX evaluateBoundingBox(PostgisTable table) throws SQLException, DataParsingException { +// ResultSet rs=conn.createStatement().executeQuery("Select ST_Extent("+table.getGeometryColumn()+") as extent from "+table.getTablename()); +// if(rs.next()) +// return DBUtils.parseST_Extent(rs.getString("extent")); +// else throw new SQLException("No extent returned"); +// } + + /* (non-Javadoc) + * @see org.gcube.application.geoportal.PostgisDBManagerI#evaluateBoundingBox(org.gcube.application.geoportal.model.PostgisTable) + */ + @Override + public PostgisTable.POINT evaluateCentroid(PostgisTable table) throws SQLException, DataParsingException { + ResultSet rs=conn.createStatement().executeQuery("Select ST_AsText(ST_Centroid(ST_Collect("+table.getGeometryColumn()+"))) as centroid from "+table.getTablename()); + if(rs.next()) + return PostgisTable.POINT.parsePOINT(rs.getString("centroid")); + else throw new SQLException("No extent returned"); + } + + /* (non-Javadoc) + * @see org.gcube.application.geoportal.PostgisDBManagerI#prepareInsertStatement(org.gcube.application.geoportal.model.PostgisTable, boolean, boolean) + */ + @Override + public PreparedStatement prepareInsertStatement(PostgisTable target, boolean createTable, boolean geometryAsText) throws SQLException { + if(createTable) { + create(target); + } + String insertStmt=target.getInsertionStatement(geometryAsText); + log.debug("Preparing insert statement : "+insertStmt); + return conn.prepareStatement(insertStmt); + } + + + @Override + public int deleteByFieldValue(PostgisTable target, PostgisTable.Field field, Object value) throws SQLException { + String query=target.getDeleteByFieldStatement(field); + log.debug("Preparing DELETE SQL {} with field {} = {} ",query,field,value); + + PreparedStatement stmt = conn.prepareStatement(query); + target.setObjectInPreparedStatement(field, value, stmt, 1); + int result=stmt.executeUpdate(); + log.debug("DELETED {} rows ",result); + return result; + } + + @Override + public DatabaseConnection getConnectionDescriptor() { + return config; + } + + + /* (non-Javadoc) + * @see org.gcube.application.geoportal.PostgisDBManagerI#deleteTable(java.lang.String) + */ + @Override + public void deleteTable(String tableName) throws SQLException { + conn.createStatement().executeUpdate("DROP TABLE "+tableName); + } + + /* (non-Javadoc) + * @see org.gcube.application.geoportal.PostgisDBManagerI#truncate(java.lang.String) + */ + @Override + public void truncate(String tableName) throws SQLException{ + conn.createStatement().executeUpdate("TRUNCATE Table "+tableName); + } + + @Override + public ResultSet queryAll(PostgisTable table) throws SQLException { + // TODO Check schema + return conn.createStatement().executeQuery("Select * from "+table.getTablename()); + } + + + // *********************** INNER UTILS CLASS + + protected static class DBUtils { + + + private static Pattern pattern = Pattern.compile("(?!=\\d\\.\\d\\.)([\\d.]+)"); + + public static BBOX parseST_Extent(String extent) throws DataParsingException { + //BOX(11.9122574810083 44.2514144864263,11.9761128271586 44.2912342569845) + try { + log.debug("Parsing BBOX "+extent); + Matcher m=pattern.matcher(extent); + + // Scanner sc = new Scanner(extent); + // double minLong = sc.nextDouble(), + // minLat = sc.nextDouble(), + // maxLong = sc.nextDouble(), + // maxLat= sc.nextDouble(); + + if(!m.find()) throw new DataParsingException("Unable to get minLong "); + Double minLong=Double.parseDouble(m.group(1)); + + if(!m.find()) throw new DataParsingException("Unable to get minLat "); + Double minLat=Double.parseDouble(m.group(1)); + + if(!m.find()) throw new DataParsingException("Unable to get maxLong "); + Double maxLong=Double.parseDouble(m.group(1)); + + if(!m.find()) throw new DataParsingException("Unable to get maxLat "); + Double maxLat=Double.parseDouble(m.group(1)); + return new BBOX(maxLat, maxLong, minLat, minLong); + }catch(Throwable t) { + throw new DataParsingException("Invalid BBOX "+extent,t); + } + } + } +} + diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManagerI.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManagerI.java new file mode 100644 index 0000000..bd1cfb4 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisDBManagerI.java @@ -0,0 +1,37 @@ +package org.gcube.application.geoportal.service.engine.postgis; + +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable; +import org.gcube.application.geoportal.service.model.internal.faults.DataParsingException; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface PostgisDBManagerI { + + void commit() throws SQLException; + + + + + + PreparedStatement prepareInsertStatement(PostgisTable target, boolean createTable, boolean geometryAsText) + throws SQLException; + + + void deleteTable(String tableName) throws SQLException; + + void truncate(String tableName) throws SQLException; + + void create(PostgisTable toCreate) throws SQLException; + + PostgisTable.POINT evaluateCentroid(PostgisTable table) throws SQLException, DataParsingException; + + + ResultSet queryAll(PostgisTable table) throws SQLException; + + int deleteByFieldValue(PostgisTable target, PostgisTable.Field field, Object value) throws SQLException; + + DatabaseConnection getConnectionDescriptor(); +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisIndex.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisIndex.java new file mode 100644 index 0000000..9ff150f --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/postgis/PostgisIndex.java @@ -0,0 +1,187 @@ +package org.gcube.application.geoportal.service.engine.postgis; + +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.common.model.rest.PostgisIndexDescriptor; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.engine.SDIManager; +import org.gcube.application.geoportal.service.model.internal.db.DBConstants; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable.Field; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable.FieldType; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.model.internal.faults.PublishException; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; +import org.gcube.application.geoportal.service.utils.Serialization; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +; + +@Slf4j +public class PostgisIndex { + + static{ + try { + Class.forName("org.postgresql.Driver"); + Class.forName("org.postgis.DriverWrapper"); + } catch (Exception var2) { + throw new RuntimeException(var2); + } + } + + + private SDIManager sdiManager; + private String wmsLink=null; + private static PostgisDBManager getDB() throws ConfigurationException { + return ImplementationProvider.get().getDbProvider().getObject(); + }; + + public PostgisIndex() throws SDIInteractionException, SQLException, ConfigurationException { + super(); + this.sdiManager=new SDIManager(); + this.wmsLink=init(); + } + + + public PostgisIndexDescriptor getInfo() throws ConfigurationException, SDIInteractionException, SQLException { + DatabaseConnection conn=getDB().getConnectionDescriptor(); + return new PostgisIndexDescriptor(conn,wmsLink); + } + + protected PostgisTable getCentroidsTable() { + return DBConstants.Concessioni.CENTROIDS; + } + + public String init() throws SQLException, ConfigurationException, SDIInteractionException { + log.debug("Contacting postgis DB .. "); + PostgisDBManagerI db=ImplementationProvider.get().getDbProvider().getObject(); + log.debug("Checking if centroids table exists.."); + PostgisTable table=getCentroidsTable(); + db.create(table); + db.commit(); + return sdiManager.configureCentroidLayer("centroids_concessioni", "gna", "gna_postgis",table,db.getConnectionDescriptor()); + } + + + + public void registerCentroid(Concessione record) throws PublishException{ + + try { + log.debug("Evaluating Centroid"); + Map centroidRow=evaluateCentroid(record); + + log.debug("Contacting postgis DB .. "); + PostgisDBManagerI db=ImplementationProvider.get().getDbProvider().getObject(); + + PostgisTable centroidsTable=getCentroidsTable(); + log.debug("Inserting / updated centroid Row {} ",centroidRow); + + PreparedStatement ps = db.prepareInsertStatement(centroidsTable, true, true); + + log.debug("Deleting centroid if present. ID is "+record.getId()); + db.deleteByFieldValue(centroidsTable, new Field(DBConstants.Concessioni.PRODUCT_ID,FieldType.TEXT), record.getMongo_id()); + + centroidsTable.fillCSVPreparedStatament(centroidRow, ps, false); + ps.executeUpdate(); + db.commit(); + + + }catch(SQLException e) { + log.warn("Unable to publish Centroid for record "+record,e); + throw new PublishException("Unable to publish centroid.",e, null); + } catch (ConfigurationException e) { + log.warn("Unable to contact centroids db "+record.getRecordType(),e); + throw new PublishException("Unable to publish centroid.",e, null); + } + + } + + + + + public void removeCentroid(Concessione record) { + try { + PostgisDBManagerI db=ImplementationProvider.get().getDbProvider().getObject(); + PostgisTable centroidsTable=getCentroidsTable(); + log.debug("Deleting centroid if present. ID is "+record.getMongo_id()); + int result= db.deleteByFieldValue(centroidsTable, new Field(DBConstants.Concessioni.PRODUCT_ID,FieldType.TEXT), record.getMongo_id()); + db.commit(); + log.info("Removed {} entries from gif Index with mongo id {} ",result,record.getMongo_id()); + }catch(Exception e) { + log.warn("Unable to remove centroid ",e); + } + } + + + protected static Map evaluateCentroid(Concessione record){ + + + // CENTROID + Map centroidsRow=new HashMap(); + centroidsRow.put(DBConstants.Concessioni.PRODUCT_ID, record.getMongo_id()); + centroidsRow.put(DBConstants.Concessioni.ANNO, record.getDataInizioProgetto().getYear()+""); + centroidsRow.put(DBConstants.Concessioni.NOME, record.getNome()); + centroidsRow.put(DBConstants.Concessioni.REGIONE, ""); //TODO + + + + if(record.getCentroidLat()==null||record.getCentroidLat()==0) + try { + log.debug("Evaluating Centroid latitude for record "+record); + record.setCentroidLat((record.getPosizionamentoScavo().getBbox().getMaxLat()+ + record.getPosizionamentoScavo().getBbox().getMinLat())/2); + }catch (Throwable t) { + log.warn("Unable to evaluate centroid latitude "+t); + } + + if(record.getCentroidLong()==null||record.getCentroidLong()==0) + try { + log.debug("Evaluating Centroid Longituted for record "+record); + record.setCentroidLong((record.getPosizionamentoScavo().getBbox().getMaxLong()+ + record.getPosizionamentoScavo().getBbox().getMinLong())/2); + }catch (Throwable t) { + log.warn("Unable to evaluate centroid latitude "+t); + } + + + centroidsRow.put(DBConstants.Defaults.XCOORD_FIELD, record.getCentroidLong()+""); + centroidsRow.put(DBConstants.Defaults.YCOORD_FIELD, record.getCentroidLat()+""); + + //Updated Schema + centroidsRow.put(DBConstants.Concessioni.DESCRIZIONE,record.getIntroduzione()); + centroidsRow.put(DBConstants.Concessioni.CONTENUTO,record.getDescrizioneContenuto()); + centroidsRow.put(DBConstants.Concessioni.AUTORE,asString(record.getAuthors())); + centroidsRow.put(DBConstants.Concessioni.CONTRIBUTORE,record.getContributore()); + centroidsRow.put(DBConstants.Concessioni.TITOLARE,asString(record.getTitolari())); + centroidsRow.put(DBConstants.Concessioni.RESPONSABILE,record.getResponsabile()); + centroidsRow.put(DBConstants.Concessioni.EDITORE,record.getEditore()); + centroidsRow.put(DBConstants.Concessioni.FINANZIAMENTO,asString(record.getFontiFinanziamento())); + centroidsRow.put(DBConstants.Concessioni.SOGGETTO,asString(record.getSoggetto())); + centroidsRow.put(DBConstants.Concessioni.RISORSE,asString(record.getRisorseCorrelate())); + centroidsRow.put(DBConstants.Concessioni.DATE_SCAVO,Serialization.FULL_FORMATTER.format(record.getDataFineProgetto())); + centroidsRow.put(DBConstants.Concessioni.DATA_ARCHIVIAZIONE,Serialization.FULL_FORMATTER.format(record.getLastUpdateTime())); + centroidsRow.put(DBConstants.Concessioni.VERSIONE,record.getVersion()); + centroidsRow.put(DBConstants.Concessioni.LICENZA,record.getLicenzaID()); + centroidsRow.put(DBConstants.Concessioni.TITOLARE_LICENZA,asString(record.getTitolareLicenza())); + centroidsRow.put(DBConstants.Concessioni.ACCESSO,record.getPolicy().toString()); + centroidsRow.put(DBConstants.Concessioni.PAROLE_CHIAVE,asString(record.getParoleChiaveLibere())); + + return centroidsRow; + } + + + private static String asString(Collection coll) { + if(coll==null||coll.isEmpty()) return ""; + StringBuilder builder=new StringBuilder(); + for(Object t : coll) { + builder.append(t.toString() +","); + } + return builder.substring(0, builder.lastIndexOf(",")); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/AbstractScopedMap.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/AbstractScopedMap.java new file mode 100644 index 0000000..50f4871 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/AbstractScopedMap.java @@ -0,0 +1,67 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.utils.ContextUtils; + +import java.time.LocalDateTime; +import java.time.temporal.TemporalAmount; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractScopedMap implements Engine{ + + // scope-> object + private ConcurrentHashMap> scopeMap=new ConcurrentHashMap>(); + + @Setter + private TemporalAmount TTL=null; + + @NonNull + private String name; + @Synchronized + public T getObject() throws ConfigurationException { + String currentScope=ContextUtils.getCurrentScope(); + log.debug(name+" : obtaining object for context "+currentScope); + + TTLObject found=scopeMap.get(currentScope); + + if(found== null){ + log.debug(name+" : init object for context "+currentScope); + TTLObject toPut=new TTLObject(LocalDateTime.now(),retrieveObject()); + scopeMap.put(currentScope, toPut); + return toPut.getTheObject(); + } + + if(TTL!=null) { + if(!found.getCreationTime().plus(TTL).isBefore(LocalDateTime.now())) { + log.debug(name+" : elapsed TTL, disposing.."); + dispose(found.getTheObject()); + found=scopeMap.put(currentScope, new TTLObject(LocalDateTime.now(),retrieveObject())); + } + }else {log.debug(name+" : TTL is null, never disposing..");} + return found.getTheObject(); + } + + + @Override + public void shustdown() { + log.warn(name + ": shutting down"); + scopeMap.forEach((String s,TTLObject o)->{ + try{if(o!=null&&o.getTheObject()!=null) + dispose(o.getTheObject()); + }catch(Throwable t) { + log.warn(name +": unable to dispose ",t); + } + }); + } + + protected abstract T retrieveObject() throws ConfigurationException; + + protected abstract void dispose(T toDispose); +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/Engine.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/Engine.java new file mode 100644 index 0000000..0e93075 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/Engine.java @@ -0,0 +1,12 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; + + +public interface Engine { + + public void init(); + public void shustdown(); + + public T getObject() throws ConfigurationException; +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java new file mode 100644 index 0000000..a8d15d5 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java @@ -0,0 +1,47 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.model.internal.db.MongoConnection; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; + +@Slf4j +public class MongoClientProvider extends AbstractScopedMap{ + + public MongoClientProvider() { + super("MongoClient cache"); + +// setTTL(Duration.of(10, ChronoUnit.MINUTES)); + } + + @Override + protected MongoClient retrieveObject() throws ConfigurationException { + MongoConnection conn=ImplementationProvider.get().getMongoConnectionProvider().getObject(); + log.debug("Connecting to "+conn); + + MongoCredential credential = MongoCredential.createCredential(conn.getUser(), conn.getDatabase(), + conn.getPassword().toCharArray()); + + + + MongoClientOptions options = MongoClientOptions.builder().sslEnabled(true).build(); + + return new MongoClient(new ServerAddress(conn.getHosts().get(0),conn.getPort()), + credential, + options); + } + + @Override + protected void dispose(MongoClient toDispose) { + toDispose.close(); + } + + @Override + public void init() { + // TODO Auto-generated method stub + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoConnectionProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoConnectionProvider.java new file mode 100644 index 0000000..9833b26 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoConnectionProvider.java @@ -0,0 +1,32 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import org.gcube.application.geoportal.service.ServiceConstants; +import org.gcube.application.geoportal.service.model.internal.db.MongoConnection; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.utils.ISUtils; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +public class MongoConnectionProvider extends AbstractScopedMap{ + + public MongoConnectionProvider() { + super("MongoDBInfo Cache"); + setTTL(Duration.of(2,ChronoUnit.MINUTES)); + } + + @Override + protected MongoConnection retrieveObject() throws ConfigurationException { + return ISUtils.queryForMongoDB(ServiceConstants.MONGO_SE_PLATFORM, ServiceConstants.MONGO_SE_GNA_FLAG); + } + + @Override + protected void dispose(MongoConnection toDispose) { + + } + + @Override + public void init() { + // TODO Auto-generated method stub + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/PostgisConnectionProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/PostgisConnectionProvider.java new file mode 100644 index 0000000..26a2e9c --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/PostgisConnectionProvider.java @@ -0,0 +1,33 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import org.gcube.application.geoportal.service.engine.postgis.PostgisDBManager; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; + +import java.sql.SQLException; + +public class PostgisConnectionProvider extends AbstractScopedMap{ + + + public PostgisConnectionProvider() { + super("Postgis connection descriptor cache"); + } + + @Override + protected PostgisDBManager retrieveObject() throws ConfigurationException { + try { + return PostgisDBManager.get(); + } catch (SQLException throwables) { + throw new ConfigurationException(throwables); + } + } + + @Override + protected void dispose(PostgisDBManager toDispose) { +// toDispose.close(); + } + + @Override + public void init() { +// + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/ProfileMapCache.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/ProfileMapCache.java new file mode 100644 index 0000000..d9a8eb4 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/ProfileMapCache.java @@ -0,0 +1,34 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import org.gcube.application.geoportal.common.model.profile.Profile; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; + +import java.util.Map; + + +public class ProfileMapCache extends AbstractScopedMap> { + + public ProfileMapCache(String name) { + super(name); + // TODO Auto-generated constructor stub + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + + @Override + protected void dispose(Map toDispose) { + // TODO Auto-generated method stub + + } + + @Override + protected Map retrieveObject() throws ConfigurationException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageClientProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageClientProvider.java new file mode 100644 index 0000000..6f4bb98 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageClientProvider.java @@ -0,0 +1,64 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.utils.ContextUtils; +import org.gcube.contentmanagement.blobstorage.service.IClient; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; +import org.gcube.contentmanager.storageclient.wrapper.AccessType; +import org.gcube.contentmanager.storageclient.wrapper.MemoryType; +import org.gcube.contentmanager.storageclient.wrapper.StorageClient; +import org.gcube.data.transfer.library.utils.Utils; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +@Slf4j +public class StorageClientProvider extends AbstractScopedMap { + + + + public StorageClientProvider() { + super("Storage client cache"); + setTTL(Duration.of(10, ChronoUnit.MINUTES)); + } + + @Override + protected IClient retrieveObject() throws ConfigurationException { + return new StorageClient(InterfaceConstants.SERVICE_CLASS, InterfaceConstants.SERVICE_NAME, ContextUtils.getCurrentCaller(), AccessType.SHARED, MemoryType.VOLATILE).getClient(); + } + + @Override + protected void dispose(IClient toDispose) { + try { + //TODO ASK +// toDispose.close(); + }catch (NullPointerException e) { + // expected if closed without uploading + }catch(Throwable t) { + log.warn(" unable to dispose "+toDispose,t); + } + } + + @Override + public void init() { + + } + + + //wrapping methods + + public InputStream open(String id) throws MalformedURLException, RemoteBackendException, IOException, ConfigurationException { + return new URL(getObject().getHttpsUrl().RFileById(id)).openConnection().getInputStream(); + } + + public String store(InputStream is) throws RemoteBackendException, ConfigurationException { + return getObject().put(true).LFile(is).RFile(Utils.getUniqueString()); + } + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageHubProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageHubProvider.java new file mode 100644 index 0000000..dc549bc --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/StorageHubProvider.java @@ -0,0 +1,25 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; + +public class StorageHubProvider implements Engine { + + + @Override + public StorageHubClient getObject() throws ConfigurationException { + return new StorageHubClient(); + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + @Override + public void shustdown() { + // TODO Auto-generated method stub + + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/TTLObject.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/TTLObject.java new file mode 100644 index 0000000..db226d5 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/TTLObject.java @@ -0,0 +1,17 @@ +package org.gcube.application.geoportal.service.engine.providers; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Getter +@ToString +@AllArgsConstructor +public class TTLObject { + + private LocalDateTime creationTime; + private T theObject; + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/DBConstants.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/DBConstants.java new file mode 100644 index 0000000..17cd57a --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/DBConstants.java @@ -0,0 +1,138 @@ +package org.gcube.application.geoportal.service.model.internal.db; + +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable.Field; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable.FieldType; +import org.gcube.application.geoportal.service.model.internal.db.PostgisTable.GeometryType; + +import java.util.ArrayList; + +public class DBConstants { + + public static enum TYPE{ + + Concessioni,Mosi,Mopr + } + + public static class Defaults{ + public static final String DEFAULT_GEOMETRY_COLUMN_NAME="geom"; + public static final String INTERNAL_ID="internal_id"; + + public static final String XCOORD_FIELD="xcoord"; + public static final String YCOORD_FIELD="ycoord"; + } + + + + /** + * nome,anno,regione,xcentroid,ycentroid,csv,shp, + * geopackage,nome_csv,nome_shp,nome_geo, + * poligono,punti,linee, + * nome_progetto, descrizione_progetto,descrizione_contenuto,autore,contributore, + * titolare_dati,responsabile,editore, + * finanziamento,soggetto, + * risorse_correlate, + * date_scavo,data_archiviazione, + * versione,licenza,titolare_licenza_copyright,accesso_dati,parole_chiave + * + * @author FabioISTI + * + */ + public static class Concessioni{ + + public static final String PRODUCT_ID="product_id"; + public static final String NOME="nome"; + public static final String ANNO="anno"; + public static final String REGIONE="regione"; + public static final String GEOMETRY=Defaults.DEFAULT_GEOMETRY_COLUMN_NAME; + + //Extension + public static final String DESCRIZIONE="descrizione"; + public static final String CONTENUTO="contenuto"; + public static final String AUTORE="autore"; + public static final String CONTRIBUTORE="contributore"; + public static final String TITOLARE="titolare"; + public static final String RESPONSABILE="responsabile"; + public static final String EDITORE="editore"; + public static final String FINANZIAMENTO="finanziamento"; + public static final String SOGGETTO="soggetto"; + public static final String RISORSE="risorse"; + public static final String DATE_SCAVO="date_scavo"; + public static final String DATA_ARCHIVIAZIONE="data_archiviazione"; + public static final String VERSIONE="versione"; + public static final String LICENZA="licenza"; + public static final String TITOLARE_LICENZA="titolare_licenza"; + public static final String ACCESSO="accesso"; + public static final String PAROLE_CHIAVE="parole_chiave"; + + + + public static final ArrayList COLUMNS=new ArrayList(); + public static final PostgisTable CENTROIDS=new PostgisTable("centroids_concessioni", + COLUMNS, GeometryType.POINT); + + static { + CENTROIDS.getFields().add(new Field(Defaults.INTERNAL_ID,FieldType.AUTOINCREMENT)); + CENTROIDS.getFields().add(new Field(PRODUCT_ID,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(NOME,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(ANNO,FieldType.INT)); + CENTROIDS.getFields().add(new Field(REGIONE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(GEOMETRY,FieldType.GEOMETRY)); + + //EXtenions + CENTROIDS.getFields().add(new Field(DESCRIZIONE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(CONTENUTO,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(AUTORE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(CONTRIBUTORE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(TITOLARE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(RESPONSABILE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(EDITORE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(FINANZIAMENTO,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(SOGGETTO,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(RISORSE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(DATE_SCAVO,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(DATA_ARCHIVIAZIONE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(VERSIONE,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(LICENZA,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(TITOLARE_LICENZA,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(ACCESSO,FieldType.TEXT)); + CENTROIDS.getFields().add(new Field(PAROLE_CHIAVE,FieldType.TEXT)); + + } + } + + public static class MOSI{ + + public static final String CODE="code"; + public static final String GEOMETRY=Defaults.DEFAULT_GEOMETRY_COLUMN_NAME; + + + public static final ArrayList COLUMNS=new ArrayList(); + public static final PostgisTable CENTROID_MOSI=new PostgisTable("centroids_mosi", + new ArrayList(), GeometryType.POINT); + + static { + CENTROID_MOSI.getFields().add(new Field(Defaults.INTERNAL_ID,FieldType.AUTOINCREMENT)); + CENTROID_MOSI.getFields().add(new Field(GEOMETRY,FieldType.GEOMETRY)); + CENTROID_MOSI.getFields().add(new Field(CODE,FieldType.TEXT)); + } + } + + public static class MOPR{ + public static final PostgisTable CENTROID_MOPR=new PostgisTable("centroids_mopr", + new ArrayList(), GeometryType.POINT); + } + + + + + public static class INTERNAL{ + public static final String DB_NAME="gna_internal_db"; + + public static final String RECORD="RECORD"; +// public static final String CONCESSIONE="CONCESSIONE"; +// public static final String + } + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/MongoConnection.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/MongoConnection.java new file mode 100644 index 0000000..8c81895 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/MongoConnection.java @@ -0,0 +1,16 @@ +package org.gcube.application.geoportal.service.model.internal.db; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class MongoConnection { + + private String user; + private String password; + private String database; + private List hosts=new ArrayList(); + private int port; +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/PostgisTable.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/PostgisTable.java new file mode 100644 index 0000000..b075ce7 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/db/PostgisTable.java @@ -0,0 +1,314 @@ +package org.gcube.application.geoportal.service.model.internal.db; + +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.model.legacy.BBOX; +import org.gcube.application.geoportal.service.model.internal.faults.DataParsingException; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +@RequiredArgsConstructor +@Getter +@ToString +public class PostgisTable { + + + @Getter + @AllArgsConstructor + public static enum GeometryType{ + MULTIPOINT("4326","geometry (MULTIPOINT,4326)","",""), + POINT("4326","geometry (POINT,4326)","",""), + LINE("4326","geometry (MULTILINESTRING,4326)","",""), + POLYGON("4326","geometry (MULTIPOLYGON,4326)","",""); + private final String SRID; + private final String definition; + private final String InsertWKT; + private final String insertWKB; + + + } + + @RequiredArgsConstructor + @Getter + @ToString + public static class Field{ + @NonNull + private String name; + @NonNull + private FieldType type; + private Boolean isIndexed; + private Object constantValue; + } + + @Getter + @AllArgsConstructor + public enum FieldType{ + INT("int",java.sql.Types.INTEGER), + TEXT("text",java.sql.Types.LONGVARCHAR), + FLOAT("float",java.sql.Types.FLOAT), + GEOMETRY("",0), + AUTOINCREMENT("BIGSERIAL PRIMARY KEY",java.sql.Types.BIGINT); + + private String definition; + private int sqlType; + } + + + @RequiredArgsConstructor + @Getter + @ToString + public static class POINT{ + + private static Pattern pattern = Pattern.compile("(?!=\\d\\.\\d\\.)([\\d.]+)"); + + public static POINT parsePOINT(String point) throws DataParsingException { + //POINT(8.30230113965909 44.8011688237011) + // x,y + try { + log.debug("Parsing POINT "+point); + Matcher m=pattern.matcher(point); + + if(!m.find()) throw new DataParsingException("Unable to get x "); + Double x=Double.parseDouble(m.group(1)); + + if(!m.find()) throw new DataParsingException("Unable to get y "); + Double y=Double.parseDouble(m.group(1)); + + return new POINT(x,y); + }catch(Throwable t) { + throw new DataParsingException("Invalid POINT "+point,t); + } + } + + @NonNull + private Double x; + @NonNull + private Double y; + + } + + + private static final NumberFormat DECIMAL_FORMAT=NumberFormat.getInstance(Locale.US); + + static { + ((DecimalFormat) DECIMAL_FORMAT).setGroupingUsed(false); + } + + + public String getGeometryColumn() { + for(Field f:fields) + if(f.getType().equals(FieldType.GEOMETRY)) return f.getName(); + return null; + } + + + @NonNull + private String tablename; + + @NonNull + private List fields; + + + @NonNull + private GeometryType geometryColumnType; + + @Setter + private BBOX boundingBox=null; + + @Setter + private POINT centroid=null; + + public void setTablename(String tablename) { + this.tablename = sanitizeFieldName(tablename); + } + + public String getCreateStatement() { + StringBuilder stmt=new StringBuilder(); + stmt.append("CREATE TABLE IF NOT EXISTS "+tablename+"( "); + for(Field field:fields){ + + String fieldDefinition=field.getType().getDefinition(); + if(field.getType().equals(FieldType.GEOMETRY)) + fieldDefinition=this.getGeometryColumnType().definition; + + stmt.append(field.getName()+" "+fieldDefinition+","); + } + stmt.deleteCharAt(stmt.lastIndexOf(",")); + stmt.append(")"); + return stmt.toString(); + } + + public String getDeleteByFieldStatement(Field field) { + return "DELETE FROM "+tablename+" WHERE "+field.getName()+" = ? "; + } + + + public String getInsertionStatement(boolean geometryText) { + StringBuilder fieldList=new StringBuilder(); + StringBuilder fieldInsertion=new StringBuilder(); + + for(Field field:fields) { + switch(field.getType()) { + case AUTOINCREMENT : break; + case GEOMETRY : { + fieldList.append(field.getName()+","); + if(geometryText) + fieldInsertion.append("ST_GeomFromText(?, 4326),"); + else + fieldInsertion.append("ST_GeomFromWKB(?, 4326),"); + break; + } + default : { + fieldList.append(field.getName()+","); + fieldInsertion.append("?,"); + } + } + } + + fieldList.deleteCharAt(fieldList.lastIndexOf(",")); + fieldInsertion.deleteCharAt(fieldInsertion.lastIndexOf(",")); + + + + return "Insert into "+tablename+" ("+fieldList+") VALUES ("+fieldInsertion+")"; + } + + + public void fillObjectsPreparedStatement(Map row, PreparedStatement toFill) throws SQLException { + int psFieldIndex=0; + HashMap rowValues=new HashMap(); + for(Map.Entry entry:row.entrySet()) + rowValues.put(sanitizeFieldName(entry.getKey()), entry.getValue()); + + + for(Field field:fields) { + if(!field.getType().equals(FieldType.AUTOINCREMENT)) { + psFieldIndex++; + + Object value=rowValues.get(field.getName()); + setObjectInPreparedStatement(field,value,toFill,psFieldIndex); + + } + + + } + } + + + public void setObjectInPreparedStatement(Field field,Object value, PreparedStatement toFill, int psFieldIndex) throws SQLException { + if(value==null) { + try{ + toFill.setNull(psFieldIndex, field.getType().sqlType); + }catch(SQLException e) { + log.error("Unable to set null for field "+field); + throw e; + } + } + else + switch(field.getType()) { + case FLOAT :{ + toFill.setFloat(psFieldIndex, (Float)value); + break; + } + case INT : { + toFill.setInt(psFieldIndex, (Integer)value); + break; + } + case TEXT : { + toFill.setString(psFieldIndex, value.toString()); + break; + } + case GEOMETRY : { + toFill.setBytes(psFieldIndex, (byte[])value); + } + } + } + + public void fillCSVPreparedStatament(Map row, PreparedStatement toFill,boolean explicitGeometry) throws SQLException { + int psFieldIndex=0; + + HashMap rowValues=new HashMap(); + for(Map.Entry entry:row.entrySet()) + rowValues.put(sanitizeFieldName(entry.getKey()), entry.getValue()); + + for(Field field:fields) { + + + if(!field.getType().equals(FieldType.AUTOINCREMENT)) { + psFieldIndex++; + + String value=rowValues.get(field.getName()); + +// if(value==null||value.equalsIgnoreCase("null")) toFill.setNull(psFieldIndex, field.getType().sqlType); +// else + switch(field.getType()) { + case FLOAT :{ + try{ + toFill.setFloat(psFieldIndex, Float.parseFloat(value)); + }catch(NumberFormatException e) { + throw new SQLException(field+" cannot be null. CSV Row is "+rowValues,e); + } + break; + } + case INT : { + try{ + toFill.setInt(psFieldIndex, Integer.parseInt(value)); + }catch(NumberFormatException e) { + log.warn("Skipping value for "+field+" row was "+rowValues,e); + toFill.setNull(psFieldIndex, java.sql.Types.INTEGER); + } + break; + } + case TEXT : { + toFill.setString(psFieldIndex, value.toString()); + break; + } + case GEOMETRY : { + if(explicitGeometry) { + toFill.setString(psFieldIndex,value); + }else { + switch(geometryColumnType){ + case POINT: { + String xRepresentation=DECIMAL_FORMAT.format(Double.parseDouble(rowValues.get(DBConstants.Defaults.XCOORD_FIELD))); + String yRepresentation=DECIMAL_FORMAT.format(Double.parseDouble(rowValues.get(DBConstants.Defaults.YCOORD_FIELD))); + + toFill.setString(psFieldIndex, "POINT("+xRepresentation+" "+ + yRepresentation+")"); + break; + } + default :{ + toFill.setString(psFieldIndex,rowValues.get("wkt")); + break; + } + } + } + break; + } + } + } + + + + + } + } + + public static String sanitizeFieldName(String fieldName) { +// return fieldName.toLowerCase().replaceAll(" ", "_").replaceAll("\\.", "").replaceAll("-", "_").replaceAll("////","_"); + return fieldName.toLowerCase().replaceAll("[^a-z0-9_\\\\]", "_"); + } + + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ConfigurationException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ConfigurationException.java new file mode 100644 index 0000000..7a76d91 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ConfigurationException.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class ConfigurationException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -3810929853461018566L; + + public ConfigurationException() { + super(); + // TODO Auto-generated constructor stub + } + + public ConfigurationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public ConfigurationException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public ConfigurationException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} \ No newline at end of file diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DataParsingException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DataParsingException.java new file mode 100644 index 0000000..f91a9a2 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DataParsingException.java @@ -0,0 +1,30 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class DataParsingException extends Exception { + + public DataParsingException() { + // TODO Auto-generated constructor stub + } + + public DataParsingException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public DataParsingException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public DataParsingException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public DataParsingException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + +} \ No newline at end of file diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DeletionException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DeletionException.java new file mode 100644 index 0000000..bb121e7 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/DeletionException.java @@ -0,0 +1,23 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class DeletionException extends Exception { + + public DeletionException() { + } + + public DeletionException(String message) { + super(message); + } + + public DeletionException(String message, Throwable cause) { + super(message, cause); + } + + public DeletionException(Throwable cause) { + super(cause); + } + + public DeletionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java new file mode 100644 index 0000000..f2f39fa --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class InvalidStateException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 8926481061304048080L; + + public InvalidStateException() { + super(); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/PublishException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/PublishException.java new file mode 100644 index 0000000..dec7bc6 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/PublishException.java @@ -0,0 +1,42 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +import lombok.NonNull; +import org.gcube.spatial.data.gis.model.report.PublishResponse; + +public class PublishException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -1356876669436308224L; + + @NonNull + private PublishResponse resp; + + public PublishException(PublishResponse resp) { + super(); + this.resp=resp; + } + + public PublishException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace,PublishResponse resp) { + super(message, cause, enableSuppression, writableStackTrace); + this.resp=resp; + } + + public PublishException(String message, Throwable cause,PublishResponse resp) { + super(message, cause); + this.resp=resp; + } + + public PublishException(String message,PublishResponse resp) { + super(message); + this.resp=resp; + } + + public PublishException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java new file mode 100644 index 0000000..635b714 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class SDIInteractionException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public SDIInteractionException() { + super(); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Commons.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Commons.java new file mode 100644 index 0000000..cc3a81e --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Commons.java @@ -0,0 +1,8 @@ +package org.gcube.application.geoportal.service.rest; + +public class Commons { + + + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java new file mode 100644 index 0000000..ed71437 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -0,0 +1,237 @@ +package org.gcube.application.geoportal.service.rest; + +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.model.rest.Configuration; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.service.engine.mongo.ConcessioniMongoManager; +import org.gcube.application.geoportal.service.engine.postgis.PostgisIndex; +import org.gcube.application.geoportal.service.model.internal.faults.DeletionException; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.json.JSONArray; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path(InterfaceConstants.Methods.MONGO_CONCESSIONI) +@Slf4j +public class ConcessioniOverMongo { + + + @GET + @Path(InterfaceConstants.Methods.CONFIGURATION_PATH) + @Produces(MediaType.APPLICATION_JSON) + public Configuration getConfiguration(){ + return new GuardedMethod(){ + + @Override + protected Configuration run() throws Exception, WebApplicationException { + Configuration toReturn = new Configuration(); + toReturn.setIndex(new PostgisIndex().getInfo()); + log.info("Returning configuration {} ",toReturn); + return toReturn; + } + }.execute().getResult(); + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Concessione replace(Concessione c) { + return new GuardedMethod () { + @Override + protected Concessione run() throws Exception, WebApplicationException { + //Concessione c=Serialization.read(jsonString, Concessione.class); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + manager.replace(c); + +// return Serialization.write(manager.getById(c.getMongo_id())); + return manager.getById(c.getMongo_id()); + } + }.execute().getResult(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Concessione createNew(Concessione c) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.registerNew(c); + } + }.execute().getResult(); + } + + + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Iterable list() { + return new GuardedMethod>() { + protected Iterable run() throws Exception ,WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + JSONArray toReturn=new JSONArray(); + return manager.list(); + + }; + }.execute().getResult(); + + + } + + + // BY ID + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione getById(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.getById(id); + } + }.execute().getResult(); + } + + @DELETE + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public void deleteById(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, + @QueryParam(InterfaceConstants.Parameters.FORCE) Boolean forceOption) { + new GuardedMethod () { + @Override + protected Concessione run() throws Exception, WebApplicationException { + try{ + Boolean force=(forceOption!=null)?forceOption:false; + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + manager.deleteById(id,force); + return null; + }catch(DeletionException e){ + throw new WebApplicationException("Unable to delete "+id,e, Response.Status.EXPECTATION_FAILED); + } + } + }.execute(); + } + + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione update(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,Concessione c) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + c.setMongo_id(id); + return manager.replace(c); + } + }.execute().getResult(); + } + + + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Path("/{"+InterfaceConstants.Methods.PUBLISH_PATH+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione publish(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.publish(id); + } + }.execute().getResult(); + } + + @DELETE + @Path("/{"+InterfaceConstants.Methods.PUBLISH_PATH+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione unpublish(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + log.info("Unpublishing {} ",id); + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.unpublish(id); + } + }.execute().getResult(); + + + } + + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/"+InterfaceConstants.Methods.REGISTER_FILES_PATH+"/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione registerFile(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, AddSectionToConcessioneRequest request) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + log.info("Registering {} file(s) for {} Concessione ID {}", + request.getStreams().size(), + request.getDestinationPath(),id); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.persistContent(id, request.getDestinationPath(), request.getStreams()); + } + }.execute().getResult(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/"+InterfaceConstants.Methods.DELETE_FILES_PATH+"/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Concessione clearFileset(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, String path) { + return new GuardedMethod() { + @Override + protected Concessione run() throws Exception, WebApplicationException { + log.info("Clearing files of {} Concessione ID {}",path,id); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.unregisterFileset(id,path); + } + }.execute().getResult(); + } + + + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/"+InterfaceConstants.Methods.SEARCH_PATH) + public Iterable search(String filter){ + return new GuardedMethod>() { + @Override + protected Iterable run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return manager.search(Document.parse(filter)); + } + }.execute().getResult(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/"+InterfaceConstants.Methods.QUERY_PATH) + public String query(String queryString){ + return new GuardedMethod() { + @Override + protected String run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + StringBuilder builder=new StringBuilder("["); + manager.query(Serialization.parseQuery(queryString)) + .forEach(d->{builder.append(d.toJson()+",");}); + builder.deleteCharAt(builder.length()-1); + builder.append("]"); + return builder.toString(); + } + }.execute().getResult(); + } + + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java new file mode 100644 index 0000000..e7537af --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java @@ -0,0 +1,49 @@ +package org.gcube.application.geoportal.service.rest; + +import lombok.extern.slf4j.Slf4j; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response.Status; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public abstract class GuardedMethod { + + private static List preoperations=new ArrayList<>(); + + public static void addPreoperation(Runnable preoperation){ + preoperations.add(preoperation); + } + + + + + private T result=null; + + public GuardedMethod execute() throws WebApplicationException{ + try { + if(!preoperations.isEmpty()) { + log.debug("Running preops (size : {} )", preoperations.size()); + for (Runnable r : preoperations) + r.run(); + } + log.debug("Executing actual method.."); + result=run(); + return this; + }catch(WebApplicationException e) { + throw e; + }catch(Throwable t) { + log.error("Unexpected error ",t); + throw new WebApplicationException("Unexpected internal error", t,Status.INTERNAL_SERVER_ERROR); + } + + } + + public T getResult() { + return result; + } + + + protected abstract T run() throws Exception,WebApplicationException; +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Profiles.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Profiles.java new file mode 100644 index 0000000..5559c02 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Profiles.java @@ -0,0 +1,5 @@ +package org.gcube.application.geoportal.service.rest; + +public class Profiles { + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Projects.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Projects.java new file mode 100644 index 0000000..d6f2acb --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Projects.java @@ -0,0 +1,134 @@ +package org.gcube.application.geoportal.service.rest; + +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.gcube.application.geoportal.common.model.project.Project; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.Collections; +import java.util.List; + +@Path(InterfaceConstants.Methods.PROJECTS) +@Slf4j +public class Projects { + + //***************** GENERIC PROJECTS + // GET ALL + @GET + @Produces(MediaType.APPLICATION_JSON) + public List getAll() { + return new GuardedMethod>() { + protected List run() throws Exception ,WebApplicationException { + return Collections.singletonList(new Project()); + }; + }.execute().getResult(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/search") + public List getFilteredAll(Document filter){ + return new GuardedMethod>() { + protected List run() throws Exception ,WebApplicationException { + return Collections.singletonList(new Project()); + }; + }.execute().getResult(); + } + + + //***************** BY PROFILE ID + + // Create new Project + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROFILE_ID+"}") + public Project registerNew(@PathParam(InterfaceConstants.Parameters.PROFILE_ID)String profileId, + Document toRegister) { + return new GuardedMethod() { + @Override + protected Project run() throws Exception, WebApplicationException { + return new Project(); + } + }.execute().getResult(); + } + + + // GET ALL (Filters apply) + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/search/{"+InterfaceConstants.Parameters.PROFILE_ID+"}") + public List getFilteredAllInProfiles(@PathParam(InterfaceConstants.Parameters.PROFILE_ID)String profileId, + Document filters) { + return new GuardedMethod>() { + protected List run() throws Exception ,WebApplicationException { + return Collections.singletonList(new Project()); + }; + }.execute().getResult(); + } + + // GET ALL + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROFILE_ID+"}") + public List getAllinProfile(@PathParam(InterfaceConstants.Parameters.PROFILE_ID)String profileId) { + return new GuardedMethod>() { + protected List run() throws Exception ,WebApplicationException { + return Collections.singletonList(new Project()); + }; + }.execute().getResult(); + } + + + //***************** BY PROFILE ID + PROJECT ID + // GET BY ID + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROFILE_ID+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Project getByID(@PathParam(InterfaceConstants.Parameters.PROFILE_ID) String profile, + @PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + Project toReturn=new GuardedMethod() { + @Override + protected Project run() throws Exception ,WebApplicationException{ + return new Project(); + } + }.execute().getResult(); + return toReturn; + } + + + // DELETE BY ID + @DELETE + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROFILE_ID+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public void delete(@PathParam(InterfaceConstants.Parameters.PROFILE_ID) String profile, + @PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + new GuardedMethod() { + @Override + protected Project run() throws Exception ,WebApplicationException{ + // TODO DELETE + return null; + } + }.execute().getResult(); + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROFILE_ID+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public Project updateDocument(@PathParam(InterfaceConstants.Parameters.PROFILE_ID) String profile, + @PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,Document toSetDocument) { + Project toReturn=new GuardedMethod() { + @Override + protected Project run() throws Exception ,WebApplicationException{ + return new Project(); + } + }.execute().getResult(); + return toReturn; + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Sections.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Sections.java new file mode 100644 index 0000000..384f142 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/Sections.java @@ -0,0 +1,51 @@ +package org.gcube.application.geoportal.service.rest; + +import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; + +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +@Slf4j +@Path(InterfaceConstants.Methods.SECTIONS+"/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") +public class Sections { + + @PathParam(InterfaceConstants.Parameters.PROJECT_ID) String projectID; + + + // Add Section to Project Document +// @PUT +// @Produces(MediaType.APPLICATION_JSON) +// @Consumes(MediaType.APPLICATION_JSON) +// public Section registerNewSection(Section toRegister) { +// try { +// log.info("Creating new Section [PROJECT_ID:{}]",projectID); +// throw new RuntimeException("Feature not yet available"); +// }catch(WebApplicationException e){ +// log.warn("Unable to serve request",e); +// throw e; +// }catch(Throwable e){ +// log.warn("Unable to serve request",e); +// throw new WebApplicationException("Unable to serve request", e); +// } +// } +// +// +// // Remove Section to Project Document +// @DELETE +// @Produces(MediaType.APPLICATION_JSON) +// @Consumes(MediaType.APPLICATION_JSON) +// @Path("{"+InterfaceConstants.Parameters.SECTION_ID+"}") +// public Project deleteSection(@QueryParam(InterfaceConstants.Parameters.SECTION_ID) String sectionID) { +// try { +// log.info("Deleting Section [ID : {}, PROJECT_ID:{}]",projectID,sectionID); +// throw new RuntimeException("Feature not yet available"); +// }catch(WebApplicationException e){ +// log.warn("Unable to serve request",e); +// throw e; +// }catch(Throwable e){ +// log.warn("Unable to serve request",e); +// throw new WebApplicationException("Unable to serve request", e); +// } +// } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ContextUtils.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ContextUtils.java new file mode 100644 index 0000000..c940b71 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ContextUtils.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.utils; + +import lombok.extern.slf4j.Slf4j; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.scope.api.ScopeProvider; + +import static org.gcube.common.authorization.client.Constants.authorizationService; + +@Slf4j +public class ContextUtils { + + public static String getCurrentScope(){ + try{ + String token=SecurityTokenProvider.instance.get(); + log.debug("Token is : "+token); + if(token==null) throw new Exception("Security Token is null"); + AuthorizationEntry entry = authorizationService().get(token); + return entry.getContext(); + }catch(Exception e ){ + log.debug("Unable to resolve token, checking scope provider..",e); + return ScopeProvider.instance.get(); + } + } + + public static String getCurrentCaller(){ + try{ + String token=SecurityTokenProvider.instance.get(); + log.debug("Token is : "+token); + if(token==null) throw new Exception("Security Token is null"); + AuthorizationEntry entry = authorizationService().get(token); + return entry.getClientInfo().getId(); + }catch(Exception e ){ + log.debug("Unable to resolve token, checking scope provider..",e); + return "Unidentified data-transfer user"; + } + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ISUtils.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ISUtils.java new file mode 100644 index 0000000..75e6baa --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/ISUtils.java @@ -0,0 +1,127 @@ +package org.gcube.application.geoportal.service.utils; + +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.service.ServiceConstants; +import org.gcube.application.geoportal.service.model.internal.db.MongoConnection; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.common.encryption.encrypter.StringEncrypter; +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.Property; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; + +import java.util.List; +import java.util.Map; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +public class ISUtils { + + public static DatabaseConnection queryForDB(String platform, String flag) throws ConfigurationException { + + List found=getAP(platform, flag); + if(found.size()>1) { + throw new ConfigurationException("Multiple SE found ["+found.size()+"] for platform : "+platform+" flag : "+flag); + }else if (found.isEmpty()){ + throw new ConfigurationException("No SE found for platform : "+platform+" flag : "+flag); + } + AccessPoint point=found.get(0); + + DatabaseConnection toReturn=new DatabaseConnection(); + toReturn.setPwd(decryptString(point.password())); + toReturn.setUser(point.username()); + toReturn.setUrl(point.address()); + return toReturn; + + } + + + public static List getAP(String platform,String flag) { + SimpleQuery query = queryFor(ServiceEndpoint.class); + + query.addCondition("$resource/Profile/Category/text() eq '"+ServiceConstants.SE_GNA_DB_CATEGORY+"'") + .addCondition("$resource/Profile/Platform/Name/text() eq '"+platform+"'") + .addCondition("$resource/Profile/AccessPoint//Property[Name/text() eq '"+ + ServiceConstants.SE_GNA_DB_FLAG+"'][Value/text() eq '"+flag+"']") + .setResult("$resource/Profile/AccessPoint"); + + DiscoveryClient client = clientFor(AccessPoint.class); + return client.submit(query); + } + + public static MongoConnection queryForMongoDB(String platform,String flag) throws ConfigurationException { + + List found=getAP(platform, flag); + if(found.size()>1) { + throw new ConfigurationException("Multiple SE found ["+found.size()+"] for platform : "+platform+" flag : "+flag); + }else if (found.isEmpty()){ + throw new ConfigurationException("No SE found for platform : "+platform+" flag : "+flag); + } + AccessPoint point=found.get(0); + MongoConnection toReturn=new MongoConnection(); + for(Property prop:point.properties()) { + switch(prop.name()) { + case "host" : { + toReturn.getHosts().add(prop.value()); + break;} + } + } + toReturn.getHosts().add(point.address()); + Map props=point.propertyMap(); + toReturn.setDatabase(props.get("database").value()); + toReturn.setPassword(decryptString(point.password())); + toReturn.setPort(Integer.parseInt(props.get("port").value())); + toReturn.setUser(point.username()); + + return toReturn; + } + + + + public static String getToken() throws ConfigurationException { + SimpleQuery query = queryFor(ServiceEndpoint.class); + + query.addCondition("$resource/Profile/Category/text() eq 'Application'") + .addCondition("$resource/Profile/Name/text() eq 'GNA-APP'") + .setResult("$resource/Profile/AccessPoint"); + + DiscoveryClient client = clientFor(AccessPoint.class); + + List found= client.submit(query); + if(found.size()>1) { + throw new ConfigurationException("Multiple Token SE found ["+found.size()+"] for Category : Application name : GNA-APP"); + }else if (found.isEmpty()){ + throw new ConfigurationException("No Token SE found ["+found.size()+"] for Category : Application name : GNA-APP"); + } + + AccessPoint point=found.get(0); + return decryptString(point.password()); + + } + + + public static String decryptString(String toDecrypt){ + try{ + return StringEncrypter.getEncrypter().decrypt(toDecrypt); + }catch(Exception e) { + throw new RuntimeException("Unable to decrypt : "+toDecrypt,e); + } + } + + + public static String getgCubeBaseEndpoint(String category,String name) { + + SimpleQuery query = queryFor(ServiceEndpoint.class); + + query.addCondition("$resource/Profile/Category/text() eq '"+category+"'") + .addCondition("$resource/Profile/Name/text() eq '"+name+"'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + + AccessPoint point=client.submit(query).get(0).profile().accessPoints().asCollection().iterator().next(); + + return point.address(); + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java new file mode 100644 index 0000000..dbe892c --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java @@ -0,0 +1,57 @@ +package org.gcube.application.geoportal.service.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.bson.Document; +import org.gcube.application.geoportal.common.model.rest.QueryRequest; + +import java.io.IOException; +import java.time.format.DateTimeFormatter; +import java.util.Iterator; + +public class Serialization { + + + public static final DateTimeFormatter FULL_FORMATTER=DateTimeFormatter.ofPattern("uuuuMMdd_HH-mm-ss"); + + public static ObjectMapper mapper; + + static { + mapper=new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); + mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + mapper.registerModule(new JavaTimeModule()); + + } + + public static T read(String jsonString,Class clazz) throws JsonProcessingException, IOException { + return mapper.readerFor(clazz).readValue(jsonString); + } + + public static Iterator readCollection(String jsonString, Class clazz) throws IOException { + return mapper.readerFor(clazz).readValues(jsonString); + } + + + public static String write(Object toWrite) throws JsonProcessingException { + String toReturn= mapper.writeValueAsString(toWrite); + return toReturn; + } + + + public static QueryRequest parseQuery(String queryString) throws IOException { + Document queryDocument = Document.parse(queryString); + QueryRequest req = new QueryRequest(); + if (queryDocument.containsKey("ordering")) + req.setOrdering(Serialization.read(((Document) queryDocument.get("ordering")).toJson(), QueryRequest.OrderedRequest.class)); + if (queryDocument.containsKey("paging")) + req.setPaging(Serialization.read(((Document) queryDocument.get("paging")).toJson(), QueryRequest.PagedRequest.class)); + req.setProjection(queryDocument.get("projection", Document.class)); + req.setFilter(queryDocument.get("filter", Document.class)); + return req; + } + +} diff --git a/geoportal-service/src/main/resources/styles/clustered_points.sld b/geoportal-service/src/main/resources/styles/clustered_points.sld new file mode 100644 index 0000000..922502d --- /dev/null +++ b/geoportal-service/src/main/resources/styles/clustered_points.sld @@ -0,0 +1,173 @@ + + + + vol_stacked_point + + + Stacked Point + Styles archeomar using stacked points + + + + + data + + + cellSize + 30 + + + outputBBOX + + wms_bbox + + + + outputWidth + + wms_width + + + + outputHeight + + wms_height + + + + + + rule1 + Site + + + count + 1 + + + + + + circle + + #FF0000 + + + 8 + + + + + rule29 + 2-9 Sites + + + count + + 2 + + + 9 + + + + + + + circle + + #AA0000 + + + 14 + + + + + + Arial + 12 + bold + + + + + 0.5 + 0.8 + + + + + 2 + + #AA0000 + 0.9 + + + + #FFFFFF + 1.0 + + + + + rule10 + 10 Sites + + + count + 9 + + + + + + circle + + #AA0000 + + + 22 + + + + + + Arial + 12 + bold + + + + + 0.5 + 0.8 + + + + + 2 + + #AA0000 + 0.9 + + + + #FFFFFF + 1.0 + + + + + + + diff --git a/geoportal-service/src/main/webapp/WEB-INF/gcube-app.xml b/geoportal-service/src/main/webapp/WEB-INF/gcube-app.xml new file mode 100644 index 0000000..ced0697 --- /dev/null +++ b/geoportal-service/src/main/webapp/WEB-INF/gcube-app.xml @@ -0,0 +1,8 @@ + + GeoPortal + Application + 1.0.0 + REST service for GeoPortal + + + diff --git a/geoportal-service/src/main/webapp/WEB-INF/web.xml b/geoportal-service/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..0835e02 --- /dev/null +++ b/geoportal-service/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,25 @@ + + + org.gcube.application.geoportal.service.GeoPortalService + org.glassfish.jersey.servlet.ServletContainer + + + javax.ws.rs.Application + org.gcube.application.geoportal.service.GeoPortalService + + + jersey.config.server.provider.classnames + org.glassfish.jersey.media.multipart.MultiPartFeature + + + + jersey.config.xml.formatOutput + true + + 1 + + + org.gcube.application.geoportal.service.GeoPortalService + /srv/* + + \ No newline at end of file diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java new file mode 100644 index 0000000..25a53da --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java @@ -0,0 +1,81 @@ +package org.gcube.application.geoportal.service; + +import org.gcube.application.geoportal.service.rest.GuardedMethod; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; + +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; + +public class BasicServiceTestUnit extends JerseyTest { + + + + @Override + protected Application configure() { + return new GeoPortalService(); + } + + protected static String scope="/gcube/devsec/devVRE"; + + @BeforeClass + public static void init() { + + GuardedMethod.addPreoperation(new Runnable() { + @Override + public void run() { + TokenSetter.set(scope); + } + }); + + +/* + ImplementationProvider.get().setStorageProvider(new StorageClientProvider() { + @Override + public IClient getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + }); + + + ImplementationProvider.get().setMongoConnectionProvider(new MongoConnectionProvider() { + @Override + public org.gcube.application.geoportal.service.model.internal.db.MongoConnection getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + }); + + ImplementationProvider.get().setMongoClientProvider(new MongoClientProvider() { + @Override + public MongoClient getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + }); + + ImplementationProvider.get().setDbProvider(new PostgisConnectionProvider() { + @Override + public PostgisDBManager getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + });*/ + } + + + protected static T check(Response resp, Class clazz) throws Exception { + String resString=resp.readEntity(String.class); + if(resp.getStatus()<200||resp.getStatus()>=300) + throw new Exception("RESP STATUS IS "+resp.getStatus()+". Message : "+resString); + System.out.println("Resp String is "+resString); + if(clazz!=null) + if (clazz==String.class) + return (T) resString; + else + return Serialization.read(resString, clazz); + else return null; + } +} diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java new file mode 100644 index 0000000..a0f3a78 --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -0,0 +1,327 @@ +package org.gcube.application.geoportal.service; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; +import org.gcube.application.geoportal.common.model.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.*; + +public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ + + + private static final String PATH=InterfaceConstants.Methods.MONGO_CONCESSIONI; + + private static final String PUBLISH_PATH=InterfaceConstants.Methods.PUBLISH_PATH; + private static final String FILES_PATH=InterfaceConstants.Methods.REGISTER_FILES_PATH; + + @Before + public void setContext(){ + TokenSetter.set(scope); + } + + + private static Concessione upload(WebTarget target,String id, String path, String ...files) throws Exception { + + ArrayList array=new ArrayList(); + for(String file:files) + array.add(new StorageUtils().putOntoStorage(new FileInputStream( + Files.getFileFromResources("concessioni/"+file)), file)); + + + AddSectionToConcessioneRequest request=new AddSectionToConcessioneRequest(); + request.setDestinationPath(path); + + request.setStreams(array); + + return check(target.path(FILES_PATH).path(id).request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(request), MediaType.APPLICATION_JSON)),Concessione.class); + + } + + + private static Concessione publish(WebTarget target, Concessione conc) throws Exception { + Response resp=target.path(PUBLISH_PATH).path(conc.getMongo_id()).request(MediaType.APPLICATION_JSON). + put(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + } + private static Concessione unpublish(WebTarget target, String id) throws Exception { + Response resp=target.path(PUBLISH_PATH).path(id).request(MediaType.APPLICATION_JSON). + delete(); + return check(resp,Concessione.class); + } + + private static Concessione register(WebTarget target, Concessione c) throws Exception { + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + } + + private static Concessione get(WebTarget target) throws Exception { + return register(target,TestModel.prepareConcessione()); + } + + private static Concessione getById(WebTarget target ,String id) throws Exception { + return check(target.path(id).request(MediaType.APPLICATION_JSON).get(),Concessione.class); + } + + private static Iterator search(String query, WebTarget target) throws Exception { + String result= check(target.path(InterfaceConstants.Methods.SEARCH_PATH).request(MediaType.APPLICATION_JSON_TYPE).post( + Entity.entity(query,MediaType.APPLICATION_JSON)),String.class); + + return Serialization.readCollection(result,Concessione.class); + } + + private static Iterator searchFile(String filename, WebTarget target) throws Exception { + String query= Files.readFileAsString(Files.getFileFromResources( + "concessioni/jsonFilters/"+filename).getAbsolutePath(), Charset.defaultCharset()); + return search(query,target); + } + + private static Iterator queryFile(String filename, WebTarget target, Class clazz) throws Exception { + String queryString= Files.readFileAsString(Files.getFileFromResources( + "concessioni/jsonQueries/"+filename).getAbsolutePath(), Charset.defaultCharset()); + + String result = check(target.path(InterfaceConstants.Methods.QUERY_PATH).request(MediaType.APPLICATION_JSON_TYPE).post( + Entity.entity(queryString,MediaType.APPLICATION_JSON)),String.class); + return Serialization.readCollection(result,clazz); + } + + private static Iterator queryFile(String filename, WebTarget target) throws Exception { + return queryFile(filename,target,Concessione.class); + } + + // ********** TESTS + + @Test + public void list() { + WebTarget target=target(PATH); + System.out.println(target.request(MediaType.APPLICATION_JSON).get(List.class)); + } + + @Test + public void search() throws Exception { + WebTarget target=target(PATH); + AtomicLong validatedCount= new AtomicLong(0); + Iterator it=searchFile("validated.json",target); + it.forEachRemaining(concessione -> {validatedCount.incrementAndGet();}); + System.out.println("Validated : "+ validatedCount.get()); + } + + @Test + public void query() throws Exception { + WebTarget target=target(PATH); + + Iterator it=null; + it=queryFile("lastRegistered.json", target); + assertTrue(count(it)==1); + + + it=queryFile("firstRegistered.json", target); + assertTrue(count(it)==1); + + System.out.println("Last Names by Fabio : "); + queryFile("lastNamesRegisteredByFabio.json", target, JSONObject.class).forEachRemaining(c -> {System.out.println(c);}); + System.out.println("Publication warning messages : "); + queryFile("publicationWarningMessages.json", target, JSONObject.class).forEachRemaining(c -> {System.out.println(c);}); + + } + + @Test + public void getConfiguration() throws Exception { + WebTarget target=target(PATH); + System.out.println(check(target.path(InterfaceConstants.Methods.CONFIGURATION_PATH).request(MediaType.APPLICATION_JSON).get(),String.class)); + } + + + @Test + public void createNew() throws Exception { + WebTarget target=target(PATH); + Concessione c=register(target,TestModel.prepareConcessione()); + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + } + + + @Test + public void delete() throws Exception { + WebTarget target=target(PATH); + Concessione c = get(target); + + check(target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).delete(),null); + + Concessione published=getFullPublished(target); + + check(target.path(published.getMongo_id()).request(MediaType.APPLICATION_JSON).delete(),null); + } + + + + + @Test + public void republish() throws Exception{ + WebTarget target=target(PATH); + Concessione published=getFullPublished(target); + published = unpublish(target,published.getMongo_id()); + System.out.println("Republishing.."); + published=publish(target,published); + Assert.assertEquals(published.getReport().getStatus(),ValidationStatus.PASSED); + } + + +// @Test +// public void handlePrecise() throws Exception { +// WebTarget target=target(PATH); +// String id="610415af02ad3d05b5f81ee3"; +// publish(target,unpublish(target,id)); +// target.path(id).queryParam(InterfaceConstants.Parameters.FORCE,true).request(MediaType.APPLICATION_JSON).delete(); +// } + + + + @Test + public void getById() throws Exception { + WebTarget target=target(PATH); + Concessione c = get(target); + Response resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); + Concessione loaded=check(resp,Concessione.class); + Assert.assertTrue(loaded.getMongo_id()!=null&&!loaded.getMongo_id().isEmpty()); + System.out.println("Got by ID "+loaded); + } + + + @Test + public void update() throws Exception { + WebTarget target=target(PATH); + Concessione c = get(target); + String newTitle="Questo titolo l'ho modificato mo nel test quello proprio apposta pewr questa cosa'"; + c.setNome(newTitle); + Response resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); + Assert.assertTrue(check(resp,Concessione.class).getNome().equals(newTitle)); + } + + @Test + public void uploadFile() throws Exception { + WebTarget target=target(PATH); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(TestModel.prepareEmptyConcessione()), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + System.out.println("ID IS "+c.getMongo_id()); + + // Insert section + c.setRelazioneScavo(TestModel.prepareConcessione().getRelazioneScavo()); + // c.getRelazioneScavo().setMongo_id(TestModel.rnd()); + + resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); + + + + + c=upload(target,c.getMongo_id(),Paths.RELAZIONE,"relazione.pdf"); + assertNotNull(c.getRelazioneScavo().getActualContent()); + assertTrue(c.getRelazioneScavo().getActualContent().size()>0); + + System.out.println("File is "+c.getRelazioneScavo().getActualContent().get(0)); + } + + @Test + public void testClearFileSet() throws Exception { + WebTarget target=target(PATH); + Concessione published=getFullPublished(target); + + + String path=Paths.POSIZIONAMENTO; + Response resp= + target.path(InterfaceConstants.Methods.DELETE_FILES_PATH).path(published.getMongo_id()). + request(MediaType.APPLICATION_JSON).put(Entity.entity(path, MediaType.APPLICATION_JSON)); + //Expecting error for deletion + assertTrue(resp.getStatus()>=300); + System.out.println("Error for deletion is "+resp.readEntity(String.class)); + resp= + target.path(InterfaceConstants.Methods.PUBLISH_PATH).path(published.getMongo_id()). + request(MediaType.APPLICATION_JSON).delete(); + check(resp,null); + + //Actually cleaning posizionamento + published=check( + target.path(InterfaceConstants.Methods.DELETE_FILES_PATH).path(published.getMongo_id()). + request(MediaType.APPLICATION_JSON).post(Entity.entity(path, MediaType.APPLICATION_JSON)),Concessione.class); + + assertTrue(published.getPosizionamentoScavo().getActualContent().isEmpty()); + + path=Paths.piantaByIndex(0); + published=check( + target.path(InterfaceConstants.Methods.DELETE_FILES_PATH).path(published.getMongo_id()). + request(MediaType.APPLICATION_JSON).post(Entity.entity(path, MediaType.APPLICATION_JSON)),Concessione.class); + + assertTrue(published.getPianteFineScavo().get(0).getActualContent().isEmpty()); + } + + @Test + public void publish() throws Exception { + WebTarget target=target(PATH); + Concessione published=getFullPublished(target); + System.out.println("Published : "+published); + System.out.println("Report is : "+published.getReport()); + assertNotNull(published.getReport()); + assertEquals(ValidationStatus.PASSED,published.getReport().getStatus()); + + assertEquals(published.getImmaginiRappresentative().size(),2); + assertEquals(published.getPianteFineScavo().size(),1); + assertNotNull(published.getPosizionamentoScavo().getWmsLink()); + for(LayerConcessione l : published.getPianteFineScavo()) + assertNotNull(l.getWmsLink()); + assertNotNull(published.getCentroidLat()); + assertNotNull(published.getCentroidLong()); + } + + private Concessione getFullPublished(WebTarget target) throws Exception { + Concessione c=TestModel.prepareConcessione(1,2); + + c.setNome("Concessione : publish test"); + + + + // Register new + c=register(target,c); + + //Upload files + upload(target,c.getMongo_id(),Paths.RELAZIONE,"relazione.pdf"); + upload(target,c.getMongo_id(),Paths.POSIZIONAMENTO,"pos.shp","pos.shx"); + + // Clash on workspaces + upload(target,c.getMongo_id(),Paths.piantaByIndex(0),"pianta.shp","pianta.shx"); + upload(target,c.getMongo_id(),Paths.imgByIndex(0),"immagine.png"); + upload(target,c.getMongo_id(),Paths.imgByIndex(1),"immagine2.png"); + + + + // Immagini + Concessione published=publish(target, c); + return published; + } + + public static long count(Iterator iterator){ + AtomicLong l=new AtomicLong(0); + iterator.forEachRemaining(el->{l.incrementAndGet();}); + return l.get(); + } +} diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/MongoTests.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/MongoTests.java new file mode 100644 index 0000000..302fe7c --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/MongoTests.java @@ -0,0 +1,86 @@ +package org.gcube.application.geoportal.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.mongodb.Block; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Projections; +import org.bson.Document; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.engine.mongo.MongoManager; +import org.gcube.application.geoportal.service.engine.providers.MongoClientProvider; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +public class MongoTests { + + public static class MongoTester extends MongoManager { + + public MongoTester() throws ConfigurationException { + } + + public MongoCollection getCollection(){return getDatabase().getCollection("legacyConcessioni");} + + @Override + protected MongoDatabase getDatabase() { + return client.getDatabase("gna_dev"); + } + } + + + @BeforeClass + public static final void init() { + ImplementationProvider.get().setMongoClientProvider(new MongoClientProvider() { + @Override + public MongoClient getObject() throws ConfigurationException { + TokenSetter.set("/gcube/devsec/devVRE"); + return super.getObject(); + } + }); + } + + + Block printBlock = new Block() { + @Override + public void apply(final Document document) { + System.out.println(document.toJson()); + } + }; + + @Test + public void listProfiles() throws JsonProcessingException, IOException, ConfigurationException { +// MongoManager manager=new MongoManager(); +// Profile f=Serialization.mapper.readerFor(Profile.class).readValue( +// Files.getFileFromResources("fakeProfile.json")); +// +// manager.iterate(new Document(),f).forEach(printBlock); + } + + + @Test + public void queries() throws ConfigurationException { + MongoTester tester=new MongoTester(); + System.out.println("Using builders.."); + tester.getCollection().find(Document.parse("{\"report.status\" : \"WARNING\"}")). + projection(Projections.include("nome")).forEach(printBlock); + System.out.println("Deserializing documents"); + tester.getCollection().find(Document.parse("{\"report.status\" : \"WARNING\"}")). + projection(Document.parse("{\"nome\" : 1}")).forEach(printBlock); + } + + @Test + public void checkQuerySerialization(){ + System.out.println(Projections.include("nome")); + } + +// @Test +// public void writeProject() { +// MongoManager manager=new MongoManager(); +// Concessione f=Serialization.mapper.readerFor(Concessione.class).readValue( +// Files.getFileFromResources("fakeProfile.json")); +// } +} diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ProjectTests.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ProjectTests.java new file mode 100644 index 0000000..cbbdb20 --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/ProjectTests.java @@ -0,0 +1,75 @@ +package org.gcube.application.geoportal.service; + +import org.bson.Document; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.junit.Test; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import java.util.Collections; +import java.util.List; + +public class ProjectTests extends BasicServiceTestUnit{ + + String testProfileId="profiled_concessione"; + String projectId="asdlkjgdasfjklgadjhkl"; + + @Test + public void getAll() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + System.out.println(target.request(MediaType.APPLICATION_JSON).get(List.class)); + } + + @Test + public void getFilteredAll() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + Document document =new Document(Collections.singletonMap("key", "value")); + + System.out.println(target.path("search").request(MediaType.APPLICATION_JSON). + post(Entity.entity(document, MediaType.APPLICATION_JSON))); + + } + + @Test + public void getAllByProfile() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + System.out.println(target.path(testProfileId).request(MediaType.APPLICATION_JSON).get(List.class)); + } + + @Test + public void getFilteredByProfile() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + Document document =new Document(Collections.singletonMap("key", "value")); + + System.out.println(target.path("search").path(testProfileId).request(MediaType.APPLICATION_JSON). + post(Entity.entity(document, MediaType.APPLICATION_JSON))); + + } + + + @Test + public void getById() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + System.out.println(target.path(testProfileId).path(projectId).request(MediaType.APPLICATION_JSON).get().readEntity(String.class)); + } + + + @Test + public void registerNew() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + Document document =new Document(Collections.singletonMap("key", "value")); + + System.out.println(target.path(testProfileId).request(MediaType.APPLICATION_JSON). + put(Entity.entity(document, MediaType.APPLICATION_JSON))); + } + + @Test + public void updateDocument() { + WebTarget target=target(InterfaceConstants.Methods.PROJECTS); + Document document =new Document(Collections.singletonMap("key", "value")); + + System.out.println(target.path(testProfileId).path(projectId).request(MediaType.APPLICATION_JSON). + put(Entity.entity(document, MediaType.APPLICATION_JSON))); + } +} diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/SDITests.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/SDITests.java new file mode 100644 index 0000000..e2f45b0 --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/SDITests.java @@ -0,0 +1,42 @@ +package org.gcube.application.geoportal.service; + + +import org.gcube.application.geoportal.service.engine.SDIManager; +import org.gcube.application.geoportal.service.engine.postgis.PostgisIndex; +import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.regex.Matcher; + +public class SDITests { + + + @Before + public void init(){ + TokenSetter.set("/gcube/devsec/devVRE"); + } + + @Test + public void registerCentroidsLayer() throws SDIInteractionException, SQLException, ConfigurationException { + PostgisIndex index=new PostgisIndex(); + } + + @Test + public void testRegexp(){ + Matcher hostMatcher=SDIManager.HOSTNAME_PATTERN.matcher("jdbc:postgresql://postgresql-srv-dev.d4science.org:5432/geoserver_dev_db"); + Assert.assertTrue(hostMatcher.find()); + System.out.println("HOST :\t"+hostMatcher.group()); + + Matcher portMatcher=SDIManager.PORT_PATTERN.matcher("jdbc:postgresql://postgresql-srv-dev.d4science.org:5432/geoserver_dev_db"); + Assert.assertTrue(portMatcher.find()); + System.out.println("PORT :\t"+portMatcher.group()); + + Matcher dbMatcher=SDIManager.DB_NAME_PATTERN.matcher("jdbc:postgresql://postgresql-srv-dev.d4science.org:5432/geoserver_dev_db"); + Assert.assertTrue(dbMatcher.find()); + System.out.println("DB :\t"+dbMatcher.group()); + } +} diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/TestModel.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/TestModel.java new file mode 100644 index 0000000..6639d5a --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/TestModel.java @@ -0,0 +1,124 @@ +package org.gcube.application.geoportal.service; + +import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.legacy.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; + + + +public class TestModel { + + + public static Concessione prepareEmptyConcessione() { + Concessione concessione=new Concessione(); + + // Generic fields + + // Concessione fields + + concessione.setNome("MONGO Italia, forse, ma su "); + concessione.setIntroduzione("This is my MONGO project"); + concessione.setDescrizioneContenuto("It contains this and that"); + + concessione.setAuthors(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + + concessione.setContributore("Contrib 1"); + concessione.setTitolari(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + concessione.setResponsabile("Someone"); + concessione.setEditore("Editore"); + + concessione.setFontiFinanziamento(Arrays.asList(new String[] {"Big pharma","Pentagon"})); + + + concessione.setSoggetto(Arrays.asList(new String[] {"Research Excavation","Archeology"})); + + + concessione.setDataInizioProgetto(LocalDateTime.now()); + concessione.setDataFineProgetto(LocalDateTime.now()); + + concessione.setLicenzaID("CC-BY"); + + concessione.setTitolareLicenza(Arrays.asList(new String[] {"Qualcun altro"})); + concessione.setTitolareCopyright(Arrays.asList(new String[] {"Chiedilo in giro"})); + + concessione.setParoleChiaveLibere(Arrays.asList(new String[] {"Robba","Stuff"})); + concessione.setParoleChiaveICCD(Arrays.asList(new String[] {"vattelapesca","somthing something"})); + + + concessione.setCentroidLat(43.0); //N-S + concessione.setCentroidLong(9.0); //E-W + + return concessione; + } + + public static final Concessione setIds(Concessione c) { +// c.setMongo_id(rnd()); + c.getRelazioneScavo().setMongo_id(rnd()); + c.getPosizionamentoScavo().setMongo_id(rnd()); + c.getPianteFineScavo().forEach((LayerConcessione l)->{l.setMongo_id(rnd());}); + c.getImmaginiRappresentative().forEach((UploadedImage i)->{i.setMongo_id(rnd());}); + return c; + } + + public static final String rnd() { + return new ObjectId().toHexString(); + } + public static Concessione prepareConcessione() { + return prepareConcessione(4,2); + } + + public static Concessione prepareConcessione(int pianteCount ,int imgsCount) { + + Concessione concessione=prepareEmptyConcessione(); + + + + // Attachments + + // Relazione scavo + RelazioneScavo relScavo=new RelazioneScavo(); + + relScavo.setAbstractIta("simple abstract section"); + relScavo.setResponsabili(concessione.getAuthors()); + + concessione.setRelazioneScavo(relScavo); + //Immagini rappresentative + ArrayList imgs=new ArrayList<>(); + for(int i=0;i piante=new ArrayList(); + for(int i=0;i + + + + org.gcube.application.geoportal.model.Record + + org.gcube.application.geoportal.model.concessioni.Concessione + org.gcube.application.geoportal.model.concessioni.LayerConcessione + org.gcube.application.geoportal.model.concessioni.RelazioneScavo + + + org.gcube.application.geoportal.model.content.AssociatedContent + org.gcube.application.geoportal.model.content.GeoServerContent + org.gcube.application.geoportal.model.content.OtherContent + org.gcube.application.geoportal.model.content.PersistedContent + org.gcube.application.geoportal.model.content.UploadedImage + org.gcube.application.geoportal.model.content.WorkspaceContent + + + org.gcube.application.geoportal.model.gis.SDILayerDescriptor + + + + + + + + + + + + \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/immagine.png b/geoportal-service/src/test/resources/concessioni/immagine.png new file mode 100644 index 0000000000000000000000000000000000000000..e777369853edae17f7cc934fa693e0fa9d82f6f9 GIT binary patch literal 5879 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 literal 0 HcmV?d00001 diff --git a/geoportal-service/src/test/resources/concessioni/immagine2.png b/geoportal-service/src/test/resources/concessioni/immagine2.png new file mode 100644 index 0000000000000000000000000000000000000000..e777369853edae17f7cc934fa693e0fa9d82f6f9 GIT binary patch literal 5879 zcmcJTcQD-Vx5q_Le$k(3!+315{VK$ zdha&M+PnFF=g!=jd+(pWnKREk=e%d0`ONvuoag<#qKyo+sK{B#iHL}(wBhRah=_=7 z2zxkyl(4q-gL8<87~8bfRUi1}?7Y=S{xa=~I7&Q}Y#uUPH(aj~t?7GG=wsq-{Lu24 zMJVyddPRZ*Bo0KCK9*7n5%SkP@9FRsf)u^#@`d*JFy}VShg02t2YgDO>A0)gT$FWw zeY#hE?RSoCt(VUuvMwkkAv_z*-e9l&v+cU1R|DEIN7yh&WT^?-{8hW4o_$d$mR*FO z<)_c>ZIb`i8q(;1ftH_CDvp={JSbXN-_C8Y4gUP)?LAvCAqAXOMS?W{DiUOZHh&8| zp0EBh1&oSUi)CXa#WW^MzQ@~=kfhTbb!B;p)4>2LKy&6>w{ES@Swq7%yc9&aR*NBi z1OZN?Gf6zU7z1=fG?x9Cw zVauxI+TPH|{P@={U|64JZ6;`3h^NUHrAbDl=d?+e;!HYRc=DCKZs=@#kP(&rV71-? z1Oq68KpTpBgX$F)J!aKW_kfVy*{1wzKDoD@`Nvy2u4ojcIQ#aV_-eA^AJlB?AcW=~RA*KCZUeFYSADbH z&!e71&Cnm>PI|K@69{a8+`NPndMxQu%~TOsR=kf!^!LFX?%I68Wy$-4rv<(!eU#2ddXU(!L789;{(qhX47_+=h- z`Bjp(!*gwI#o{i+8$6IPQSzJSqTzO~@V)AT>~|&->1sAJ!MK$eoejWX{!V|WWAQWb zIx4a?KL&DJ-_nVP` zUuV*MCNS%b|5O3gMPpZvQA%5-%<{9_%XhaBA%!|S9X+2?B8tEBVR`!EdDab1#~v+MaXqJyiXkS&aILR`$X*oJdZNv&>Q90u}FPZLsQ@ z%RH+n2Qz4gZOAF(F8LAj;DURbnytu$CCYAZO9pCt!2p(Fv7D~uC3Idt`Qi8oerW0- zpp%qu=%OJWNz@j!zd7W{rHSe{p8q{}&OEhghn85;tuPTPLf14)KJ5IoVkyy@wpw#rmPgNGaX`fNvVWTHHcS@ z(WFa*bl*RF$?GD02p3Fg*Hf7q%(IzBS4h6-PyJS6M;oj2=h?lmpMAFCn-wBJVE^qy zn`xO}d^Zmv*FM@M!A-2E`3>4$s8u`gPBq`B58q zeFCBVcJcRbVkRc0^}1L!Fr$`zgv+S;svM(4Kf|!I1|&_eK!+iP;fwKbHrIMx{57D+ z2W6EMi28d_;X3DGh|OB`iWA|hB<^`2=Z&>t;A}0`SXGV`Xjw`KSa(joztv?{CBSe4 zS+-qd+_EaIII^SKr{IdGfQjK*Vj%wZVqcOz`n^M@89uvL$&ql`6I~>|$M)uQfWCD$ z6l+qU9qbo;*?u+;`O5Gp0K6XjdS(Mco;jf~$Mbduw8YV_Kk+~=imA-c zkrvl6ZeTv3lja!0VaW>t10y@Db&OE2l4y)e8y4zBj8H1gzYfRi+2G_MtuszxG7mdr zti+R+OySzZFa`Dcm6PTA(x-sr&rM5|I+(`_B50$>Sm#?$Ni+ywF@4P{>kJ<=AT)nV0dpQwFkpa!_%m`*kAZXl{PEt5|k zf*W%VHd~ye4|ZG6(qRUy%(k+oVTvo-cX;DpsIdS7C68vKnqiyIwct@ zeGx(Hkt&9FEM%SM0fty~MLay*`!?o*Em6YFd{u<-EY<}h#yvy>Jx_IZ=GJT_ekZ=14YA^ ztJP97lx!>^2>0LpAtqJMRkGE`ehuyM2ktHHiCLRS7-N*Y?&O~VJLlY&%(JmqoVq-C zwo#*Wm`(YobK)0)iAD8THFKYX3vnA)J9einI>i@EB}obh!n{&4(srtnsm{-a7V6Jr zM6^B3;;L4xxOhn}n}H@Zcmceyn`~@sT*r^+Z~f{MByK4_sU{wGk+N(EuPrD2@~&H4 z!xnBIgYzehck+BYvPh9Kg@S&?93S9;DeKN>iI#2cl<8ZljO5N3U%eAo$~#j%qv$nX zc#6z-ZiJSKMFW*Ss88k=@kQOiLt`VSCc%&7KXllJ{i$nc|2)R*Zp(6W^mZ~KI+qP| zU)75g%L$pI-6)#8s4?PC=n$QC&$l!uR5D0>j#d}E-5hxAINT7OkKJ$Hrc?8Fnrup* z4Jj~LFnv8(ZTXGoNilBc(~AB!#kKk$tnxAwyFnjfeUzwpiX$=J$qx%fAU$jYKg%!( z2gaHhR8@S(QbXIG+9TbuJ>z1DlNpg_iRTEb%z17U<@;I9N6F{C-{yUnL{K?x-;%br z<6fg}hI=cQO72|cpl3tmZFH}4pjxvXRko1rZvmy^HeH^rtmeVi{&uw*kj>0@46SCFGK*7|v zoddt}9-Ii1?EK_Uf7jZZUKcwbc&6WTZ++*jPUa)G0zcY=wx84Q3s$~jeqbOTC zk8UKGOIN8f$I;&)6gn9|bemN; zP0RjTpZ#_VKH1oR(T1uR@T?;vOC5 zHV$ifhMq%P7B=MF#X>y8hxTD3@zhi2i*t|z!4xrcXrhPHd=Biy;shke=TV{{3Me~} zh-IezUZ+gH{^WT82ot7-)SNFH^K01Ao_rP#b$GH_gu;breHVbxB#;)^@A}ByEBl3Z zVMQewJ16QyEY;-$uX%@}ei|es3z_43ZVTFl2Lby(0mtB~B9tu@6# zN;V}>lNw(XBl&@l!!=()8|tH;;(0b3gv2|0Yve&_;+b>v^|w)>7#C%6(WPjcS}| zhVDz{?`?D6@!De`CoR=y6I09fOKjdD{vAEj?I~Lqv^*SHgV_l4S+O){>k zhe@}y2a9U`GSIl#cXh~d833)DN47N1XO!<410{>$nZcqF=Rmftx%-(|tC#A?Cs+Gz zDOJl^knIlf>B)!p!%iyCZ&0<|xlg!i!PU&5@iPf-8C{lMA2Lo`fuZSj7hQl`d+V<_Q!R4!{{m-@Wpj z6$V{Qu~BOoWT0zG7xss^3hK#eVX(PRY`|JKHXnU1INt5veZ`X_T=4K#zIPpZW}g^OVENNN_VJFa?M5|EH|b(8iR8J7^A)HHCY5IpE?Y)Y2w-7lXU`J94Bt=l{@(94ot&W)FOOM zrROl#1LW{z>0{jMka`h6Pz?7|d?re14~vR#ljusqROgR83Z}1ti^icUEYv#r+Xc22 z47Jkt1sQs(ocYj0+3e2Ndf^Mcy@@#9D@<%rQrv45Y`}A+&=nJtPl1lawYr&MV~yAc zmKY;glj~0lldFqne~?`rN`P zQics?u?Y()IvVm8;KM`pXQe3pJJ=6f^BD&rzUwO>tEp5s zd|UF#=My(rB6&CRPg3vppU@)1i-0vf=7dkbPZYyxOv$O`eV)(VVV!6vdK+i5cbB^D zywkib`S$e_Tf@KZg?g!t;f2m6307^9D+Rqg7={0QpUFJ2%Mb1Syy*!JfQ8W^@BR`! zk>#Q24uzYN(u9Ie19DKf+h;d#o>M;>7to#5sREaBEvgE}t<>w6m-}DnrGM%#;GLa% zE8eKASiJDlp}j!z(!_ZtSlHHHI&P)-ecF(BlQAzc&7&la+MjA|zfi*CZt9X8y0nb} zZY)^MoLYjl%e30hO_hv&S1k3+yQBuI5Zc;EtR2I9p+un#a9SY-g+z>auuB=x@Ss#g z+sHIPNxz@wB+6ra_;$W(r`W?&ek9U@+E&}am1!T~rr%#f#S*;oBKy28U(7QZeP2Sv z9@H+$QQx{HYUj&)e^9{E*-(^gqH@XfAopdQX6u9qfM?NV>M$@&Z#^c|R_vNKJnGAn zr0`dt-f0YaFJhu0#+X?jQz@5D3YW$BR9l zl#T-X3gq6$#jz>Q_T#_lF^72+L_Jle~zdF{Q@6OAIKb4RML^-xgU*v$lGQmHKfhbJT7xKh9%9+HV*aG{q=7xY;i803%J@-ueR+P$=?AGw;C+`)SjQ$FdmFgPs%=0TCh*6zv;Jg*aM zJLJS>21Y=)8~!J$3!cB^2qeSz|KGT7_t0&3$TdqUImgB!eyjPY(iy-*K}ukB>pm@> zNbbyo4WE|3@UU;(sLz5sq+}E1eMwyjK$}Qg!$7?fW*z!pH9#0Y literal 0 HcmV?d00001 diff --git a/geoportal-service/src/test/resources/concessioni/jsonFilters/all.json b/geoportal-service/src/test/resources/concessioni/jsonFilters/all.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonFilters/all.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonFilters/legacyid.json b/geoportal-service/src/test/resources/concessioni/jsonFilters/legacyid.json new file mode 100644 index 0000000..610e687 --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonFilters/legacyid.json @@ -0,0 +1,3 @@ +{ + "id" : {$gt : 0} +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonFilters/missingCentroid.json b/geoportal-service/src/test/resources/concessioni/jsonFilters/missingCentroid.json new file mode 100644 index 0000000..ab28660 --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonFilters/missingCentroid.json @@ -0,0 +1,4 @@ +{ + "centroidLat" : 0 + +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonFilters/nonFabio.json b/geoportal-service/src/test/resources/concessioni/jsonFilters/nonFabio.json new file mode 100644 index 0000000..b40d2fe --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonFilters/nonFabio.json @@ -0,0 +1,3 @@ +{ + "creationUser" : {$ne : "fabio.sinibaldi"} +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonFilters/validated.json b/geoportal-service/src/test/resources/concessioni/jsonFilters/validated.json new file mode 100644 index 0000000..f05a79f --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonFilters/validated.json @@ -0,0 +1,3 @@ +{ + "report.status": {$eq : "PASSED"} +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonQueries/firstRegistered.json b/geoportal-service/src/test/resources/concessioni/jsonQueries/firstRegistered.json new file mode 100644 index 0000000..f70d918 --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonQueries/firstRegistered.json @@ -0,0 +1,10 @@ +{ + "paging" : { + "offset" : 0, + "limit" : 1 + }, + "ordering" : { + "direction" : "ASCENDING", + "fields" : ["creationTime","nome"] + } +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonQueries/lastNamesRegisteredByFabio.json b/geoportal-service/src/test/resources/concessioni/jsonQueries/lastNamesRegisteredByFabio.json new file mode 100644 index 0000000..668175a --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonQueries/lastNamesRegisteredByFabio.json @@ -0,0 +1,18 @@ +{ + "paging" : { + "offset" : 0, + "limit" : 1 + }, + "ordering" : { + "direction" : "DESCENDING", + "fields" : ["creationTime","nome"] + }, + "filter" : { + "creationUser" : {$eq : "fabio.sinibaldi"} + }, + "projection" : { + "nome" : 1 + } + + +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonQueries/lastRegistered.json b/geoportal-service/src/test/resources/concessioni/jsonQueries/lastRegistered.json new file mode 100644 index 0000000..3324a3a --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonQueries/lastRegistered.json @@ -0,0 +1,11 @@ +{ + "paging" : { + "offset" : 0, + "limit" : 1 + }, + "ordering" : { + "direction" : "DESCENDING", + "fields" : ["creationTime","nome"] + } + +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/jsonQueries/publicationWarningMessages.json b/geoportal-service/src/test/resources/concessioni/jsonQueries/publicationWarningMessages.json new file mode 100644 index 0000000..514bb64 --- /dev/null +++ b/geoportal-service/src/test/resources/concessioni/jsonQueries/publicationWarningMessages.json @@ -0,0 +1,10 @@ +{ + "ordering" : { + "direction" : "DESCENDING", + "fields" : ["nome"] + }, + + "filter" : {"report.status" : {"$eq" : "WARNING"}}, + "projection" : {"report.warningMessages" : 1} + +} \ No newline at end of file diff --git a/geoportal-service/src/test/resources/concessioni/pianta.shp b/geoportal-service/src/test/resources/concessioni/pianta.shp new file mode 100644 index 0000000000000000000000000000000000000000..24ed2bdd3d25b6c8965fc22bc6ee127cde266d34 GIT binary patch literal 96444 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 T@Xw2L6Z^4K`n?uobe;JZkG~-u literal 0 HcmV?d00001 diff --git a/geoportal-service/src/test/resources/concessioni/pos.dbf b/geoportal-service/src/test/resources/concessioni/pos.dbf new file mode 100644 index 0000000000000000000000000000000000000000..eff02b72637ff9a84083e2bd39192053e7087ab2 GIT binary patch literal 461 zcmZRsVBu$BU|>jOhz63FATtFn<_uzS!MPBIUt(@5R0u2|fF{qYV59&8QTcg^K(4Mr vaAJCTX1;+H(e0WJ;|g&7Lmc8U%&G8??s0Qp$8 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 + + + + + %d{yyyy-MM-dd HH:mm:ss} | %-5p | [%thread] %logger{5}:%L - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6c0422a --- /dev/null +++ b/pom.xml @@ -0,0 +1,142 @@ + + 4.0.0 + + maven-parent + org.gcube.tools + 1.1.0 + + org.gcube.application.cms + gcube-cms-suite + pom + 1.0.0-SNAPSHOT + Gcube CMS Suite + + + UTF-8 + 8.0-M4 + 3.0.1 + distro + https://code-repo.d4science.org/gCubeSystem + + + + scm:git:${gitBaseUrl}/${project.artifactId}.git + scm:git:${gitBaseUrl}/${project.artifactId}.git + ${gitBaseUrl}/${project.artifactId}.git + + + + + + geoportal-service + geoportal-client + geoportal-common + + + + + + + + + + org.gcube.distribution + gcube-bom + 2.0.1 + pom + import + + + + + + + + + + + + + org.projectlombok + lombok + 1.14.8 + + + org.slf4j + slf4j-api + + + + + + + ch.qos.logback + logback-classic + test + + + + junit + junit + 4.12 + test + + + + + gCube CMS Suite is a set of components designed to manage complex space-temporal Documents defined by metadata Profiles. + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + flatten-maven-plugin + [1.0.0,) + + flatten + + + + + false + + + + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.0.1 + + true + target + + + + flatten + process-resources + + flatten + + + + + + + \ No newline at end of file