geonode-pycsw 3.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
- geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
- geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
- geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
- geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
- geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
- pycsw/__init__.py +33 -0
- pycsw/core/__init__.py +29 -0
- pycsw/core/admin.py +703 -0
- pycsw/core/config.py +621 -0
- pycsw/core/etree.py +43 -0
- pycsw/core/formats/__init__.py +29 -0
- pycsw/core/formats/fmt_json.py +69 -0
- pycsw/core/log.py +71 -0
- pycsw/core/metadata.py +1996 -0
- pycsw/core/pygeofilter_evaluate.py +83 -0
- pycsw/core/repository.py +941 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
- pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
- pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
- pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
- pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
- pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
- pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
- pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
- pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
- pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
- pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
- pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
- pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
- pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
- pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
- pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
- pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
- pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
- pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
- pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
- pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
- pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
- pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
- pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
- pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
- pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
- pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
- pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
- pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
- pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
- pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
- pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
- pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
- pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
- pycsw/core/util.py +552 -0
- pycsw/oaipmh.py +311 -0
- pycsw/ogc/__init__.py +29 -0
- pycsw/ogc/api/__init__.py +28 -0
- pycsw/ogc/api/oapi.py +558 -0
- pycsw/ogc/api/records.py +1414 -0
- pycsw/ogc/api/templates/_base.html +76 -0
- pycsw/ogc/api/templates/collection.html +44 -0
- pycsw/ogc/api/templates/collections.html +45 -0
- pycsw/ogc/api/templates/conformance.html +21 -0
- pycsw/ogc/api/templates/exception.html +8 -0
- pycsw/ogc/api/templates/item.html +228 -0
- pycsw/ogc/api/templates/items.html +337 -0
- pycsw/ogc/api/templates/landing_page.html +27 -0
- pycsw/ogc/api/templates/openapi.html +58 -0
- pycsw/ogc/api/templates/queryables.html +50 -0
- pycsw/ogc/api/templates/stac_items.html +173 -0
- pycsw/ogc/api/templates/static/favicon.ico +0 -0
- pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
- pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
- pycsw/ogc/api/util.py +252 -0
- pycsw/ogc/csw/__init__.py +29 -0
- pycsw/ogc/csw/cql.py +133 -0
- pycsw/ogc/csw/csw2.py +2042 -0
- pycsw/ogc/csw/csw3.py +2193 -0
- pycsw/ogc/fes/__init__.py +29 -0
- pycsw/ogc/fes/fes1.py +433 -0
- pycsw/ogc/fes/fes2.py +451 -0
- pycsw/ogc/gml/__init__.py +29 -0
- pycsw/ogc/gml/gml3.py +240 -0
- pycsw/ogc/gml/gml32.py +243 -0
- pycsw/opensearch.py +857 -0
- pycsw/plugins/__init__.py +29 -0
- pycsw/plugins/outputschemas/__init__.py +31 -0
- pycsw/plugins/outputschemas/atom.py +143 -0
- pycsw/plugins/outputschemas/datacite.py +375 -0
- pycsw/plugins/outputschemas/dif.py +213 -0
- pycsw/plugins/outputschemas/fgdc.py +180 -0
- pycsw/plugins/outputschemas/gm03.py +240 -0
- pycsw/plugins/profiles/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/apiso.py +757 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
- pycsw/plugins/profiles/ebrim/__init__.py +29 -0
- pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
- pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
- pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
- pycsw/plugins/profiles/profile.py +141 -0
- pycsw/plugins/repository/__init__.py +29 -0
- pycsw/plugins/repository/odc/__init__.py +29 -0
- pycsw/plugins/repository/odc/odc.py +153 -0
- pycsw/server.py +938 -0
- pycsw/sru.py +217 -0
- pycsw/stac/__init__.py +29 -0
- pycsw/stac/api.py +571 -0
- pycsw/wsgi.py +233 -0
- pycsw/wsgi_flask.py +345 -0
pycsw/ogc/csw/csw3.py
ADDED
|
@@ -0,0 +1,2193 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# =================================================================
|
|
3
|
+
#
|
|
4
|
+
# Authors: Tom Kralidis <tomkralidis@gmail.com>
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2024 Tom Kralidis
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person
|
|
9
|
+
# obtaining a copy of this software and associated documentation
|
|
10
|
+
# files (the "Software"), to deal in the Software without
|
|
11
|
+
# restriction, including without limitation the rights to use,
|
|
12
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
# copies of the Software, and to permit persons to whom the
|
|
14
|
+
# Software is furnished to do so, subject to the following
|
|
15
|
+
# conditions:
|
|
16
|
+
#
|
|
17
|
+
# The above copyright notice and this permission notice shall be
|
|
18
|
+
# included in all copies or substantial portions of the Software.
|
|
19
|
+
#
|
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
22
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
24
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
25
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
26
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
27
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
28
|
+
#
|
|
29
|
+
# =================================================================
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
from time import time
|
|
33
|
+
from pycsw.core.etree import etree
|
|
34
|
+
from pycsw.ogc.csw.cql import cql2fes
|
|
35
|
+
from pycsw import opensearch
|
|
36
|
+
from pycsw.core import metadata, util
|
|
37
|
+
from pycsw.core.formats.fmt_json import xml2dict
|
|
38
|
+
from pycsw.ogc.fes import fes1, fes2
|
|
39
|
+
import logging
|
|
40
|
+
|
|
41
|
+
LOGGER = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Csw3(object):
|
|
45
|
+
''' CSW 3.x server '''
|
|
46
|
+
def __init__(self, server_csw):
|
|
47
|
+
''' Initialize CSW3 '''
|
|
48
|
+
|
|
49
|
+
self.parent = server_csw
|
|
50
|
+
self.version = '3.0.0'
|
|
51
|
+
|
|
52
|
+
def getcapabilities(self):
|
|
53
|
+
''' Handle GetCapabilities request '''
|
|
54
|
+
serviceidentification = True
|
|
55
|
+
serviceprovider = True
|
|
56
|
+
operationsmetadata = True
|
|
57
|
+
filtercaps = False
|
|
58
|
+
languages = False
|
|
59
|
+
|
|
60
|
+
# validate acceptformats
|
|
61
|
+
LOGGER.info('Validating ows20:AcceptFormats')
|
|
62
|
+
LOGGER.debug(self.parent.context.model['operations']['GetCapabilities']['parameters']['acceptFormats']['values'])
|
|
63
|
+
if 'acceptformats' in self.parent.kvp:
|
|
64
|
+
bfound = False
|
|
65
|
+
for fmt in self.parent.kvp['acceptformats'].split(','):
|
|
66
|
+
if fmt in self.parent.context.model['operations']['GetCapabilities']['parameters']['acceptFormats']['values']:
|
|
67
|
+
self.parent.mimetype = fmt
|
|
68
|
+
bfound = True
|
|
69
|
+
break
|
|
70
|
+
if not bfound:
|
|
71
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
72
|
+
'acceptformats', 'Invalid acceptFormats parameter value: %s' %
|
|
73
|
+
self.parent.kvp['acceptformats'])
|
|
74
|
+
|
|
75
|
+
if 'sections' in self.parent.kvp and self.parent.kvp['sections'] != '':
|
|
76
|
+
serviceidentification = False
|
|
77
|
+
serviceprovider = False
|
|
78
|
+
operationsmetadata = False
|
|
79
|
+
for section in self.parent.kvp['sections'].split(','):
|
|
80
|
+
if section == 'ServiceIdentification':
|
|
81
|
+
serviceidentification = True
|
|
82
|
+
if section == 'ServiceProvider':
|
|
83
|
+
serviceprovider = True
|
|
84
|
+
if section == 'OperationsMetadata':
|
|
85
|
+
operationsmetadata = True
|
|
86
|
+
if section == 'All':
|
|
87
|
+
serviceidentification = True
|
|
88
|
+
serviceprovider = True
|
|
89
|
+
operationsmetadata = True
|
|
90
|
+
filtercaps = True
|
|
91
|
+
languages = True
|
|
92
|
+
else:
|
|
93
|
+
filtercaps = True
|
|
94
|
+
languages = True
|
|
95
|
+
|
|
96
|
+
# check extra parameters that may be def'd by profiles
|
|
97
|
+
if self.parent.profiles is not None:
|
|
98
|
+
for prof in self.parent.profiles['loaded'].keys():
|
|
99
|
+
result = \
|
|
100
|
+
self.parent.profiles['loaded'][prof].check_parameters(self.parent.kvp)
|
|
101
|
+
if result is not None:
|
|
102
|
+
return self.exceptionreport(result['code'],
|
|
103
|
+
result['locator'], result['text'])
|
|
104
|
+
|
|
105
|
+
# @updateSequence: get latest update to repository
|
|
106
|
+
try:
|
|
107
|
+
updatesequence = \
|
|
108
|
+
util.get_time_iso2unix(self.parent.repository.query_insert())
|
|
109
|
+
except Exception as err:
|
|
110
|
+
LOGGER.debug(f'Cannot set updatesequence: {err}')
|
|
111
|
+
updatesequence = None
|
|
112
|
+
|
|
113
|
+
node = etree.Element(util.nspath_eval('csw30:Capabilities',
|
|
114
|
+
self.parent.context.namespaces),
|
|
115
|
+
nsmap=self.parent.context.namespaces, version='3.0.0',
|
|
116
|
+
updateSequence=str(updatesequence))
|
|
117
|
+
|
|
118
|
+
if 'updatesequence' in self.parent.kvp:
|
|
119
|
+
if int(self.parent.kvp['updatesequence']) == updatesequence:
|
|
120
|
+
return node
|
|
121
|
+
elif int(self.parent.kvp['updatesequence']) > updatesequence:
|
|
122
|
+
return self.exceptionreport('InvalidUpdateSequence',
|
|
123
|
+
'updatesequence',
|
|
124
|
+
'outputsequence specified (%s) is higher than server\'s \
|
|
125
|
+
updatesequence (%s)' % (self.parent.kvp['updatesequence'],
|
|
126
|
+
updatesequence))
|
|
127
|
+
|
|
128
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
129
|
+
self.parent.context.namespaces)] = '%s %s/cat/csw/3.0/cswGetCapabilities.xsd' % \
|
|
130
|
+
(self.parent.context.namespaces['csw30'],
|
|
131
|
+
self.parent.config['server'].get('ogc_schemas_base'))
|
|
132
|
+
|
|
133
|
+
metadata_main = self.parent.config['metadata']
|
|
134
|
+
|
|
135
|
+
if serviceidentification:
|
|
136
|
+
LOGGER.info('Writing section ServiceIdentification')
|
|
137
|
+
|
|
138
|
+
serviceidentification = etree.SubElement(node, \
|
|
139
|
+
util.nspath_eval('ows20:ServiceIdentification',
|
|
140
|
+
self.parent.context.namespaces))
|
|
141
|
+
|
|
142
|
+
etree.SubElement(serviceidentification,
|
|
143
|
+
util.nspath_eval('ows20:Title', self.parent.context.namespaces)).text = \
|
|
144
|
+
metadata_main['identification'].get('title', 'missing')
|
|
145
|
+
|
|
146
|
+
etree.SubElement(serviceidentification,
|
|
147
|
+
util.nspath_eval('ows20:Abstract', self.parent.context.namespaces)).text = \
|
|
148
|
+
metadata_main['identification'].get('description', 'missing')
|
|
149
|
+
|
|
150
|
+
keywords = etree.SubElement(serviceidentification,
|
|
151
|
+
util.nspath_eval('ows20:Keywords', self.parent.context.namespaces))
|
|
152
|
+
|
|
153
|
+
for k in metadata_main['identification']['keywords']:
|
|
154
|
+
etree.SubElement(
|
|
155
|
+
keywords, util.nspath_eval('ows20:Keyword',
|
|
156
|
+
self.parent.context.namespaces)).text = k
|
|
157
|
+
|
|
158
|
+
etree.SubElement(keywords,
|
|
159
|
+
util.nspath_eval('ows20:Type', self.parent.context.namespaces),
|
|
160
|
+
codeSpace='ISOTC211/19115').text = \
|
|
161
|
+
metadata_main['identification'].get('keywords_type', 'missing')
|
|
162
|
+
|
|
163
|
+
etree.SubElement(serviceidentification,
|
|
164
|
+
util.nspath_eval('ows20:ServiceType', self.parent.context.namespaces),
|
|
165
|
+
codeSpace='OGC').text = 'CSW'
|
|
166
|
+
|
|
167
|
+
for stv in self.parent.context.model['parameters']['version']['values']:
|
|
168
|
+
etree.SubElement(serviceidentification,
|
|
169
|
+
util.nspath_eval('ows20:ServiceTypeVersion',
|
|
170
|
+
self.parent.context.namespaces)).text = stv
|
|
171
|
+
|
|
172
|
+
if self.parent.profiles is not None:
|
|
173
|
+
for prof in self.parent.profiles['loaded'].keys():
|
|
174
|
+
prof_name = self.parent.profiles['loaded'][prof].name
|
|
175
|
+
prof_val = self.parent.profiles['loaded'][prof].namespaces[prof_name]
|
|
176
|
+
|
|
177
|
+
etree.SubElement(serviceidentification,
|
|
178
|
+
util.nspath_eval('ows20:Profile',
|
|
179
|
+
self.parent.context.namespaces)).text = prof_val
|
|
180
|
+
|
|
181
|
+
etree.SubElement(serviceidentification,
|
|
182
|
+
util.nspath_eval('ows20:Fees', self.parent.context.namespaces)).text = \
|
|
183
|
+
metadata_main['identification'].get('fees', 'missing')
|
|
184
|
+
|
|
185
|
+
etree.SubElement(serviceidentification,
|
|
186
|
+
util.nspath_eval('ows20:AccessConstraints',
|
|
187
|
+
self.parent.context.namespaces)).text = \
|
|
188
|
+
metadata_main['identification'].get('accessconstraints', 'missing')
|
|
189
|
+
|
|
190
|
+
if serviceprovider:
|
|
191
|
+
LOGGER.info('Writing section ServiceProvider')
|
|
192
|
+
serviceprovider = etree.SubElement(node,
|
|
193
|
+
util.nspath_eval('ows20:ServiceProvider', self.parent.context.namespaces))
|
|
194
|
+
|
|
195
|
+
etree.SubElement(serviceprovider,
|
|
196
|
+
util.nspath_eval('ows20:ProviderName', self.parent.context.namespaces)).text = \
|
|
197
|
+
metadata_main['provider'].get('name', 'missing')
|
|
198
|
+
|
|
199
|
+
providersite = etree.SubElement(serviceprovider,
|
|
200
|
+
util.nspath_eval('ows20:ProviderSite', self.parent.context.namespaces))
|
|
201
|
+
|
|
202
|
+
providersite.attrib[util.nspath_eval('xlink:type',
|
|
203
|
+
self.parent.context.namespaces)] = 'simple'
|
|
204
|
+
|
|
205
|
+
providersite.attrib[util.nspath_eval('xlink:href',
|
|
206
|
+
self.parent.context.namespaces)] = \
|
|
207
|
+
metadata_main['provider'].get('url', 'missing')
|
|
208
|
+
|
|
209
|
+
servicecontact = etree.SubElement(serviceprovider,
|
|
210
|
+
util.nspath_eval('ows20:ServiceContact', self.parent.context.namespaces))
|
|
211
|
+
|
|
212
|
+
etree.SubElement(servicecontact,
|
|
213
|
+
util.nspath_eval('ows20:IndividualName',
|
|
214
|
+
self.parent.context.namespaces)).text = \
|
|
215
|
+
metadata_main['contact'].get('name', 'missing')
|
|
216
|
+
|
|
217
|
+
etree.SubElement(servicecontact,
|
|
218
|
+
util.nspath_eval('ows20:PositionName',
|
|
219
|
+
self.parent.context.namespaces)).text = \
|
|
220
|
+
metadata_main['contact'].get('position', 'missing')
|
|
221
|
+
|
|
222
|
+
contactinfo = etree.SubElement(servicecontact,
|
|
223
|
+
util.nspath_eval('ows20:ContactInfo', self.parent.context.namespaces))
|
|
224
|
+
|
|
225
|
+
phone = etree.SubElement(contactinfo, util.nspath_eval('ows20:Phone',
|
|
226
|
+
self.parent.context.namespaces))
|
|
227
|
+
|
|
228
|
+
etree.SubElement(phone, util.nspath_eval('ows20:Voice',
|
|
229
|
+
self.parent.context.namespaces)).text = \
|
|
230
|
+
metadata_main['contact'].get('phone', 'missing')
|
|
231
|
+
|
|
232
|
+
etree.SubElement(phone, util.nspath_eval('ows20:Facsimile',
|
|
233
|
+
self.parent.context.namespaces)).text = \
|
|
234
|
+
metadata_main['contact'].get('fax', 'missing')
|
|
235
|
+
|
|
236
|
+
address = etree.SubElement(contactinfo,
|
|
237
|
+
util.nspath_eval('ows20:Address', self.parent.context.namespaces))
|
|
238
|
+
|
|
239
|
+
etree.SubElement(address,
|
|
240
|
+
util.nspath_eval('ows20:DeliveryPoint',
|
|
241
|
+
self.parent.context.namespaces)).text = \
|
|
242
|
+
metadata_main['contact'].get('address', 'missing')
|
|
243
|
+
|
|
244
|
+
etree.SubElement(address, util.nspath_eval('ows20:City',
|
|
245
|
+
self.parent.context.namespaces)).text = \
|
|
246
|
+
metadata_main['contact'].get('city', 'missing')
|
|
247
|
+
|
|
248
|
+
etree.SubElement(address,
|
|
249
|
+
util.nspath_eval('ows20:AdministrativeArea',
|
|
250
|
+
self.parent.context.namespaces)).text = \
|
|
251
|
+
metadata_main['contact'].get('stateorprovince', 'missing')
|
|
252
|
+
|
|
253
|
+
etree.SubElement(address,
|
|
254
|
+
util.nspath_eval('ows20:PostalCode',
|
|
255
|
+
self.parent.context.namespaces)).text = \
|
|
256
|
+
str(metadata_main['contact'].get('postalcode', 'missing'))
|
|
257
|
+
|
|
258
|
+
etree.SubElement(address,
|
|
259
|
+
util.nspath_eval('ows20:Country', self.parent.context.namespaces)).text = \
|
|
260
|
+
metadata_main['contact'].get('country', 'missing')
|
|
261
|
+
|
|
262
|
+
etree.SubElement(address,
|
|
263
|
+
util.nspath_eval('ows20:ElectronicMailAddress',
|
|
264
|
+
self.parent.context.namespaces)).text = \
|
|
265
|
+
metadata_main['contact'].get('email', 'missing')
|
|
266
|
+
|
|
267
|
+
url = etree.SubElement(contactinfo,
|
|
268
|
+
util.nspath_eval('ows20:OnlineResource', self.parent.context.namespaces))
|
|
269
|
+
|
|
270
|
+
url.attrib[util.nspath_eval('xlink:type',
|
|
271
|
+
self.parent.context.namespaces)] = 'simple'
|
|
272
|
+
|
|
273
|
+
url.attrib[util.nspath_eval('xlink:href',
|
|
274
|
+
self.parent.context.namespaces)] = \
|
|
275
|
+
metadata_main['contact'].get('url', 'missing')
|
|
276
|
+
|
|
277
|
+
etree.SubElement(contactinfo,
|
|
278
|
+
util.nspath_eval('ows20:HoursOfService',
|
|
279
|
+
self.parent.context.namespaces)).text = \
|
|
280
|
+
metadata_main['contact'].get('hours', 'missing')
|
|
281
|
+
|
|
282
|
+
etree.SubElement(contactinfo,
|
|
283
|
+
util.nspath_eval('ows20:ContactInstructions',
|
|
284
|
+
self.parent.context.namespaces)).text = \
|
|
285
|
+
metadata_main['contact'].get('instructions', 'missing')
|
|
286
|
+
|
|
287
|
+
etree.SubElement(servicecontact,
|
|
288
|
+
util.nspath_eval('ows20:Role', self.parent.context.namespaces),
|
|
289
|
+
codeSpace='ISOTC211/19115').text = \
|
|
290
|
+
metadata_main['contact'].get('role', 'missing')
|
|
291
|
+
|
|
292
|
+
if operationsmetadata:
|
|
293
|
+
LOGGER.info('Writing section OperationsMetadata')
|
|
294
|
+
operationsmetadata = etree.SubElement(node,
|
|
295
|
+
util.nspath_eval('ows20:OperationsMetadata',
|
|
296
|
+
self.parent.context.namespaces))
|
|
297
|
+
|
|
298
|
+
for operation in self.parent.context.model['operations_order']:
|
|
299
|
+
oper = etree.SubElement(operationsmetadata,
|
|
300
|
+
util.nspath_eval('ows20:Operation', self.parent.context.namespaces),
|
|
301
|
+
name=operation)
|
|
302
|
+
|
|
303
|
+
dcp = etree.SubElement(oper, util.nspath_eval('ows20:DCP',
|
|
304
|
+
self.parent.context.namespaces))
|
|
305
|
+
|
|
306
|
+
http = etree.SubElement(dcp, util.nspath_eval('ows20:HTTP',
|
|
307
|
+
self.parent.context.namespaces))
|
|
308
|
+
|
|
309
|
+
if self.parent.context.model['operations'][operation]['methods']['get']:
|
|
310
|
+
get = etree.SubElement(http, util.nspath_eval('ows20:Get',
|
|
311
|
+
self.parent.context.namespaces))
|
|
312
|
+
|
|
313
|
+
get.attrib[util.nspath_eval('xlink:type',\
|
|
314
|
+
self.parent.context.namespaces)] = 'simple'
|
|
315
|
+
|
|
316
|
+
get.attrib[util.nspath_eval('xlink:href',\
|
|
317
|
+
self.parent.context.namespaces)] = self.parent.config['server'].get('url')
|
|
318
|
+
|
|
319
|
+
if self.parent.context.model['operations'][operation]['methods']['post']:
|
|
320
|
+
post = etree.SubElement(http, util.nspath_eval('ows20:Post',
|
|
321
|
+
self.parent.context.namespaces))
|
|
322
|
+
post.attrib[util.nspath_eval('xlink:type',
|
|
323
|
+
self.parent.context.namespaces)] = 'simple'
|
|
324
|
+
post.attrib[util.nspath_eval('xlink:href',
|
|
325
|
+
self.parent.context.namespaces)] = \
|
|
326
|
+
self.parent.config['server'].get('url')
|
|
327
|
+
|
|
328
|
+
for parameter in \
|
|
329
|
+
sorted(self.parent.context.model['operations'][operation]['parameters']):
|
|
330
|
+
param = etree.SubElement(oper,
|
|
331
|
+
util.nspath_eval('ows20:Parameter',
|
|
332
|
+
self.parent.context.namespaces), name=parameter)
|
|
333
|
+
|
|
334
|
+
param.append(self._write_allowed_values(self.parent.context.model['operations'][operation]['parameters'][parameter]['values']))
|
|
335
|
+
|
|
336
|
+
if operation == 'GetRecords': # advertise queryables, MaxRecordDefault
|
|
337
|
+
for qbl in sorted(self.parent.repository.queryables.keys()):
|
|
338
|
+
if qbl not in ['_all', 'SupportedDublinCoreQueryables']:
|
|
339
|
+
param = etree.SubElement(oper,
|
|
340
|
+
util.nspath_eval('ows20:Constraint',
|
|
341
|
+
self.parent.context.namespaces), name=qbl)
|
|
342
|
+
|
|
343
|
+
param.append(self._write_allowed_values(self.parent.repository.queryables[qbl]))
|
|
344
|
+
|
|
345
|
+
if self.parent.profiles is not None:
|
|
346
|
+
for con in sorted(self.parent.context.model[\
|
|
347
|
+
'operations']['GetRecords']['constraints'].keys()):
|
|
348
|
+
param = etree.SubElement(oper,
|
|
349
|
+
util.nspath_eval('ows20:Constraint',
|
|
350
|
+
self.parent.context.namespaces), name=con)
|
|
351
|
+
|
|
352
|
+
param.append(self._write_allowed_values(self.parent.context.model['operations']['GetRecords']['constraints'][con]['values']))
|
|
353
|
+
|
|
354
|
+
extra_constraints = {
|
|
355
|
+
'OpenSearchDescriptionDocument': ['%s?mode=opensearch&service=CSW&version=3.0.0&request=GetCapabilities' % self.parent.config['server'].get('url')],
|
|
356
|
+
'MaxRecordDefault': self.parent.context.model['constraints']['MaxRecordDefault']['values'],
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for key in sorted(extra_constraints.keys()):
|
|
360
|
+
param = etree.SubElement(oper,
|
|
361
|
+
util.nspath_eval('ows20:Constraint',
|
|
362
|
+
self.parent.context.namespaces), name=key)
|
|
363
|
+
param.append(self._write_allowed_values(extra_constraints[key]))
|
|
364
|
+
|
|
365
|
+
if 'FederatedCatalogues' in self.parent.context.model['constraints']:
|
|
366
|
+
param = etree.SubElement(oper,
|
|
367
|
+
util.nspath_eval('ows20:Constraint',
|
|
368
|
+
self.parent.context.namespaces), name='FederatedCatalogues')
|
|
369
|
+
param.append(self._write_allowed_values(self.parent.context.model['constraints']['FederatedCatalogues']['values']))
|
|
370
|
+
|
|
371
|
+
for parameter in sorted(self.parent.context.model['parameters'].keys()):
|
|
372
|
+
param = etree.SubElement(operationsmetadata,
|
|
373
|
+
util.nspath_eval('ows20:Parameter', self.parent.context.namespaces),
|
|
374
|
+
name=parameter)
|
|
375
|
+
|
|
376
|
+
param.append(self._write_allowed_values(self.parent.context.model['parameters'][parameter]['values']))
|
|
377
|
+
|
|
378
|
+
for qbl in sorted(self.parent.repository.queryables.keys()):
|
|
379
|
+
if qbl == 'SupportedDublinCoreQueryables':
|
|
380
|
+
param = etree.SubElement(operationsmetadata,
|
|
381
|
+
util.nspath_eval('ows20:Constraint',
|
|
382
|
+
self.parent.context.namespaces), name='CoreQueryables')
|
|
383
|
+
param.append(self._write_allowed_values(self.parent.repository.queryables[qbl]))
|
|
384
|
+
|
|
385
|
+
for constraint in sorted(self.parent.context.model['constraints'].keys()):
|
|
386
|
+
param = etree.SubElement(operationsmetadata,
|
|
387
|
+
util.nspath_eval('ows20:Constraint', self.parent.context.namespaces),
|
|
388
|
+
name=constraint)
|
|
389
|
+
|
|
390
|
+
param.append(self._write_allowed_values(self.parent.context.model['constraints'][constraint]['values']))
|
|
391
|
+
|
|
392
|
+
if self.parent.profiles is not None:
|
|
393
|
+
for prof in self.parent.profiles['loaded'].keys():
|
|
394
|
+
ecnode = \
|
|
395
|
+
self.parent.profiles['loaded'][prof].get_extendedcapabilities()
|
|
396
|
+
if ecnode is not None:
|
|
397
|
+
operationsmetadata.append(ecnode)
|
|
398
|
+
|
|
399
|
+
if languages:
|
|
400
|
+
LOGGER.info('Writing section ows:Languages')
|
|
401
|
+
langs = etree.SubElement(node,
|
|
402
|
+
util.nspath_eval('ows20:Languages', self.parent.context.namespaces))
|
|
403
|
+
etree.SubElement(langs,
|
|
404
|
+
util.nspath_eval('ows20:Language', self.parent.context.namespaces)).text = self.parent.language['639_code']
|
|
405
|
+
|
|
406
|
+
if not filtercaps:
|
|
407
|
+
return node
|
|
408
|
+
|
|
409
|
+
# always write out Filter_Capabilities
|
|
410
|
+
LOGGER.info('Writing section Filter_Capabilities')
|
|
411
|
+
fltcaps = etree.SubElement(node,
|
|
412
|
+
util.nspath_eval('fes20:Filter_Capabilities', self.parent.context.namespaces))
|
|
413
|
+
|
|
414
|
+
conformance = etree.SubElement(fltcaps,
|
|
415
|
+
util.nspath_eval('fes20:Conformance', self.parent.context.namespaces))
|
|
416
|
+
|
|
417
|
+
for value in fes2.MODEL['Conformance']['values']:
|
|
418
|
+
constraint = etree.SubElement(conformance,
|
|
419
|
+
util.nspath_eval('fes20:Constraint', self.parent.context.namespaces),
|
|
420
|
+
name=value)
|
|
421
|
+
etree.SubElement(constraint,
|
|
422
|
+
util.nspath_eval('ows11:NoValues', self.parent.context.namespaces))
|
|
423
|
+
etree.SubElement(constraint,
|
|
424
|
+
util.nspath_eval('ows11:DefaultValue', self.parent.context.namespaces)).text = 'TRUE'
|
|
425
|
+
|
|
426
|
+
idcaps = etree.SubElement(fltcaps,
|
|
427
|
+
util.nspath_eval('fes20:Id_Capabilities', self.parent.context.namespaces))
|
|
428
|
+
|
|
429
|
+
for idcap in fes2.MODEL['Ids']['values']:
|
|
430
|
+
etree.SubElement(idcaps, util.nspath_eval('fes20:ResourceIdentifier',
|
|
431
|
+
self.parent.context.namespaces), name=idcap)
|
|
432
|
+
|
|
433
|
+
scalarcaps = etree.SubElement(fltcaps,
|
|
434
|
+
util.nspath_eval('fes20:Scalar_Capabilities', self.parent.context.namespaces))
|
|
435
|
+
|
|
436
|
+
etree.SubElement(scalarcaps, util.nspath_eval('fes20:LogicalOperators',
|
|
437
|
+
self.parent.context.namespaces))
|
|
438
|
+
|
|
439
|
+
cmpops = etree.SubElement(scalarcaps,
|
|
440
|
+
util.nspath_eval('fes20:ComparisonOperators', self.parent.context.namespaces))
|
|
441
|
+
|
|
442
|
+
for cmpop in sorted(fes2.MODEL['ComparisonOperators'].keys()):
|
|
443
|
+
etree.SubElement(cmpops,
|
|
444
|
+
util.nspath_eval('fes20:ComparisonOperator',
|
|
445
|
+
self.parent.context.namespaces), name=fes2.MODEL['ComparisonOperators'][cmpop]['opname'])
|
|
446
|
+
|
|
447
|
+
spatialcaps = etree.SubElement(fltcaps,
|
|
448
|
+
util.nspath_eval('fes20:Spatial_Capabilities', self.parent.context.namespaces))
|
|
449
|
+
|
|
450
|
+
geomops = etree.SubElement(spatialcaps,
|
|
451
|
+
util.nspath_eval('fes20:GeometryOperands', self.parent.context.namespaces))
|
|
452
|
+
|
|
453
|
+
for geomtype in \
|
|
454
|
+
fes2.MODEL['GeometryOperands']['values']:
|
|
455
|
+
etree.SubElement(geomops,
|
|
456
|
+
util.nspath_eval('fes20:GeometryOperand',
|
|
457
|
+
self.parent.context.namespaces), name=geomtype)
|
|
458
|
+
|
|
459
|
+
spatialops = etree.SubElement(spatialcaps,
|
|
460
|
+
util.nspath_eval('fes20:SpatialOperators', self.parent.context.namespaces))
|
|
461
|
+
|
|
462
|
+
for spatial_comparison in \
|
|
463
|
+
fes2.MODEL['SpatialOperators']['values']:
|
|
464
|
+
etree.SubElement(spatialops,
|
|
465
|
+
util.nspath_eval('fes20:SpatialOperator', self.parent.context.namespaces),
|
|
466
|
+
name=spatial_comparison)
|
|
467
|
+
|
|
468
|
+
functions = etree.SubElement(fltcaps,
|
|
469
|
+
util.nspath_eval('fes20:Functions', self.parent.context.namespaces))
|
|
470
|
+
|
|
471
|
+
for fnop in sorted(fes2.MODEL['Functions'].keys()):
|
|
472
|
+
fn = etree.SubElement(functions,
|
|
473
|
+
util.nspath_eval('fes20:Function', self.parent.context.namespaces),
|
|
474
|
+
name=fnop)
|
|
475
|
+
|
|
476
|
+
etree.SubElement(fn, util.nspath_eval('fes20:Returns',
|
|
477
|
+
self.parent.context.namespaces)).text = \
|
|
478
|
+
fes2.MODEL['Functions'][fnop]['returns']
|
|
479
|
+
|
|
480
|
+
return node
|
|
481
|
+
|
|
482
|
+
def getdomain(self):
|
|
483
|
+
''' Handle GetDomain request '''
|
|
484
|
+
if ('parametername' not in self.parent.kvp and
|
|
485
|
+
'valuereference' not in self.parent.kvp):
|
|
486
|
+
return self.exceptionreport('MissingParameterValue',
|
|
487
|
+
'parametername', 'Missing value. \
|
|
488
|
+
One of valuereference or parametername must be specified')
|
|
489
|
+
|
|
490
|
+
node = etree.Element(util.nspath_eval('csw30:GetDomainResponse',
|
|
491
|
+
self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
|
|
492
|
+
|
|
493
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
494
|
+
self.parent.context.namespaces)] = '%s %s/cat/csw/3.0/cswGetDomain.xsd' % \
|
|
495
|
+
(self.parent.context.namespaces['csw30'],
|
|
496
|
+
self.parent.config['server'].get('ogc_schemas_base'))
|
|
497
|
+
|
|
498
|
+
if 'parametername' in self.parent.kvp:
|
|
499
|
+
for pname in self.parent.kvp['parametername'].split(','):
|
|
500
|
+
LOGGER.info('Parsing parametername %s', pname)
|
|
501
|
+
domainvalue = etree.SubElement(node,
|
|
502
|
+
util.nspath_eval('csw30:DomainValues', self.parent.context.namespaces),
|
|
503
|
+
type='csw30:Record', resultType='available')
|
|
504
|
+
etree.SubElement(domainvalue,
|
|
505
|
+
util.nspath_eval('csw30:ParameterName',
|
|
506
|
+
self.parent.context.namespaces)).text = pname
|
|
507
|
+
try:
|
|
508
|
+
operation, parameter = pname.split('.')
|
|
509
|
+
except Exception as err:
|
|
510
|
+
LOGGER.debug(f'pname2 not found: {err}')
|
|
511
|
+
return node
|
|
512
|
+
if (operation in self.parent.context.model['operations'] and
|
|
513
|
+
parameter in self.parent.context.model['operations'][operation]['parameters']):
|
|
514
|
+
listofvalues = etree.SubElement(domainvalue,
|
|
515
|
+
util.nspath_eval('csw30:ListOfValues', self.parent.context.namespaces))
|
|
516
|
+
for val in \
|
|
517
|
+
sorted(self.parent.context.model['operations'][operation]\
|
|
518
|
+
['parameters'][parameter]['values']):
|
|
519
|
+
etree.SubElement(listofvalues,
|
|
520
|
+
util.nspath_eval('csw30:Value',
|
|
521
|
+
self.parent.context.namespaces)).text = val
|
|
522
|
+
|
|
523
|
+
if 'valuereference' in self.parent.kvp:
|
|
524
|
+
for pname in self.parent.kvp['valuereference'].split(','):
|
|
525
|
+
LOGGER.info('Parsing valuereference %s', pname)
|
|
526
|
+
|
|
527
|
+
if pname.find('/') == 0: # it's an XPath
|
|
528
|
+
pname2 = pname
|
|
529
|
+
else: # it's a core queryable, map to internal typename model
|
|
530
|
+
try:
|
|
531
|
+
pname2 = self.parent.repository.queryables['_all'][pname]['dbcol']
|
|
532
|
+
except Exception as err:
|
|
533
|
+
LOGGER.debug(f'pname2 not found: {err}')
|
|
534
|
+
|
|
535
|
+
# decipher typename
|
|
536
|
+
dvtype = None
|
|
537
|
+
if self.parent.profiles is not None:
|
|
538
|
+
for prof in self.parent.profiles['loaded'].keys():
|
|
539
|
+
for prefix in self.parent.profiles['loaded'][prof].prefixes:
|
|
540
|
+
if pname2.find(prefix) != -1:
|
|
541
|
+
dvtype = self.parent.profiles['loaded'][prof].typename
|
|
542
|
+
break
|
|
543
|
+
if not dvtype:
|
|
544
|
+
dvtype = 'csw30:Record'
|
|
545
|
+
|
|
546
|
+
domainvalue = etree.SubElement(node,
|
|
547
|
+
util.nspath_eval('csw30:DomainValues', self.parent.context.namespaces),
|
|
548
|
+
type=dvtype, resultType='available')
|
|
549
|
+
etree.SubElement(domainvalue,
|
|
550
|
+
util.nspath_eval('csw30:ValueReference',
|
|
551
|
+
self.parent.context.namespaces)).text = pname
|
|
552
|
+
|
|
553
|
+
try:
|
|
554
|
+
LOGGER.debug(
|
|
555
|
+
'Querying repository property %s, typename %s, \
|
|
556
|
+
domainquerytype %s',
|
|
557
|
+
pname2, dvtype, self.parent.domainquerytype)
|
|
558
|
+
|
|
559
|
+
results = self.parent.repository.query_domain(
|
|
560
|
+
pname2, dvtype, self.parent.domainquerytype, True)
|
|
561
|
+
|
|
562
|
+
LOGGER.debug('Results: %d', len(results))
|
|
563
|
+
|
|
564
|
+
if self.parent.domainquerytype == 'range':
|
|
565
|
+
rangeofvalues = etree.SubElement(domainvalue,
|
|
566
|
+
util.nspath_eval('csw30:RangeOfValues',
|
|
567
|
+
self.parent.context.namespaces))
|
|
568
|
+
|
|
569
|
+
etree.SubElement(rangeofvalues,
|
|
570
|
+
util.nspath_eval('csw30:MinValue',
|
|
571
|
+
self.parent.context.namespaces)).text = results[0][0]
|
|
572
|
+
|
|
573
|
+
etree.SubElement(rangeofvalues,
|
|
574
|
+
util.nspath_eval('csw30:MaxValue',
|
|
575
|
+
self.parent.context.namespaces)).text = results[0][1]
|
|
576
|
+
else:
|
|
577
|
+
listofvalues = etree.SubElement(domainvalue,
|
|
578
|
+
util.nspath_eval('csw30:ListOfValues',
|
|
579
|
+
self.parent.context.namespaces))
|
|
580
|
+
for result in results:
|
|
581
|
+
LOGGER.debug(str(result))
|
|
582
|
+
if (result is not None and
|
|
583
|
+
result[0] is not None): # drop null values
|
|
584
|
+
etree.SubElement(listofvalues,
|
|
585
|
+
util.nspath_eval('csw30:Value',
|
|
586
|
+
self.parent.context.namespaces),
|
|
587
|
+
count=str(result[1])).text = result[0]
|
|
588
|
+
except Exception as err:
|
|
589
|
+
# here we fail silently back to the client because
|
|
590
|
+
# CSW tells us to
|
|
591
|
+
LOGGER.exception('No results for propertyname')
|
|
592
|
+
return node
|
|
593
|
+
|
|
594
|
+
def getrecords(self):
|
|
595
|
+
''' Handle GetRecords request '''
|
|
596
|
+
|
|
597
|
+
timestamp = util.get_today_and_now()
|
|
598
|
+
|
|
599
|
+
if ('elementsetname' not in self.parent.kvp and
|
|
600
|
+
'elementname' not in self.parent.kvp):
|
|
601
|
+
if self.parent.requesttype == 'GET':
|
|
602
|
+
LOGGER.debug(self.parent.requesttype)
|
|
603
|
+
self.parent.kvp['elementsetname'] = 'summary'
|
|
604
|
+
else:
|
|
605
|
+
# mutually exclusive required
|
|
606
|
+
return self.exceptionreport('MissingParameterValue',
|
|
607
|
+
'elementsetname',
|
|
608
|
+
'Missing one of ElementSetName or ElementName parameter(s)')
|
|
609
|
+
|
|
610
|
+
if 'elementsetname' in self.parent.kvp and 'elementname' in self.parent.kvp and self.parent.kvp['elementname']:
|
|
611
|
+
# mutually exclusive required
|
|
612
|
+
return self.exceptionreport('NoApplicableCode',
|
|
613
|
+
'elementsetname',
|
|
614
|
+
'Only ONE of ElementSetName or ElementName parameter(s) is permitted')
|
|
615
|
+
|
|
616
|
+
if 'elementsetname' not in self.parent.kvp:
|
|
617
|
+
self.parent.kvp['elementsetname'] = 'summary'
|
|
618
|
+
|
|
619
|
+
if 'outputschema' not in self.parent.kvp:
|
|
620
|
+
self.parent.kvp['outputschema'] = self.parent.context.namespaces['csw30']
|
|
621
|
+
|
|
622
|
+
LOGGER.debug(self.parent.context.model['operations']['GetRecords']['parameters']['outputSchema']['values'])
|
|
623
|
+
if (self.parent.kvp['outputschema'] not in self.parent.context.model['operations']
|
|
624
|
+
['GetRecords']['parameters']['outputSchema']['values']):
|
|
625
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
626
|
+
'outputschema', 'Invalid outputSchema parameter value: %s' %
|
|
627
|
+
self.parent.kvp['outputschema'])
|
|
628
|
+
|
|
629
|
+
if 'outputformat' not in self.parent.kvp:
|
|
630
|
+
self.parent.kvp['outputformat'] = 'application/xml'
|
|
631
|
+
|
|
632
|
+
if 'HTTP_ACCEPT' in self.parent.environ:
|
|
633
|
+
LOGGER.debug('Detected HTTP Accept header: %s', self.parent.environ['HTTP_ACCEPT'])
|
|
634
|
+
formats_match = False
|
|
635
|
+
if 'outputformat' in self.parent.kvp:
|
|
636
|
+
LOGGER.debug(self.parent.kvp['outputformat'])
|
|
637
|
+
for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
|
|
638
|
+
LOGGER.info('Comparing %s and %s', ofmt, self.parent.kvp['outputformat'])
|
|
639
|
+
if ofmt.split('/')[0] in self.parent.kvp['outputformat']:
|
|
640
|
+
LOGGER.debug('Found output match')
|
|
641
|
+
formats_match = True
|
|
642
|
+
if not formats_match and self.parent.environ['HTTP_ACCEPT'] != '*/*':
|
|
643
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
644
|
+
'outputformat', 'HTTP Accept header (%s) and outputformat (%s) must agree' %
|
|
645
|
+
(self.parent.environ['HTTP_ACCEPT'], self.parent.kvp['outputformat']))
|
|
646
|
+
else:
|
|
647
|
+
for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
|
|
648
|
+
if ofmt in self.parent.context.model['operations']['GetRecords']['parameters']['outputFormat']['values']:
|
|
649
|
+
self.parent.kvp['outputformat'] = ofmt
|
|
650
|
+
break
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
if (self.parent.kvp['outputformat'] not in self.parent.context.model['operations']
|
|
654
|
+
['GetRecords']['parameters']['outputFormat']['values']):
|
|
655
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
656
|
+
'outputformat', 'Invalid outputFormat parameter value: %s' %
|
|
657
|
+
self.parent.kvp['outputformat'])
|
|
658
|
+
|
|
659
|
+
if 'outputformat' in self.parent.kvp:
|
|
660
|
+
LOGGER.info('Setting content type')
|
|
661
|
+
self.parent.contenttype = self.parent.kvp['outputformat']
|
|
662
|
+
if self.parent.kvp['outputformat'] == 'application/atom+xml':
|
|
663
|
+
self.parent.kvp['outputschema'] = self.parent.context.namespaces['atom']
|
|
664
|
+
self.parent.mode = 'opensearch'
|
|
665
|
+
|
|
666
|
+
if (('elementname' not in self.parent.kvp or
|
|
667
|
+
len(self.parent.kvp['elementname']) == 0) and
|
|
668
|
+
self.parent.kvp['elementsetname'] not in
|
|
669
|
+
self.parent.context.model['operations']['GetRecords']['parameters']
|
|
670
|
+
['ElementSetName']['values']):
|
|
671
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
672
|
+
'elementsetname', 'Invalid ElementSetName parameter value: %s' %
|
|
673
|
+
self.parent.kvp['elementsetname'])
|
|
674
|
+
|
|
675
|
+
if 'typenames' not in self.parent.kvp:
|
|
676
|
+
return self.exceptionreport('MissingParameterValue',
|
|
677
|
+
'typenames', 'Missing typenames parameter')
|
|
678
|
+
|
|
679
|
+
if ('typenames' in self.parent.kvp and
|
|
680
|
+
self.parent.requesttype == 'GET'): # passed via GET
|
|
681
|
+
#self.parent.kvp['typenames'] = self.parent.kvp['typenames'].split(',')
|
|
682
|
+
self.parent.kvp['typenames'] = ['csw:Record' if x=='Record' else x for x in self.parent.kvp['typenames'].split(',')]
|
|
683
|
+
|
|
684
|
+
if 'namespace' in self.parent.kvp:
|
|
685
|
+
LOGGER.info('resolving KVP namespace bindings')
|
|
686
|
+
LOGGER.debug(self.parent.kvp['typenames'])
|
|
687
|
+
self.parent.kvp['typenames'] = self.resolve_nsmap(self.parent.kvp['typenames'])
|
|
688
|
+
if 'elementname' in self.parent.kvp:
|
|
689
|
+
LOGGER.debug(self.parent.kvp['elementname'])
|
|
690
|
+
self.parent.kvp['elementname'] = self.resolve_nsmap(self.parent.kvp['elementname'].split(','))
|
|
691
|
+
|
|
692
|
+
if 'typenames' in self.parent.kvp:
|
|
693
|
+
for tname in self.parent.kvp['typenames']:
|
|
694
|
+
#if tname == 'Record':
|
|
695
|
+
# tname = 'csw:Record'
|
|
696
|
+
if (tname not in self.parent.context.model['operations']['GetRecords']
|
|
697
|
+
['parameters']['typeNames']['values']):
|
|
698
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
699
|
+
'typenames', 'Invalid typeNames parameter value: %s' %
|
|
700
|
+
tname)
|
|
701
|
+
|
|
702
|
+
# check elementname's
|
|
703
|
+
if 'elementname' in self.parent.kvp:
|
|
704
|
+
for ename in self.parent.kvp['elementname']:
|
|
705
|
+
if ename not in self.parent.repository.queryables['_all']:
|
|
706
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
707
|
+
'elementname', 'Invalid ElementName parameter value: %s' %
|
|
708
|
+
ename)
|
|
709
|
+
|
|
710
|
+
maxrecords_cfg = int(self.parent.config['server'].get('maxrecords', -1))
|
|
711
|
+
|
|
712
|
+
if 'maxrecords' in self.parent.kvp and self.parent.kvp['maxrecords'] == 'unlimited':
|
|
713
|
+
LOGGER.debug('Detected maxrecords=unlimited')
|
|
714
|
+
self.parent.kvp.pop('maxrecords')
|
|
715
|
+
|
|
716
|
+
if 'maxrecords' not in self.parent.kvp: # not specified by client
|
|
717
|
+
if maxrecords_cfg > -1: # specified in config
|
|
718
|
+
self.parent.kvp['maxrecords'] = maxrecords_cfg
|
|
719
|
+
else: # spec default
|
|
720
|
+
self.parent.kvp['maxrecords'] = 10
|
|
721
|
+
else: # specified by client
|
|
722
|
+
if self.parent.kvp['maxrecords'] == '':
|
|
723
|
+
self.parent.kvp['maxrecords'] = 10
|
|
724
|
+
if maxrecords_cfg > -1: # set in config
|
|
725
|
+
if int(self.parent.kvp['maxrecords']) > maxrecords_cfg:
|
|
726
|
+
self.parent.kvp['maxrecords'] = maxrecords_cfg
|
|
727
|
+
|
|
728
|
+
if any(x in opensearch.QUERY_PARAMETERS for x in self.parent.kvp):
|
|
729
|
+
LOGGER.debug('OpenSearch Geo/Time parameters detected.')
|
|
730
|
+
self.parent.kvp['constraintlanguage'] = 'FILTER'
|
|
731
|
+
try:
|
|
732
|
+
tmp_filter = opensearch.kvp2filterxml(self.parent.kvp, self.parent.context,
|
|
733
|
+
self.parent.profiles, fes_version='2.0')
|
|
734
|
+
except Exception as err:
|
|
735
|
+
return self.exceptionreport('InvalidParameterValue', 'bbox', str(err))
|
|
736
|
+
|
|
737
|
+
if tmp_filter != "":
|
|
738
|
+
self.parent.kvp['constraint'] = tmp_filter
|
|
739
|
+
LOGGER.debug('OpenSearch Geo/Time parameters to Filter: %s.', self.parent.kvp['constraint'])
|
|
740
|
+
|
|
741
|
+
if self.parent.requesttype == 'GET':
|
|
742
|
+
if 'constraint' in self.parent.kvp:
|
|
743
|
+
# GET request
|
|
744
|
+
LOGGER.debug('csw:Constraint passed over HTTP GET.')
|
|
745
|
+
if 'constraintlanguage' not in self.parent.kvp:
|
|
746
|
+
return self.exceptionreport('MissingParameterValue',
|
|
747
|
+
'constraintlanguage',
|
|
748
|
+
'constraintlanguage required when constraint specified')
|
|
749
|
+
if (self.parent.kvp['constraintlanguage'] not in
|
|
750
|
+
self.parent.context.model['operations']['GetRecords']['parameters']
|
|
751
|
+
['CONSTRAINTLANGUAGE']['values']):
|
|
752
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
753
|
+
'constraintlanguage', 'Invalid constraintlanguage: %s'
|
|
754
|
+
% self.parent.kvp['constraintlanguage'])
|
|
755
|
+
if self.parent.kvp['constraintlanguage'] == 'CQL_TEXT':
|
|
756
|
+
tmp = self.parent.kvp['constraint']
|
|
757
|
+
try:
|
|
758
|
+
LOGGER.info('Transforming CQL into fes1')
|
|
759
|
+
LOGGER.debug('CQL: %s', tmp)
|
|
760
|
+
self.parent.kvp['constraint'] = {}
|
|
761
|
+
self.parent.kvp['constraint']['type'] = 'filter'
|
|
762
|
+
cql = cql2fes(tmp, self.parent.context.namespaces, fes_version='1.0')
|
|
763
|
+
self.parent.kvp['constraint']['where'], self.parent.kvp['constraint']['values'] = fes1.parse(cql,
|
|
764
|
+
self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
|
|
765
|
+
self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
|
|
766
|
+
self.parent.kvp['constraint']['_dict'] = xml2dict(etree.tostring(cql), self.parent.context.namespaces)
|
|
767
|
+
except Exception as err:
|
|
768
|
+
LOGGER.exception('Invalid CQL query %s', tmp)
|
|
769
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
770
|
+
'constraint', 'Invalid Filter syntax')
|
|
771
|
+
elif self.parent.kvp['constraintlanguage'] == 'FILTER':
|
|
772
|
+
# validate filter XML
|
|
773
|
+
try:
|
|
774
|
+
schema = os.path.join(self.parent.config['server'].get('home'),
|
|
775
|
+
'core', 'schemas', 'ogc', 'filter', '2.0', '_wrapper.xsd')
|
|
776
|
+
LOGGER.info('Validating Filter %s.', self.parent.kvp['constraint'])
|
|
777
|
+
schema = etree.XMLSchema(file=schema)
|
|
778
|
+
parser = etree.XMLParser(schema=schema, resolve_entities=False)
|
|
779
|
+
doc = etree.fromstring(self.parent.kvp['constraint'], parser)
|
|
780
|
+
LOGGER.debug('Filter is valid XML.')
|
|
781
|
+
self.parent.kvp['constraint'] = {}
|
|
782
|
+
self.parent.kvp['constraint']['type'] = 'filter'
|
|
783
|
+
self.parent.kvp['constraint']['where'], self.parent.kvp['constraint']['values'] = \
|
|
784
|
+
fes2.parse(doc,
|
|
785
|
+
self.parent.repository.queryables['_all'],
|
|
786
|
+
self.parent.repository.dbtype,
|
|
787
|
+
self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
|
|
788
|
+
self.parent.kvp['constraint']['_dict'] = xml2dict(etree.tostring(doc), self.parent.context.namespaces)
|
|
789
|
+
except Exception as err:
|
|
790
|
+
errortext = \
|
|
791
|
+
'Exception: document not valid.\nError: %s' % str(err)
|
|
792
|
+
|
|
793
|
+
LOGGER.exception(errortext)
|
|
794
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
795
|
+
'bbox', 'Invalid Filter query: %s' % errortext)
|
|
796
|
+
else:
|
|
797
|
+
self.parent.kvp['constraint'] = {}
|
|
798
|
+
|
|
799
|
+
if 'sortby' not in self.parent.kvp:
|
|
800
|
+
self.parent.kvp['sortby'] = None
|
|
801
|
+
elif 'sortby' in self.parent.kvp and self.parent.requesttype == 'GET':
|
|
802
|
+
LOGGER.debug('Sorted query specified')
|
|
803
|
+
tmp = self.parent.kvp['sortby']
|
|
804
|
+
self.parent.kvp['sortby'] = {}
|
|
805
|
+
|
|
806
|
+
try:
|
|
807
|
+
name, order = tmp.rsplit(':', 1)
|
|
808
|
+
except Exception:
|
|
809
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
810
|
+
'sortby', 'Invalid SortBy value: must be in the format\
|
|
811
|
+
propertyname:A or propertyname:D')
|
|
812
|
+
|
|
813
|
+
try:
|
|
814
|
+
self.parent.kvp['sortby']['propertyname'] = \
|
|
815
|
+
self.parent.repository.queryables['_all'][name]['dbcol']
|
|
816
|
+
if name.find('BoundingBox') != -1 or name.find('Envelope') != -1:
|
|
817
|
+
# it's a spatial sort
|
|
818
|
+
self.parent.kvp['sortby']['spatial'] = True
|
|
819
|
+
except Exception as err:
|
|
820
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
821
|
+
'sortby', 'Invalid SortBy propertyname: %s' % name)
|
|
822
|
+
|
|
823
|
+
if order not in ['A', 'D']:
|
|
824
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
825
|
+
'sortby', 'Invalid SortBy value: sort order must be "A" or "D"')
|
|
826
|
+
|
|
827
|
+
if order == 'D':
|
|
828
|
+
self.parent.kvp['sortby']['order'] = 'DESC'
|
|
829
|
+
else:
|
|
830
|
+
self.parent.kvp['sortby']['order'] = 'ASC'
|
|
831
|
+
|
|
832
|
+
if 'startposition' not in self.parent.kvp or not self.parent.kvp['startposition']:
|
|
833
|
+
self.parent.kvp['startposition'] = 1
|
|
834
|
+
|
|
835
|
+
if 'recordids' in self.parent.kvp and self.parent.kvp['recordids'] != '':
|
|
836
|
+
# query repository
|
|
837
|
+
LOGGER.info('Querying repository with RECORD ids: %s', self.parent.kvp['recordids'])
|
|
838
|
+
results = self.parent.repository.query_ids(self.parent.kvp['recordids'].split(','))
|
|
839
|
+
matched = str(len(results))
|
|
840
|
+
if len(results) == 0:
|
|
841
|
+
return self.exceptionreport('NotFound', 'recordids',
|
|
842
|
+
'No records found for \'%s\'' % self.parent.kvp['recordids'])
|
|
843
|
+
else:
|
|
844
|
+
# query repository
|
|
845
|
+
LOGGER.info('Querying repository with constraint: %s,\
|
|
846
|
+
sortby: %s, typenames: %s, maxrecords: %s, startposition: %s.',
|
|
847
|
+
self.parent.kvp['constraint'], self.parent.kvp['sortby'], self.parent.kvp['typenames'],
|
|
848
|
+
self.parent.kvp['maxrecords'], self.parent.kvp['startposition'])
|
|
849
|
+
|
|
850
|
+
try:
|
|
851
|
+
matched, results = self.parent.repository.query(
|
|
852
|
+
constraint=self.parent.kvp['constraint'],
|
|
853
|
+
sortby=self.parent.kvp['sortby'], typenames=self.parent.kvp['typenames'],
|
|
854
|
+
maxrecords=self.parent.kvp['maxrecords'],
|
|
855
|
+
startposition=int(self.parent.kvp['startposition'])-1)
|
|
856
|
+
except Exception as err:
|
|
857
|
+
LOGGER.exception('Invalid query syntax. Query: %s', self.parent.kvp['constraint'])
|
|
858
|
+
LOGGER.exception('Invalid query syntax. Result: %s', err)
|
|
859
|
+
return self.exceptionreport('InvalidParameterValue', 'constraint',
|
|
860
|
+
'Invalid query syntax')
|
|
861
|
+
|
|
862
|
+
if int(matched) == 0:
|
|
863
|
+
returned = nextrecord = '0'
|
|
864
|
+
elif int(self.parent.kvp['maxrecords']) == 0:
|
|
865
|
+
returned = nextrecord = '0'
|
|
866
|
+
elif int(matched) < int(self.parent.kvp['startposition']):
|
|
867
|
+
returned = nextrecord = '0'
|
|
868
|
+
elif int(matched) <= int(self.parent.kvp['startposition']) + int(self.parent.kvp['maxrecords']) - 1:
|
|
869
|
+
returned = str(int(matched) - int(self.parent.kvp['startposition']) + 1)
|
|
870
|
+
nextrecord = '0'
|
|
871
|
+
else:
|
|
872
|
+
returned = str(self.parent.kvp['maxrecords'])
|
|
873
|
+
nextrecord = str(int(self.parent.kvp['startposition']) + int(self.parent.kvp['maxrecords']))
|
|
874
|
+
|
|
875
|
+
LOGGER.debug('Results: matched: %s, returned: %s, next: %s',
|
|
876
|
+
matched, returned, nextrecord)
|
|
877
|
+
|
|
878
|
+
node = etree.Element(util.nspath_eval('csw30:GetRecordsResponse',
|
|
879
|
+
self.parent.context.namespaces),
|
|
880
|
+
nsmap=self.parent.context.namespaces, version='3.0.0')
|
|
881
|
+
|
|
882
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
883
|
+
self.parent.context.namespaces)] = \
|
|
884
|
+
'%s %s/cat/csw/3.0/cswGetRecords.xsd' % \
|
|
885
|
+
(self.parent.context.namespaces['csw30'], self.parent.config['server'].get('ogc_schemas_base'))
|
|
886
|
+
|
|
887
|
+
if 'requestid' in self.parent.kvp and self.parent.kvp['requestid'] is not None:
|
|
888
|
+
etree.SubElement(node, util.nspath_eval('csw:RequestId',
|
|
889
|
+
self.parent.context.namespaces)).text = self.parent.kvp['requestid']
|
|
890
|
+
|
|
891
|
+
etree.SubElement(node, util.nspath_eval('csw30:SearchStatus',
|
|
892
|
+
self.parent.context.namespaces), timestamp=timestamp)
|
|
893
|
+
|
|
894
|
+
#if 'where' not in self.parent.kvp['constraint'] and \
|
|
895
|
+
#self.parent.kvp['resulttype'] is None:
|
|
896
|
+
# returned = '0'
|
|
897
|
+
|
|
898
|
+
searchresults = etree.SubElement(node,
|
|
899
|
+
util.nspath_eval('csw30:SearchResults', self.parent.context.namespaces),
|
|
900
|
+
numberOfRecordsMatched=matched, numberOfRecordsReturned=returned,
|
|
901
|
+
nextRecord=nextrecord, recordSchema=self.parent.kvp['outputschema'],
|
|
902
|
+
expires=timestamp, status=get_resultset_status(matched, nextrecord))
|
|
903
|
+
|
|
904
|
+
if self.parent.kvp['elementsetname'] is not None:
|
|
905
|
+
searchresults.attrib['elementSet'] = self.parent.kvp['elementsetname']
|
|
906
|
+
|
|
907
|
+
#if 'where' not in self.parent.kvp['constraint'] \
|
|
908
|
+
#and self.parent.kvp['resulttype'] is None:
|
|
909
|
+
# LOGGER.debug('Empty result set returned')
|
|
910
|
+
# return node
|
|
911
|
+
|
|
912
|
+
if results is not None:
|
|
913
|
+
if len(results) < int(self.parent.kvp['maxrecords']):
|
|
914
|
+
max1 = len(results)
|
|
915
|
+
else:
|
|
916
|
+
max1 = int(self.parent.kvp['startposition']) + (int(self.parent.kvp['maxrecords'])-1)
|
|
917
|
+
LOGGER.info('Presenting records %s - %s',
|
|
918
|
+
self.parent.kvp['startposition'], max1)
|
|
919
|
+
|
|
920
|
+
for res in results:
|
|
921
|
+
node_ = None
|
|
922
|
+
if self.parent.xslts:
|
|
923
|
+
try:
|
|
924
|
+
node_ = self.parent._render_xslt(res)
|
|
925
|
+
except Exception as err:
|
|
926
|
+
self.parent.response = self.exceptionreport(
|
|
927
|
+
'NoApplicableCode', 'service',
|
|
928
|
+
'XSLT transformation failed. Check server logs for errors')
|
|
929
|
+
return self.parent.response
|
|
930
|
+
if node_ is not None:
|
|
931
|
+
searchresults.append(node_)
|
|
932
|
+
else:
|
|
933
|
+
try:
|
|
934
|
+
if (self.parent.kvp['outputschema'] ==
|
|
935
|
+
'http://www.opengis.net/cat/csw/3.0' and
|
|
936
|
+
('csw:Record' in self.parent.kvp['typenames'] or
|
|
937
|
+
'csw30:Record' in self.parent.kvp['typenames'])):
|
|
938
|
+
# serialize csw:Record inline
|
|
939
|
+
searchresults.append(self._write_record(
|
|
940
|
+
res, self.parent.repository.queryables['_all']))
|
|
941
|
+
elif (self.parent.kvp['outputschema'] ==
|
|
942
|
+
'http://www.opengis.net/cat/csw/3.0' and
|
|
943
|
+
'csw:Record' not in self.parent.kvp['typenames']):
|
|
944
|
+
# serialize into csw:Record model
|
|
945
|
+
|
|
946
|
+
for prof in self.parent.profiles['loaded']:
|
|
947
|
+
# find source typename
|
|
948
|
+
if self.parent.profiles['loaded'][prof].typename in \
|
|
949
|
+
self.parent.kvp['typenames']:
|
|
950
|
+
typename = self.parent.profiles['loaded'][prof].typename
|
|
951
|
+
break
|
|
952
|
+
|
|
953
|
+
util.transform_mappings(
|
|
954
|
+
self.parent.repository.queryables['_all'],
|
|
955
|
+
self.parent.context.model['typenames'][typename][
|
|
956
|
+
'mappings']['csw:Record']
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
searchresults.append(self._write_record(
|
|
960
|
+
res, self.parent.repository.queryables['_all']))
|
|
961
|
+
elif self.parent.kvp['outputschema'] in self.parent.outputschemas: # use outputschema serializer
|
|
962
|
+
searchresults.append(self.parent.outputschemas[self.parent.kvp['outputschema']].write_record(res, self.parent.kvp['elementsetname'], self.parent.context, self.parent.config['server'].get('url')))
|
|
963
|
+
else: # use profile serializer
|
|
964
|
+
searchresults.append(
|
|
965
|
+
self.parent.profiles['loaded'][self.parent.kvp['outputschema']].\
|
|
966
|
+
write_record(res, self.parent.kvp['elementsetname'],
|
|
967
|
+
self.parent.kvp['outputschema'],
|
|
968
|
+
self.parent.repository.queryables['_all']))
|
|
969
|
+
except Exception as err:
|
|
970
|
+
self.parent.response = self.exceptionreport(
|
|
971
|
+
'NoApplicableCode', 'service',
|
|
972
|
+
'Record serialization failed: %s' % str(err))
|
|
973
|
+
return self.parent.response
|
|
974
|
+
|
|
975
|
+
hopcount = int(self.parent.kvp.get('hopcount', 2)) - 1
|
|
976
|
+
|
|
977
|
+
if ('federatedcatalogues' in self.parent.config and
|
|
978
|
+
self.parent.kvp.get('distributedsearch') and
|
|
979
|
+
hopcount > 0):
|
|
980
|
+
|
|
981
|
+
LOGGER.debug('DistributedSearch specified (hopCount: %s).', hopcount)
|
|
982
|
+
|
|
983
|
+
from owslib.csw import CatalogueServiceWeb
|
|
984
|
+
from owslib.ows import ExceptionReport
|
|
985
|
+
for fedcat in self.parent.config.get('federatedcatalogues', []):
|
|
986
|
+
LOGGER.info('Performing distributed search on federated \
|
|
987
|
+
catalogue: %s', fedcat)
|
|
988
|
+
try:
|
|
989
|
+
start_time = time()
|
|
990
|
+
|
|
991
|
+
remotecsw = CatalogueServiceWeb(fedcat, version='3.0.0', skip_caps=True)
|
|
992
|
+
if str(self.parent.request).startswith('http'):
|
|
993
|
+
self.parent.request = self.parent.request.split('?')[-1]
|
|
994
|
+
self.parent.request = self.parent.request.replace('mode=opensearch', '')
|
|
995
|
+
remotecsw.getrecords(xml=self.parent.request,
|
|
996
|
+
esn=self.parent.kvp['elementsetname'],
|
|
997
|
+
outputschema=self.parent.kvp['outputschema'])
|
|
998
|
+
|
|
999
|
+
fsr = etree.SubElement(searchresults, util.nspath_eval(
|
|
1000
|
+
'csw30:FederatedSearchResult',
|
|
1001
|
+
self.parent.context.namespaces),
|
|
1002
|
+
catalogueURL=fedcat)
|
|
1003
|
+
|
|
1004
|
+
msg = 'Distributed search results from catalogue %s: %s.' % (fedcat, remotecsw.results)
|
|
1005
|
+
LOGGER.debug(msg)
|
|
1006
|
+
fsr.append(etree.Comment(msg))
|
|
1007
|
+
|
|
1008
|
+
search_result = etree.SubElement(fsr, util.nspath_eval(
|
|
1009
|
+
'csw30:searchResult', self.parent.context.namespaces),
|
|
1010
|
+
recordSchema=self.parent.kvp['outputschema'],
|
|
1011
|
+
elementSetName=self.parent.kvp['elementsetname'],
|
|
1012
|
+
numberOfRecordsMatched=str(remotecsw.results['matches']),
|
|
1013
|
+
numberOfRecordsReturned=str(remotecsw.results['returned']),
|
|
1014
|
+
nextRecord=str(remotecsw.results['nextrecord']),
|
|
1015
|
+
elapsedTime=str(get_elapsed_time(start_time, time())),
|
|
1016
|
+
status=get_resultset_status(
|
|
1017
|
+
remotecsw.results['matches'],
|
|
1018
|
+
remotecsw.results['nextrecord']))
|
|
1019
|
+
|
|
1020
|
+
for result in remotecsw.records.values():
|
|
1021
|
+
search_result.append(etree.fromstring(result.xml, self.parent.context.parser))
|
|
1022
|
+
|
|
1023
|
+
except ExceptionReport as err:
|
|
1024
|
+
error_string = 'remote CSW %s returned exception: ' % fedcat
|
|
1025
|
+
searchresults.append(etree.Comment(
|
|
1026
|
+
' %s\n\n%s ' % (error_string, err)))
|
|
1027
|
+
LOGGER.exception(error_string)
|
|
1028
|
+
except Exception as err:
|
|
1029
|
+
error_string = 'remote CSW %s returned error: ' % fedcat
|
|
1030
|
+
searchresults.append(etree.Comment(
|
|
1031
|
+
' %s\n\n%s ' % (error_string, err)))
|
|
1032
|
+
LOGGER.exception(error_string)
|
|
1033
|
+
|
|
1034
|
+
searchresults.attrib['elapsedTime'] = str(get_elapsed_time(self.parent.process_time_start, time()))
|
|
1035
|
+
|
|
1036
|
+
if 'responsehandler' in self.parent.kvp: # process the handler
|
|
1037
|
+
self.parent._process_responsehandler(etree.tostring(node,
|
|
1038
|
+
pretty_print=self.parent.pretty_print))
|
|
1039
|
+
else:
|
|
1040
|
+
return node
|
|
1041
|
+
|
|
1042
|
+
def getrecordbyid(self, raw=False):
|
|
1043
|
+
''' Handle GetRecordById request '''
|
|
1044
|
+
|
|
1045
|
+
if 'id' not in self.parent.kvp:
|
|
1046
|
+
return self.exceptionreport('MissingParameterValue', 'id',
|
|
1047
|
+
'Missing id parameter')
|
|
1048
|
+
if len(self.parent.kvp['id']) < 1:
|
|
1049
|
+
return self.exceptionreport('InvalidParameterValue', 'id',
|
|
1050
|
+
'Invalid id parameter')
|
|
1051
|
+
if 'outputschema' not in self.parent.kvp:
|
|
1052
|
+
self.parent.kvp['outputschema'] = self.parent.context.namespaces['csw30']
|
|
1053
|
+
|
|
1054
|
+
if 'HTTP_ACCEPT' in self.parent.environ:
|
|
1055
|
+
LOGGER.debug('Detected HTTP Accept header: %s', self.parent.environ['HTTP_ACCEPT'])
|
|
1056
|
+
formats_match = False
|
|
1057
|
+
if 'outputformat' in self.parent.kvp:
|
|
1058
|
+
LOGGER.debug(self.parent.kvp['outputformat'])
|
|
1059
|
+
for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
|
|
1060
|
+
LOGGER.info('Comparing %s and %s', ofmt, self.parent.kvp['outputformat'])
|
|
1061
|
+
if ofmt.split('/')[0] in self.parent.kvp['outputformat']:
|
|
1062
|
+
LOGGER.debug('FOUND OUTPUT MATCH')
|
|
1063
|
+
formats_match = True
|
|
1064
|
+
if not formats_match:
|
|
1065
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
1066
|
+
'outputformat', 'HTTP Accept header (%s) and outputformat (%s) must agree' %
|
|
1067
|
+
(self.parent.environ['HTTP_ACCEPT'], self.parent.kvp['outputformat']))
|
|
1068
|
+
else:
|
|
1069
|
+
for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
|
|
1070
|
+
if ofmt in self.parent.context.model['operations']['GetRecords']['parameters']['outputFormat']['values']:
|
|
1071
|
+
self.parent.kvp['outputformat'] = ofmt
|
|
1072
|
+
break
|
|
1073
|
+
|
|
1074
|
+
if ('outputformat' in self.parent.kvp and
|
|
1075
|
+
self.parent.kvp['outputformat'] not in
|
|
1076
|
+
self.parent.context.model['operations']['GetRecordById']['parameters']
|
|
1077
|
+
['outputFormat']['values']):
|
|
1078
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
1079
|
+
'outputformat', 'Invalid outputformat parameter %s' %
|
|
1080
|
+
self.parent.kvp['outputformat'])
|
|
1081
|
+
|
|
1082
|
+
if ('outputschema' in self.parent.kvp and self.parent.kvp['outputschema'] not in
|
|
1083
|
+
self.parent.context.model['operations']['GetRecordById']['parameters']
|
|
1084
|
+
['outputSchema']['values']):
|
|
1085
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
1086
|
+
'outputschema', 'Invalid outputschema parameter %s' %
|
|
1087
|
+
self.parent.kvp['outputschema'])
|
|
1088
|
+
|
|
1089
|
+
if 'outputformat' in self.parent.kvp:
|
|
1090
|
+
self.parent.contenttype = self.parent.kvp['outputformat']
|
|
1091
|
+
if self.parent.kvp['outputformat'] == 'application/atom+xml':
|
|
1092
|
+
self.parent.kvp['outputschema'] = self.parent.context.namespaces['atom']
|
|
1093
|
+
self.parent.mode = 'opensearch'
|
|
1094
|
+
|
|
1095
|
+
if 'elementsetname' not in self.parent.kvp:
|
|
1096
|
+
self.parent.kvp['elementsetname'] = 'summary'
|
|
1097
|
+
else:
|
|
1098
|
+
if (self.parent.kvp['elementsetname'] not in
|
|
1099
|
+
self.parent.context.model['operations']['GetRecordById']['parameters']
|
|
1100
|
+
['ElementSetName']['values']):
|
|
1101
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
1102
|
+
'elementsetname', 'Invalid elementsetname parameter %s' %
|
|
1103
|
+
self.parent.kvp['elementsetname'])
|
|
1104
|
+
|
|
1105
|
+
# query repository
|
|
1106
|
+
LOGGER.info('Querying repository with ids: %s', self.parent.kvp['id'])
|
|
1107
|
+
results = self.parent.repository.query_ids([self.parent.kvp['id']])
|
|
1108
|
+
|
|
1109
|
+
if raw: # GetRepositoryItem request
|
|
1110
|
+
LOGGER.debug('GetRepositoryItem request.')
|
|
1111
|
+
if len(results) > 0:
|
|
1112
|
+
return etree.fromstring(util.getqattr(results[0],
|
|
1113
|
+
self.parent.context.md_core_model['mappings']['pycsw:XML']), self.parent.context.parser)
|
|
1114
|
+
|
|
1115
|
+
for result in results:
|
|
1116
|
+
node_ = None
|
|
1117
|
+
if self.parent.xslts:
|
|
1118
|
+
try:
|
|
1119
|
+
node_ = self.parent._render_xslt(result)
|
|
1120
|
+
except Exception as err:
|
|
1121
|
+
self.parent.response = self.exceptionreport(
|
|
1122
|
+
'NoApplicableCode', 'service',
|
|
1123
|
+
'XSLT transformation failed. Check server logs for errors %s' % str(err))
|
|
1124
|
+
return self.parent.response
|
|
1125
|
+
if node_ is not None:
|
|
1126
|
+
node = node_
|
|
1127
|
+
else:
|
|
1128
|
+
if (util.getqattr(result,
|
|
1129
|
+
self.parent.context.md_core_model['mappings']['pycsw:Typename']) == 'csw:Record'
|
|
1130
|
+
and self.parent.kvp['outputschema'] ==
|
|
1131
|
+
'http://www.opengis.net/cat/csw/3.0'):
|
|
1132
|
+
# serialize record inline
|
|
1133
|
+
node = self._write_record(
|
|
1134
|
+
result, self.parent.repository.queryables['_all'])
|
|
1135
|
+
elif (self.parent.kvp['outputschema'] ==
|
|
1136
|
+
'http://www.opengis.net/cat/csw/3.0'):
|
|
1137
|
+
# serialize into csw:Record model
|
|
1138
|
+
typename = None
|
|
1139
|
+
|
|
1140
|
+
for prof in self.parent.profiles['loaded']: # find source typename
|
|
1141
|
+
if self.parent.profiles['loaded'][prof].typename in \
|
|
1142
|
+
[util.getqattr(result, self.parent.context.md_core_model['mappings']['pycsw:Typename'])]:
|
|
1143
|
+
typename = self.parent.profiles['loaded'][prof].typename
|
|
1144
|
+
break
|
|
1145
|
+
|
|
1146
|
+
if typename is not None:
|
|
1147
|
+
util.transform_mappings(
|
|
1148
|
+
self.parent.repository.queryables['_all'],
|
|
1149
|
+
self.parent.context.model['typenames'][typename][
|
|
1150
|
+
'mappings']['csw:Record']
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
node = self._write_record( result, self.parent.repository.queryables['_all'])
|
|
1154
|
+
elif self.parent.kvp['outputschema'] in self.parent.outputschemas: # use outputschema serializer
|
|
1155
|
+
node = self.parent.outputschemas[self.parent.kvp['outputschema']].write_record(result, self.parent.kvp['elementsetname'], self.parent.context, self.parent.config['server'].get('url'))
|
|
1156
|
+
else: # it's a profile output
|
|
1157
|
+
node = self.parent.profiles['loaded'][self.parent.kvp['outputschema']].write_record(
|
|
1158
|
+
result, self.parent.kvp['elementsetname'],
|
|
1159
|
+
self.parent.kvp['outputschema'], self.parent.repository.queryables['_all'])
|
|
1160
|
+
|
|
1161
|
+
if raw and len(results) == 0:
|
|
1162
|
+
return None
|
|
1163
|
+
|
|
1164
|
+
if len(results) == 0:
|
|
1165
|
+
return self.exceptionreport('NotFound', 'id',
|
|
1166
|
+
'No repository item found for \'%s\'' % self.parent.kvp['id'])
|
|
1167
|
+
|
|
1168
|
+
return node
|
|
1169
|
+
|
|
1170
|
+
def getrepositoryitem(self):
|
|
1171
|
+
''' Handle GetRepositoryItem request '''
|
|
1172
|
+
|
|
1173
|
+
# similar to GetRecordById without csw:* wrapping
|
|
1174
|
+
node = self.parent.getrecordbyid(raw=True)
|
|
1175
|
+
if node is None:
|
|
1176
|
+
return self.exceptionreport('NotFound', 'id',
|
|
1177
|
+
'No repository item found for \'%s\'' % self.parent.kvp['id'])
|
|
1178
|
+
else:
|
|
1179
|
+
return node
|
|
1180
|
+
|
|
1181
|
+
def transaction(self):
|
|
1182
|
+
''' Handle Transaction request '''
|
|
1183
|
+
|
|
1184
|
+
try:
|
|
1185
|
+
self.parent._test_manager()
|
|
1186
|
+
except Exception as err:
|
|
1187
|
+
return self.exceptionreport('NoApplicableCode', 'transaction',
|
|
1188
|
+
str(err))
|
|
1189
|
+
|
|
1190
|
+
inserted = 0
|
|
1191
|
+
updated = 0
|
|
1192
|
+
deleted = 0
|
|
1193
|
+
|
|
1194
|
+
insertresults = []
|
|
1195
|
+
|
|
1196
|
+
LOGGER.debug('Transaction list: %s', self.parent.kvp['transactions'])
|
|
1197
|
+
|
|
1198
|
+
for ttype in self.parent.kvp['transactions']:
|
|
1199
|
+
if ttype['type'] == 'insert':
|
|
1200
|
+
try:
|
|
1201
|
+
record = metadata.parse_record(self.parent.context,
|
|
1202
|
+
ttype['xml'], self.parent.repository)[0]
|
|
1203
|
+
except Exception as err:
|
|
1204
|
+
LOGGER.exception('Transaction (insert) failed')
|
|
1205
|
+
return self.exceptionreport('NoApplicableCode', 'insert',
|
|
1206
|
+
'Transaction (insert) failed: record parsing failed: %s' \
|
|
1207
|
+
% str(err))
|
|
1208
|
+
|
|
1209
|
+
LOGGER.debug('Transaction operation: %s', record)
|
|
1210
|
+
|
|
1211
|
+
if not hasattr(record,
|
|
1212
|
+
self.parent.context.md_core_model['mappings']['pycsw:Identifier']):
|
|
1213
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1214
|
+
'insert', 'Record requires an identifier')
|
|
1215
|
+
|
|
1216
|
+
# insert new record
|
|
1217
|
+
try:
|
|
1218
|
+
self.parent.repository.insert(record, 'local',
|
|
1219
|
+
util.get_today_and_now())
|
|
1220
|
+
|
|
1221
|
+
inserted += 1
|
|
1222
|
+
insertresults.append(
|
|
1223
|
+
{'identifier': getattr(record,
|
|
1224
|
+
self.parent.context.md_core_model['mappings']['pycsw:Identifier']),
|
|
1225
|
+
'title': getattr(record,
|
|
1226
|
+
self.parent.context.md_core_model['mappings']['pycsw:Title'])})
|
|
1227
|
+
except Exception as err:
|
|
1228
|
+
LOGGER.exception('Transaction (insert) failed')
|
|
1229
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1230
|
+
'insert', 'Transaction (insert) failed: %s.' % str(err))
|
|
1231
|
+
|
|
1232
|
+
elif ttype['type'] == 'update':
|
|
1233
|
+
if 'constraint' not in ttype:
|
|
1234
|
+
# update full existing resource in repository
|
|
1235
|
+
try:
|
|
1236
|
+
record = metadata.parse_record(self.parent.context,
|
|
1237
|
+
ttype['xml'], self.parent.repository)[0]
|
|
1238
|
+
identifier = getattr(record,
|
|
1239
|
+
self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
1240
|
+
except Exception as err:
|
|
1241
|
+
return self.exceptionreport('NoApplicableCode', 'insert',
|
|
1242
|
+
'Transaction (update) failed: record parsing failed: %s' \
|
|
1243
|
+
% str(err))
|
|
1244
|
+
|
|
1245
|
+
# query repository to see if record already exists
|
|
1246
|
+
LOGGER.info('checking if record exists (%s)', identifier)
|
|
1247
|
+
|
|
1248
|
+
results = self.parent.repository.query_ids(ids=[identifier])
|
|
1249
|
+
|
|
1250
|
+
if len(results) == 0:
|
|
1251
|
+
LOGGER.debug('id %s does not exist in repository', identifier)
|
|
1252
|
+
else: # existing record, it's an update
|
|
1253
|
+
try:
|
|
1254
|
+
self.parent.repository.update(record)
|
|
1255
|
+
updated += 1
|
|
1256
|
+
except Exception as err:
|
|
1257
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1258
|
+
'update',
|
|
1259
|
+
'Transaction (update) failed: %s.' % str(err))
|
|
1260
|
+
else: # update by record property and constraint
|
|
1261
|
+
# get / set XPath for property names
|
|
1262
|
+
for rp in ttype['recordproperty']:
|
|
1263
|
+
if rp['name'] not in self.parent.repository.queryables['_all']:
|
|
1264
|
+
# is it an XPath?
|
|
1265
|
+
if rp['name'].find('/') != -1:
|
|
1266
|
+
# scan outputschemas; if match, bind
|
|
1267
|
+
for osch in self.parent.outputschemas.values():
|
|
1268
|
+
for key, value in osch.XPATH_MAPPINGS.items():
|
|
1269
|
+
if value == rp['name']: # match
|
|
1270
|
+
rp['rp'] = {'xpath': value, 'name': key}
|
|
1271
|
+
rp['rp']['dbcol'] = self.parent.repository.queryables['_all'][key]
|
|
1272
|
+
break
|
|
1273
|
+
else:
|
|
1274
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1275
|
+
'update', 'Transaction (update) failed: invalid property2: %s.' % str(rp['name']))
|
|
1276
|
+
else:
|
|
1277
|
+
rp['rp']= \
|
|
1278
|
+
self.parent.repository.queryables['_all'][rp['name']]
|
|
1279
|
+
|
|
1280
|
+
LOGGER.debug('Record Properties: %s', ttype['recordproperty'])
|
|
1281
|
+
try:
|
|
1282
|
+
updated += self.parent.repository.update(record=None,
|
|
1283
|
+
recprops=ttype['recordproperty'],
|
|
1284
|
+
constraint=ttype['constraint'])
|
|
1285
|
+
except Exception as err:
|
|
1286
|
+
LOGGER.exception('Transaction (update) failed')
|
|
1287
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1288
|
+
'update',
|
|
1289
|
+
'Transaction (update) failed: %s.' % str(err))
|
|
1290
|
+
|
|
1291
|
+
elif ttype['type'] == 'delete':
|
|
1292
|
+
deleted += self.parent.repository.delete(ttype['constraint'])
|
|
1293
|
+
|
|
1294
|
+
node = etree.Element(util.nspath_eval('csw30:TransactionResponse',
|
|
1295
|
+
self.parent.context.namespaces), nsmap=self.parent.context.namespaces, version='3.0.0')
|
|
1296
|
+
|
|
1297
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
1298
|
+
self.parent.context.namespaces)] = '%s %s/csw/3.0/cswTransaction.xsd' % \
|
|
1299
|
+
(self.parent.context.namespaces['csw30'], self.parent.config['server'].get('ogc_schemas_base'))
|
|
1300
|
+
|
|
1301
|
+
node.append(
|
|
1302
|
+
self._write_transactionsummary(
|
|
1303
|
+
inserted=inserted, updated=updated, deleted=deleted))
|
|
1304
|
+
|
|
1305
|
+
if (len(insertresults) > 0 and self.parent.kvp['verboseresponse']):
|
|
1306
|
+
# show insert result identifiers
|
|
1307
|
+
node.append(self._write_verboseresponse(insertresults))
|
|
1308
|
+
|
|
1309
|
+
return node
|
|
1310
|
+
|
|
1311
|
+
def harvest(self):
|
|
1312
|
+
''' Handle Harvest request '''
|
|
1313
|
+
|
|
1314
|
+
service_identifier = None
|
|
1315
|
+
old_identifier = None
|
|
1316
|
+
deleted = []
|
|
1317
|
+
|
|
1318
|
+
try:
|
|
1319
|
+
self.parent._test_manager()
|
|
1320
|
+
except Exception as err:
|
|
1321
|
+
return self.exceptionreport('NoApplicableCode', 'harvest', str(err))
|
|
1322
|
+
|
|
1323
|
+
if self.parent.requesttype == 'GET':
|
|
1324
|
+
if 'resourcetype' not in self.parent.kvp:
|
|
1325
|
+
return self.exceptionreport('MissingParameterValue',
|
|
1326
|
+
'resourcetype', 'Missing resourcetype parameter')
|
|
1327
|
+
if 'source' not in self.parent.kvp:
|
|
1328
|
+
return self.exceptionreport('MissingParameterValue',
|
|
1329
|
+
'source', 'Missing source parameter')
|
|
1330
|
+
|
|
1331
|
+
# validate resourcetype
|
|
1332
|
+
if (self.parent.kvp['resourcetype'] not in
|
|
1333
|
+
self.parent.context.model['operations']['Harvest']['parameters']['ResourceType']
|
|
1334
|
+
['values']):
|
|
1335
|
+
return self.exceptionreport('InvalidParameterValue',
|
|
1336
|
+
'resourcetype', 'Invalid resource type parameter: %s.\
|
|
1337
|
+
Allowable resourcetype values: %s' % (self.parent.kvp['resourcetype'],
|
|
1338
|
+
','.join(sorted(self.parent.context.model['operations']['Harvest']['parameters']
|
|
1339
|
+
['ResourceType']['values']))))
|
|
1340
|
+
|
|
1341
|
+
if (self.parent.kvp['resourcetype'].find('opengis.net') == -1 and
|
|
1342
|
+
self.parent.kvp['resourcetype'].find('urn:geoss:waf') == -1):
|
|
1343
|
+
# fetch content-based resource
|
|
1344
|
+
LOGGER.info('Fetching resource %s', self.parent.kvp['source'])
|
|
1345
|
+
try:
|
|
1346
|
+
content = util.http_request('GET', self.parent.kvp['source'])
|
|
1347
|
+
except Exception as err:
|
|
1348
|
+
errortext = 'Error fetching resource %s.\nError: %s.' % \
|
|
1349
|
+
(self.parent.kvp['source'], str(err))
|
|
1350
|
+
LOGGER.exception(errortext)
|
|
1351
|
+
return self.exceptionreport('InvalidParameterValue', 'source',
|
|
1352
|
+
errortext)
|
|
1353
|
+
else: # it's a service URL
|
|
1354
|
+
content = self.parent.kvp['source']
|
|
1355
|
+
# query repository to see if service already exists
|
|
1356
|
+
LOGGER.info('checking if service exists (%s)', content)
|
|
1357
|
+
results = self.parent.repository.query_source(content)
|
|
1358
|
+
|
|
1359
|
+
if len(results) > 0: # exists, keep identifier for update
|
|
1360
|
+
LOGGER.debug('Service already exists, keeping identifier and results')
|
|
1361
|
+
service_identifier = getattr(results[0], self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
1362
|
+
service_results = results
|
|
1363
|
+
LOGGER.debug('Identifier is %s', service_identifier)
|
|
1364
|
+
# return self.exceptionreport('NoApplicableCode', 'source',
|
|
1365
|
+
# 'Insert failed: service %s already in repository' % content)
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
if hasattr(self.parent.repository, 'local_ingest') and self.parent.repository.local_ingest:
|
|
1369
|
+
updated = 0
|
|
1370
|
+
deleted = []
|
|
1371
|
+
try:
|
|
1372
|
+
ir = self.parent.repository.insert(self.parent.kvp['resourcetype'], self.parent.kvp['source'])
|
|
1373
|
+
inserted = len(ir)
|
|
1374
|
+
except Exception as err:
|
|
1375
|
+
LOGGER.exception('Harvest (insert) failed')
|
|
1376
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1377
|
+
'source', 'Harvest (insert) failed: %s.' % str(err))
|
|
1378
|
+
else:
|
|
1379
|
+
# parse resource into record
|
|
1380
|
+
try:
|
|
1381
|
+
records_parsed = metadata.parse_record(self.parent.context,
|
|
1382
|
+
content, self.parent.repository, self.parent.kvp['resourcetype'],
|
|
1383
|
+
pagesize=self.parent.csw_harvest_pagesize)
|
|
1384
|
+
except Exception as err:
|
|
1385
|
+
LOGGER.exception(err)
|
|
1386
|
+
return self.exceptionreport('NoApplicableCode', 'source',
|
|
1387
|
+
'Harvest failed: record parsing failed: %s' % str(err))
|
|
1388
|
+
|
|
1389
|
+
inserted = 0
|
|
1390
|
+
updated = 0
|
|
1391
|
+
ir = []
|
|
1392
|
+
|
|
1393
|
+
LOGGER.debug('Total Records parsed: %d', len(records_parsed))
|
|
1394
|
+
for record in records_parsed:
|
|
1395
|
+
if self.parent.kvp['resourcetype'] == 'urn:geoss:waf':
|
|
1396
|
+
src = record.source
|
|
1397
|
+
else:
|
|
1398
|
+
src = self.parent.kvp['source']
|
|
1399
|
+
|
|
1400
|
+
setattr(record, self.parent.context.md_core_model['mappings']['pycsw:Source'],
|
|
1401
|
+
src)
|
|
1402
|
+
|
|
1403
|
+
setattr(record, self.parent.context.md_core_model['mappings']['pycsw:InsertDate'],
|
|
1404
|
+
util.get_today_and_now())
|
|
1405
|
+
|
|
1406
|
+
identifier = getattr(record,
|
|
1407
|
+
self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
1408
|
+
source = getattr(record,
|
|
1409
|
+
self.parent.context.md_core_model['mappings']['pycsw:Source'])
|
|
1410
|
+
insert_date = getattr(record,
|
|
1411
|
+
self.parent.context.md_core_model['mappings']['pycsw:InsertDate'])
|
|
1412
|
+
title = getattr(record,
|
|
1413
|
+
self.parent.context.md_core_model['mappings']['pycsw:Title'])
|
|
1414
|
+
|
|
1415
|
+
record_type = getattr(record, self.parent.context.md_core_model['mappings']['pycsw:Type'])
|
|
1416
|
+
|
|
1417
|
+
record_identifier = getattr(record, self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
1418
|
+
|
|
1419
|
+
if record_type == 'service' and service_identifier is not None: # service endpoint
|
|
1420
|
+
LOGGER.info('Replacing service identifier from %s to %s', record_identifier, service_identifier)
|
|
1421
|
+
old_identifier = record_identifier
|
|
1422
|
+
identifier = record_identifier = service_identifier
|
|
1423
|
+
if (record_type != 'service' and service_identifier is not None
|
|
1424
|
+
and old_identifier is not None): # service resource
|
|
1425
|
+
if record_identifier.find(old_identifier) != -1:
|
|
1426
|
+
new_identifier = record_identifier.replace(old_identifier, service_identifier)
|
|
1427
|
+
LOGGER.info('Replacing service resource identifier from %s to %s', record_identifier, new_identifier)
|
|
1428
|
+
identifier = record_identifier = new_identifier
|
|
1429
|
+
|
|
1430
|
+
ir.append({'identifier': identifier, 'title': title})
|
|
1431
|
+
|
|
1432
|
+
results = []
|
|
1433
|
+
if 'source' not in self.parent.config['repository']:
|
|
1434
|
+
# query repository to see if record already exists
|
|
1435
|
+
LOGGER.info('checking if record exists (%s)', identifier)
|
|
1436
|
+
results = self.parent.repository.query_ids(ids=[identifier])
|
|
1437
|
+
|
|
1438
|
+
if len(results) == 0: # check for service identifier
|
|
1439
|
+
LOGGER.info('checking if service id exists (%s)', service_identifier)
|
|
1440
|
+
results = self.parent.repository.query_ids(ids=[service_identifier])
|
|
1441
|
+
|
|
1442
|
+
LOGGER.debug(str(results))
|
|
1443
|
+
|
|
1444
|
+
if len(results) == 0: # new record, it's a new insert
|
|
1445
|
+
inserted += 1
|
|
1446
|
+
try:
|
|
1447
|
+
tmp = self.parent.repository.insert(record, source, insert_date)
|
|
1448
|
+
if tmp is not None: ir = tmp
|
|
1449
|
+
except Exception as err:
|
|
1450
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1451
|
+
'source', 'Harvest (insert) failed: %s.' % str(err))
|
|
1452
|
+
else: # existing record, it's an update
|
|
1453
|
+
if source != results[0].source:
|
|
1454
|
+
# same identifier, but different source
|
|
1455
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1456
|
+
'source', 'Insert failed: identifier %s in repository\
|
|
1457
|
+
has source %s.' % (identifier, source))
|
|
1458
|
+
|
|
1459
|
+
try:
|
|
1460
|
+
self.parent.repository.update(record)
|
|
1461
|
+
except Exception as err:
|
|
1462
|
+
return self.exceptionreport('NoApplicableCode',
|
|
1463
|
+
'source', 'Harvest (update) failed: %s.' % str(err))
|
|
1464
|
+
updated += 1
|
|
1465
|
+
|
|
1466
|
+
if service_identifier is not None:
|
|
1467
|
+
fresh_records = [str(i['identifier']) for i in ir]
|
|
1468
|
+
existing_records = [str(i.identifier) for i in service_results]
|
|
1469
|
+
|
|
1470
|
+
deleted = set(existing_records) - set(fresh_records)
|
|
1471
|
+
LOGGER.debug('Records to delete: %s', deleted)
|
|
1472
|
+
|
|
1473
|
+
for to_delete in deleted:
|
|
1474
|
+
delete_constraint = {
|
|
1475
|
+
'type': 'filter',
|
|
1476
|
+
'values': [to_delete],
|
|
1477
|
+
'where': 'identifier = :pvalue0'
|
|
1478
|
+
}
|
|
1479
|
+
self.parent.repository.delete(delete_constraint)
|
|
1480
|
+
|
|
1481
|
+
node = etree.Element(util.nspath_eval('csw:HarvestResponse',
|
|
1482
|
+
self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
|
|
1483
|
+
|
|
1484
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
1485
|
+
self.parent.context.namespaces)] = \
|
|
1486
|
+
'%s %s/csw/3.0/cswHarvest.xsd' % (self.parent.context.namespaces['csw30'],
|
|
1487
|
+
self.parent.config['server'].get('ogc_schemas_base'))
|
|
1488
|
+
|
|
1489
|
+
node2 = etree.SubElement(node,
|
|
1490
|
+
util.nspath_eval('csw:TransactionResponse',
|
|
1491
|
+
self.parent.context.namespaces), version='2.0.2')
|
|
1492
|
+
|
|
1493
|
+
node2.append(
|
|
1494
|
+
self._write_transactionsummary(inserted=len(ir), updated=updated,
|
|
1495
|
+
deleted=len(deleted)))
|
|
1496
|
+
|
|
1497
|
+
if inserted > 0:
|
|
1498
|
+
# show insert result identifiers
|
|
1499
|
+
node2.append(self._write_verboseresponse(ir))
|
|
1500
|
+
|
|
1501
|
+
if 'responsehandler' in self.parent.kvp: # process the handler
|
|
1502
|
+
self.parent._process_responsehandler(etree.tostring(node,
|
|
1503
|
+
pretty_print=self.parent.pretty_print))
|
|
1504
|
+
else:
|
|
1505
|
+
return node
|
|
1506
|
+
|
|
1507
|
+
def _write_record(self, recobj, queryables):
|
|
1508
|
+
''' Generate csw30:Record '''
|
|
1509
|
+
if self.parent.kvp['elementsetname'] == 'brief':
|
|
1510
|
+
elname = 'BriefRecord'
|
|
1511
|
+
elif self.parent.kvp['elementsetname'] == 'summary':
|
|
1512
|
+
elname = 'SummaryRecord'
|
|
1513
|
+
else:
|
|
1514
|
+
elname = 'Record'
|
|
1515
|
+
|
|
1516
|
+
record = etree.Element(util.nspath_eval('csw30:%s' % elname,
|
|
1517
|
+
self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
|
|
1518
|
+
|
|
1519
|
+
if ('elementname' in self.parent.kvp and
|
|
1520
|
+
len(self.parent.kvp['elementname']) > 0):
|
|
1521
|
+
for req_term in ['dc:identifier', 'dc:title']:
|
|
1522
|
+
if req_term not in self.parent.kvp['elementname']:
|
|
1523
|
+
value = util.getqattr(recobj, queryables[req_term]['dbcol'])
|
|
1524
|
+
etree.SubElement(record,
|
|
1525
|
+
util.nspath_eval(req_term,
|
|
1526
|
+
self.parent.context.namespaces)).text = value
|
|
1527
|
+
for elemname in self.parent.kvp['elementname']:
|
|
1528
|
+
if (elemname.find('BoundingBox') != -1 or
|
|
1529
|
+
elemname.find('Envelope') != -1):
|
|
1530
|
+
bboxel = write_boundingbox(util.getqattr(recobj,
|
|
1531
|
+
self.parent.context.md_core_model['mappings']['pycsw:BoundingBox']),
|
|
1532
|
+
self.parent.context.namespaces)
|
|
1533
|
+
if bboxel is not None:
|
|
1534
|
+
record.append(bboxel)
|
|
1535
|
+
else:
|
|
1536
|
+
value = util.getqattr(recobj, queryables[elemname]['dbcol'])
|
|
1537
|
+
elem = etree.SubElement(record,
|
|
1538
|
+
util.nspath_eval(elemname,
|
|
1539
|
+
self.parent.context.namespaces))
|
|
1540
|
+
if value:
|
|
1541
|
+
elem.text = value
|
|
1542
|
+
elif 'elementsetname' in self.parent.kvp:
|
|
1543
|
+
if (self.parent.kvp['elementsetname'] == 'full' and
|
|
1544
|
+
util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
|
|
1545
|
+
['pycsw:Typename']) == 'csw:Record' and
|
|
1546
|
+
util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
|
|
1547
|
+
['pycsw:Schema']) == 'http://www.opengis.net/cat/csw/3.0' and
|
|
1548
|
+
util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
|
|
1549
|
+
['pycsw:Type']) != 'service'):
|
|
1550
|
+
# dump record as is and exit
|
|
1551
|
+
return etree.fromstring(util.getqattr(recobj,
|
|
1552
|
+
self.parent.context.md_core_model['mappings']['pycsw:XML']), self.parent.context.parser)
|
|
1553
|
+
|
|
1554
|
+
etree.SubElement(record,
|
|
1555
|
+
util.nspath_eval('dc:identifier', self.parent.context.namespaces)).text = \
|
|
1556
|
+
util.getqattr(recobj,
|
|
1557
|
+
self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
1558
|
+
|
|
1559
|
+
for i in ['dc:title', 'dc:type']:
|
|
1560
|
+
val = util.getqattr(recobj, queryables[i]['dbcol'])
|
|
1561
|
+
if not val:
|
|
1562
|
+
val = ''
|
|
1563
|
+
etree.SubElement(record, util.nspath_eval(i,
|
|
1564
|
+
self.parent.context.namespaces)).text = val
|
|
1565
|
+
|
|
1566
|
+
if self.parent.kvp['elementsetname'] in ['summary', 'full']:
|
|
1567
|
+
# add summary elements
|
|
1568
|
+
keywords = util.getqattr(recobj, queryables['dc:subject']['dbcol'])
|
|
1569
|
+
if keywords is not None:
|
|
1570
|
+
for keyword in keywords.split(','):
|
|
1571
|
+
etree.SubElement(record,
|
|
1572
|
+
util.nspath_eval('dc:subject',
|
|
1573
|
+
self.parent.context.namespaces)).text = keyword
|
|
1574
|
+
|
|
1575
|
+
val = util.getqattr(recobj, self.parent.context.md_core_model['mappings']['pycsw:TopicCategory'])
|
|
1576
|
+
if val:
|
|
1577
|
+
etree.SubElement(record,
|
|
1578
|
+
util.nspath_eval('dc:subject',
|
|
1579
|
+
self.parent.context.namespaces), scheme='http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_TopicCategoryCode').text = val
|
|
1580
|
+
|
|
1581
|
+
val = util.getqattr(recobj, queryables['dc:format']['dbcol'])
|
|
1582
|
+
if val:
|
|
1583
|
+
etree.SubElement(record,
|
|
1584
|
+
util.nspath_eval('dc:format',
|
|
1585
|
+
self.parent.context.namespaces)).text = val
|
|
1586
|
+
|
|
1587
|
+
# links
|
|
1588
|
+
rlinks = util.getqattr(recobj,
|
|
1589
|
+
self.parent.context.md_core_model['mappings']['pycsw:Links'])
|
|
1590
|
+
|
|
1591
|
+
if rlinks:
|
|
1592
|
+
LOGGER.info(f'link type: {type(rlinks)}')
|
|
1593
|
+
for link in util.jsonify_links(rlinks):
|
|
1594
|
+
ref = etree.SubElement(record, util.nspath_eval('dct:references',
|
|
1595
|
+
self.parent.context.namespaces))
|
|
1596
|
+
if link.get('protocol'):
|
|
1597
|
+
ref.attrib['scheme'] = link['protocol']
|
|
1598
|
+
ref.text = link['url']
|
|
1599
|
+
|
|
1600
|
+
for i in ['dc:relation', 'dct:modified', 'dct:abstract']:
|
|
1601
|
+
val = util.getqattr(recobj, queryables[i]['dbcol'])
|
|
1602
|
+
if val is not None:
|
|
1603
|
+
etree.SubElement(record,
|
|
1604
|
+
util.nspath_eval(i, self.parent.context.namespaces)).text = val
|
|
1605
|
+
|
|
1606
|
+
if self.parent.kvp['elementsetname'] == 'full': # add full elements
|
|
1607
|
+
for i in ['dc:date', 'dc:creator', \
|
|
1608
|
+
'dc:publisher', 'dc:contributor', 'dc:source', \
|
|
1609
|
+
'dc:language', 'dc:rights', 'dct:alternative']:
|
|
1610
|
+
val = util.getqattr(recobj, queryables[i]['dbcol'])
|
|
1611
|
+
if val:
|
|
1612
|
+
etree.SubElement(record,
|
|
1613
|
+
util.nspath_eval(i, self.parent.context.namespaces)).text = val
|
|
1614
|
+
val = util.getqattr(recobj, queryables['dct:spatial']['dbcol'])
|
|
1615
|
+
if val:
|
|
1616
|
+
etree.SubElement(record,
|
|
1617
|
+
util.nspath_eval('dct:spatial', self.parent.context.namespaces), scheme='http://www.opengis.net/def/crs').text = val
|
|
1618
|
+
|
|
1619
|
+
# always write out ows:BoundingBox
|
|
1620
|
+
bboxel = write_boundingbox(getattr(recobj,
|
|
1621
|
+
self.parent.context.md_core_model['mappings']['pycsw:BoundingBox']),
|
|
1622
|
+
self.parent.context.namespaces)
|
|
1623
|
+
|
|
1624
|
+
if bboxel is not None:
|
|
1625
|
+
record.append(bboxel)
|
|
1626
|
+
|
|
1627
|
+
if self.parent.kvp['elementsetname'] != 'brief': # add temporal extent
|
|
1628
|
+
begin = util.getqattr(record, self.parent.context.md_core_model['mappings']['pycsw:TempExtent_begin'])
|
|
1629
|
+
end = util.getqattr(record, self.parent.context.md_core_model['mappings']['pycsw:TempExtent_end'])
|
|
1630
|
+
|
|
1631
|
+
if begin or end:
|
|
1632
|
+
tempext = etree.SubElement(record, util.nspath_eval('csw30:TemporalExtent', self.parent.context.namespaces))
|
|
1633
|
+
if begin:
|
|
1634
|
+
etree.SubElement(record, util.nspath_eval('csw30:begin', self.parent.context.namespaces)).text = begin
|
|
1635
|
+
if end:
|
|
1636
|
+
etree.SubElement(record, util.nspath_eval('csw30:end', self.parent.context.namespaces)).text = end
|
|
1637
|
+
|
|
1638
|
+
return record
|
|
1639
|
+
|
|
1640
|
+
def _parse_constraint(self, element):
|
|
1641
|
+
''' Parse csw:Constraint '''
|
|
1642
|
+
|
|
1643
|
+
query = {}
|
|
1644
|
+
|
|
1645
|
+
tmp = element.find(util.nspath_eval('fes20:Filter', self.parent.context.namespaces))
|
|
1646
|
+
if tmp is not None:
|
|
1647
|
+
LOGGER.debug('Filter constraint specified')
|
|
1648
|
+
try:
|
|
1649
|
+
query['type'] = 'filter'
|
|
1650
|
+
query['where'], query['values'] = fes2.parse(tmp,
|
|
1651
|
+
self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
|
|
1652
|
+
self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
|
|
1653
|
+
query['_dict'] = xml2dict(etree.tostring(tmp), self.parent.context.namespaces)
|
|
1654
|
+
except Exception as err:
|
|
1655
|
+
return 'Invalid Filter request: %s' % err
|
|
1656
|
+
|
|
1657
|
+
tmp = element.find(util.nspath_eval('csw30:CqlText', self.parent.context.namespaces))
|
|
1658
|
+
if tmp is not None:
|
|
1659
|
+
LOGGER.debug('CQL specified: %s.', tmp.text)
|
|
1660
|
+
try:
|
|
1661
|
+
LOGGER.info('Transforming CQL into OGC Filter')
|
|
1662
|
+
query['type'] = 'filter'
|
|
1663
|
+
cql = cql2fes(tmp.text, self.parent.context.namespaces, fes_version='2.0')
|
|
1664
|
+
query['where'], query['values'] = fes2.parse(cql,
|
|
1665
|
+
self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
|
|
1666
|
+
self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
|
|
1667
|
+
query['_dict'] = xml2dict(etree.tostring(cql), self.parent.context.namespaces)
|
|
1668
|
+
except Exception as err:
|
|
1669
|
+
LOGGER.exception('Invalid CQL request: %s', tmp.text)
|
|
1670
|
+
LOGGER.exception('Error message: %s', err)
|
|
1671
|
+
return 'Invalid CQL request'
|
|
1672
|
+
return query
|
|
1673
|
+
|
|
1674
|
+
def parse_postdata(self, postdata):
|
|
1675
|
+
''' Parse POST XML '''
|
|
1676
|
+
|
|
1677
|
+
request = {}
|
|
1678
|
+
try:
|
|
1679
|
+
LOGGER.info('Parsing %s.', postdata)
|
|
1680
|
+
doc = etree.fromstring(postdata, self.parent.context.parser)
|
|
1681
|
+
except Exception as err:
|
|
1682
|
+
errortext = \
|
|
1683
|
+
'Exception: document not well-formed.\nError: %s.' % str(err)
|
|
1684
|
+
LOGGER.exception(errortext)
|
|
1685
|
+
return errortext
|
|
1686
|
+
|
|
1687
|
+
# if this is a SOAP request, get to SOAP-ENV:Body/csw:*
|
|
1688
|
+
if (doc.tag == util.nspath_eval('soapenv:Envelope',
|
|
1689
|
+
self.parent.context.namespaces)):
|
|
1690
|
+
|
|
1691
|
+
LOGGER.debug('SOAP request specified')
|
|
1692
|
+
self.parent.soap = True
|
|
1693
|
+
|
|
1694
|
+
doc = doc.find(
|
|
1695
|
+
util.nspath_eval('soapenv:Body',
|
|
1696
|
+
self.parent.context.namespaces)).xpath('child::*')[0]
|
|
1697
|
+
|
|
1698
|
+
xsd_filename = '_wrapper.xsd'
|
|
1699
|
+
schema = os.path.join(self.parent.config['server'].get('home'),
|
|
1700
|
+
'core', 'schemas', 'ogc', 'cat', 'csw', '3.0', xsd_filename)
|
|
1701
|
+
|
|
1702
|
+
try:
|
|
1703
|
+
# it is virtually impossible to validate a csw:Transaction
|
|
1704
|
+
# csw:Insert|csw:Update (with single child) XML document.
|
|
1705
|
+
# Only validate non csw:Transaction XML
|
|
1706
|
+
|
|
1707
|
+
if doc.find('.//%s' % util.nspath_eval('csw30:Insert',
|
|
1708
|
+
self.parent.context.namespaces)) is None and \
|
|
1709
|
+
len(doc.xpath('//csw30:Update/child::*',
|
|
1710
|
+
namespaces=self.parent.context.namespaces)) == 0:
|
|
1711
|
+
|
|
1712
|
+
LOGGER.info('Validating %s', postdata)
|
|
1713
|
+
schema = etree.XMLSchema(file=schema)
|
|
1714
|
+
parser = etree.XMLParser(schema=schema, resolve_entities=False)
|
|
1715
|
+
if hasattr(self.parent, 'soap') and self.parent.soap:
|
|
1716
|
+
# validate the body of the SOAP request
|
|
1717
|
+
doc = etree.fromstring(etree.tostring(doc), parser)
|
|
1718
|
+
else: # validate the request normally
|
|
1719
|
+
doc = etree.fromstring(postdata, parser)
|
|
1720
|
+
LOGGER.debug('Request is valid XML')
|
|
1721
|
+
else: # parse Transaction without validation
|
|
1722
|
+
doc = etree.fromstring(postdata, self.parent.context.parser)
|
|
1723
|
+
except Exception as err:
|
|
1724
|
+
errortext = \
|
|
1725
|
+
'Exception: the document is not valid.\nError: %s' % str(err)
|
|
1726
|
+
LOGGER.exception(errortext)
|
|
1727
|
+
return errortext
|
|
1728
|
+
|
|
1729
|
+
request['request'] = etree.QName(doc).localname
|
|
1730
|
+
LOGGER.debug('Request operation %s specified.', request['request'])
|
|
1731
|
+
tmp = doc.find('.').attrib.get('service')
|
|
1732
|
+
if tmp is not None:
|
|
1733
|
+
request['service'] = tmp
|
|
1734
|
+
|
|
1735
|
+
tmp = doc.find('.').attrib.get('version')
|
|
1736
|
+
if tmp is not None:
|
|
1737
|
+
request['version'] = tmp
|
|
1738
|
+
|
|
1739
|
+
tmp = doc.find('.//%s' % util.nspath_eval('ows20:Version',
|
|
1740
|
+
self.parent.context.namespaces))
|
|
1741
|
+
|
|
1742
|
+
if tmp is not None:
|
|
1743
|
+
request['version'] = tmp.text
|
|
1744
|
+
|
|
1745
|
+
tmp = doc.find('.').attrib.get('updateSequence')
|
|
1746
|
+
if tmp is not None:
|
|
1747
|
+
request['updatesequence'] = tmp
|
|
1748
|
+
|
|
1749
|
+
# GetCapabilities
|
|
1750
|
+
if request['request'] == 'GetCapabilities':
|
|
1751
|
+
tmp = doc.find(util.nspath_eval('ows20:Sections',
|
|
1752
|
+
self.parent.context.namespaces))
|
|
1753
|
+
if tmp is not None:
|
|
1754
|
+
request['sections'] = ','.join([section.text for section in \
|
|
1755
|
+
doc.findall(util.nspath_eval('ows20:Sections/ows20:Section',
|
|
1756
|
+
self.parent.context.namespaces))])
|
|
1757
|
+
|
|
1758
|
+
tmp = doc.find(util.nspath_eval('ows20:AcceptFormats',
|
|
1759
|
+
self.parent.context.namespaces))
|
|
1760
|
+
if tmp is not None:
|
|
1761
|
+
request['acceptformats'] = ','.join([aformat.text for aformat in \
|
|
1762
|
+
doc.findall(util.nspath_eval('ows20:AcceptFormats/ows20:OutputFormat',
|
|
1763
|
+
self.parent.context.namespaces))])
|
|
1764
|
+
|
|
1765
|
+
tmp = doc.find(util.nspath_eval('ows20:AcceptVersions',
|
|
1766
|
+
self.parent.context.namespaces))
|
|
1767
|
+
if tmp is not None:
|
|
1768
|
+
request['acceptversions'] = ','.join([version.text for version in \
|
|
1769
|
+
doc.findall(util.nspath_eval('ows20:AcceptVersions/ows20:Version',
|
|
1770
|
+
self.parent.context.namespaces))])
|
|
1771
|
+
|
|
1772
|
+
# GetDomain
|
|
1773
|
+
if request['request'] == 'GetDomain':
|
|
1774
|
+
tmp = doc.find(util.nspath_eval('csw30:ParameterName',
|
|
1775
|
+
self.parent.context.namespaces))
|
|
1776
|
+
if tmp is not None:
|
|
1777
|
+
request['parametername'] = tmp.text
|
|
1778
|
+
|
|
1779
|
+
tmp = doc.find(util.nspath_eval('csw30:ValueReference',
|
|
1780
|
+
self.parent.context.namespaces))
|
|
1781
|
+
if tmp is not None:
|
|
1782
|
+
request['valuereference'] = tmp.text
|
|
1783
|
+
|
|
1784
|
+
# GetRecords
|
|
1785
|
+
if request['request'] == 'GetRecords':
|
|
1786
|
+
tmp = doc.find('.').attrib.get('outputSchema')
|
|
1787
|
+
request['outputschema'] = tmp if tmp is not None \
|
|
1788
|
+
else self.parent.context.namespaces['csw30']
|
|
1789
|
+
|
|
1790
|
+
tmp = doc.find('.').attrib.get('outputFormat')
|
|
1791
|
+
request['outputformat'] = tmp if tmp is not None \
|
|
1792
|
+
else 'application/xml'
|
|
1793
|
+
|
|
1794
|
+
tmp = doc.find('.').attrib.get('startPosition')
|
|
1795
|
+
request['startposition'] = tmp if tmp is not None else 1
|
|
1796
|
+
|
|
1797
|
+
tmp = doc.find('.').attrib.get('requestId')
|
|
1798
|
+
request['requestid'] = tmp if tmp is not None else None
|
|
1799
|
+
|
|
1800
|
+
tmp = doc.find('.').attrib.get('maxRecords')
|
|
1801
|
+
if tmp is not None:
|
|
1802
|
+
request['maxrecords'] = tmp
|
|
1803
|
+
|
|
1804
|
+
tmp = doc.find(util.nspath_eval('csw30:DistributedSearch',
|
|
1805
|
+
self.parent.context.namespaces))
|
|
1806
|
+
if tmp is not None:
|
|
1807
|
+
request['distributedsearch'] = True
|
|
1808
|
+
hopcount = tmp.attrib.get('hopCount')
|
|
1809
|
+
request['hopcount'] = int(hopcount) if hopcount is not None \
|
|
1810
|
+
else 2
|
|
1811
|
+
else:
|
|
1812
|
+
request['distributedsearch'] = False
|
|
1813
|
+
|
|
1814
|
+
tmp = doc.find(util.nspath_eval('csw30:ResponseHandler',
|
|
1815
|
+
self.parent.context.namespaces))
|
|
1816
|
+
if tmp is not None:
|
|
1817
|
+
request['responsehandler'] = tmp.text
|
|
1818
|
+
|
|
1819
|
+
tmp = doc.find(util.nspath_eval('csw30:Query/csw30:ElementSetName',
|
|
1820
|
+
self.parent.context.namespaces))
|
|
1821
|
+
request['elementsetname'] = tmp.text if tmp is not None else None
|
|
1822
|
+
|
|
1823
|
+
tmp = doc.find(util.nspath_eval(
|
|
1824
|
+
'csw30:Query', self.parent.context.namespaces)).attrib.get('typeNames')
|
|
1825
|
+
request['typenames'] = tmp.split() if tmp is not None \
|
|
1826
|
+
else 'csw:Record'
|
|
1827
|
+
|
|
1828
|
+
request['elementname'] = [elname.text for elname in \
|
|
1829
|
+
doc.findall(util.nspath_eval('csw30:Query/csw30:ElementName',
|
|
1830
|
+
self.parent.context.namespaces))]
|
|
1831
|
+
|
|
1832
|
+
request['constraint'] = {}
|
|
1833
|
+
tmp = doc.find(util.nspath_eval('csw30:Query/csw30:Constraint',
|
|
1834
|
+
self.parent.context.namespaces))
|
|
1835
|
+
|
|
1836
|
+
if tmp is not None:
|
|
1837
|
+
request['constraint'] = self._parse_constraint(tmp)
|
|
1838
|
+
if isinstance(request['constraint'], str): # parse error
|
|
1839
|
+
return 'Invalid Constraint: %s' % request['constraint']
|
|
1840
|
+
else:
|
|
1841
|
+
LOGGER.debug('No csw30:Constraint (fes20:Filter or csw30:CqlText) \
|
|
1842
|
+
specified')
|
|
1843
|
+
|
|
1844
|
+
tmp = doc.find(util.nspath_eval('csw30:Query/fes20:SortBy',
|
|
1845
|
+
self.parent.context.namespaces))
|
|
1846
|
+
if tmp is not None:
|
|
1847
|
+
LOGGER.debug('Sorted query specified')
|
|
1848
|
+
request['sortby'] = {}
|
|
1849
|
+
|
|
1850
|
+
try:
|
|
1851
|
+
elname = tmp.find(util.nspath_eval(
|
|
1852
|
+
'fes20:SortProperty/fes20:ValueReference',
|
|
1853
|
+
self.parent.context.namespaces)).text
|
|
1854
|
+
|
|
1855
|
+
request['sortby']['propertyname'] = \
|
|
1856
|
+
self.parent.repository.queryables['_all'][elname]['dbcol']
|
|
1857
|
+
|
|
1858
|
+
if (elname.find('BoundingBox') != -1 or
|
|
1859
|
+
elname.find('Envelope') != -1):
|
|
1860
|
+
# it's a spatial sort
|
|
1861
|
+
request['sortby']['spatial'] = True
|
|
1862
|
+
except Exception as err:
|
|
1863
|
+
errortext = \
|
|
1864
|
+
'Invalid fes20:SortProperty/fes20:ValueReference: %s' % str(err)
|
|
1865
|
+
LOGGER.exception(errortext)
|
|
1866
|
+
return errortext
|
|
1867
|
+
|
|
1868
|
+
tmp2 = tmp.find(util.nspath_eval(
|
|
1869
|
+
'fes20:SortProperty/fes20:SortOrder', self.parent.context.namespaces))
|
|
1870
|
+
request['sortby']['order'] = tmp2.text if tmp2 is not None \
|
|
1871
|
+
else 'ASC'
|
|
1872
|
+
else:
|
|
1873
|
+
request['sortby'] = None
|
|
1874
|
+
|
|
1875
|
+
# GetRecordById
|
|
1876
|
+
if request['request'] == 'GetRecordById':
|
|
1877
|
+
request['id'] = None
|
|
1878
|
+
tmp = doc.find(util.nspath_eval('csw30:Id', self.parent.context.namespaces))
|
|
1879
|
+
if tmp is not None:
|
|
1880
|
+
request['id'] = tmp.text
|
|
1881
|
+
|
|
1882
|
+
tmp = doc.find(util.nspath_eval('csw30:ElementSetName',
|
|
1883
|
+
self.parent.context.namespaces))
|
|
1884
|
+
request['elementsetname'] = tmp.text if tmp is not None \
|
|
1885
|
+
else 'summary'
|
|
1886
|
+
|
|
1887
|
+
tmp = doc.find('.').attrib.get('outputSchema')
|
|
1888
|
+
request['outputschema'] = tmp if tmp is not None \
|
|
1889
|
+
else self.parent.context.namespaces['csw30']
|
|
1890
|
+
|
|
1891
|
+
tmp = doc.find('.').attrib.get('outputFormat')
|
|
1892
|
+
if tmp is not None:
|
|
1893
|
+
request['outputformat'] = tmp
|
|
1894
|
+
|
|
1895
|
+
# Transaction
|
|
1896
|
+
if request['request'] == 'Transaction':
|
|
1897
|
+
request['verboseresponse'] = True
|
|
1898
|
+
tmp = doc.find('.').attrib.get('verboseResponse')
|
|
1899
|
+
if tmp is not None:
|
|
1900
|
+
if tmp in ['false', '0']:
|
|
1901
|
+
request['verboseresponse'] = False
|
|
1902
|
+
|
|
1903
|
+
tmp = doc.find('.').attrib.get('requestId')
|
|
1904
|
+
request['requestid'] = tmp if tmp is not None else None
|
|
1905
|
+
|
|
1906
|
+
request['transactions'] = []
|
|
1907
|
+
|
|
1908
|
+
for ttype in \
|
|
1909
|
+
doc.xpath('//csw30:Insert', namespaces=self.parent.context.namespaces):
|
|
1910
|
+
tname = ttype.attrib.get('typeName')
|
|
1911
|
+
|
|
1912
|
+
for mdrec in ttype.xpath('child::*'):
|
|
1913
|
+
xml = mdrec
|
|
1914
|
+
request['transactions'].append(
|
|
1915
|
+
{'type': 'insert', 'typename': tname, 'xml': xml})
|
|
1916
|
+
|
|
1917
|
+
for ttype in \
|
|
1918
|
+
doc.xpath('//csw30:Update', namespaces=self.parent.context.namespaces):
|
|
1919
|
+
child = ttype.xpath('child::*')
|
|
1920
|
+
update = {'type': 'update'}
|
|
1921
|
+
|
|
1922
|
+
if len(child) == 1: # it's a wholesale update
|
|
1923
|
+
update['xml'] = child[0]
|
|
1924
|
+
else: # it's a RecordProperty with Constraint Update
|
|
1925
|
+
update['recordproperty'] = []
|
|
1926
|
+
|
|
1927
|
+
for recprop in ttype.findall(
|
|
1928
|
+
util.nspath_eval('csw:RecordProperty',
|
|
1929
|
+
self.parent.context.namespaces)):
|
|
1930
|
+
rpname = recprop.find(util.nspath_eval('csw30:Name',
|
|
1931
|
+
self.parent.context.namespaces)).text
|
|
1932
|
+
rpvalue = recprop.find(
|
|
1933
|
+
util.nspath_eval('csw30:Value',
|
|
1934
|
+
self.parent.context.namespaces)).text
|
|
1935
|
+
|
|
1936
|
+
update['recordproperty'].append(
|
|
1937
|
+
{'name': rpname, 'value': rpvalue})
|
|
1938
|
+
|
|
1939
|
+
update['constraint'] = self._parse_constraint(
|
|
1940
|
+
ttype.find(util.nspath_eval('csw30:Constraint',
|
|
1941
|
+
self.parent.context.namespaces)))
|
|
1942
|
+
|
|
1943
|
+
request['transactions'].append(update)
|
|
1944
|
+
|
|
1945
|
+
for ttype in \
|
|
1946
|
+
doc.xpath('//csw30:Delete', namespaces=self.parent.context.namespaces):
|
|
1947
|
+
tname = ttype.attrib.get('typeName')
|
|
1948
|
+
constraint = self._parse_constraint(
|
|
1949
|
+
ttype.find(util.nspath_eval('csw30:Constraint',
|
|
1950
|
+
self.parent.context.namespaces)))
|
|
1951
|
+
|
|
1952
|
+
if isinstance(constraint, str): # parse error
|
|
1953
|
+
return 'Invalid Constraint: %s' % constraint
|
|
1954
|
+
|
|
1955
|
+
request['transactions'].append(
|
|
1956
|
+
{'type': 'delete', 'typename': tname, 'constraint': constraint})
|
|
1957
|
+
|
|
1958
|
+
# Harvest
|
|
1959
|
+
if request['request'] == 'Harvest':
|
|
1960
|
+
request['source'] = doc.find(util.nspath_eval('csw30:Source',
|
|
1961
|
+
self.parent.context.namespaces)).text
|
|
1962
|
+
|
|
1963
|
+
request['resourcetype'] = \
|
|
1964
|
+
doc.find(util.nspath_eval('csw30:ResourceType',
|
|
1965
|
+
self.parent.context.namespaces)).text
|
|
1966
|
+
|
|
1967
|
+
tmp = doc.find(util.nspath_eval('csw30:ResourceFormat',
|
|
1968
|
+
self.parent.context.namespaces))
|
|
1969
|
+
if tmp is not None:
|
|
1970
|
+
request['resourceformat'] = tmp.text
|
|
1971
|
+
else:
|
|
1972
|
+
request['resourceformat'] = 'application/xml'
|
|
1973
|
+
|
|
1974
|
+
tmp = doc.find(util.nspath_eval('csw30:HarvestInterval',
|
|
1975
|
+
self.parent.context.namespaces))
|
|
1976
|
+
if tmp is not None:
|
|
1977
|
+
request['harvestinterval'] = tmp.text
|
|
1978
|
+
|
|
1979
|
+
tmp = doc.find(util.nspath_eval('csw30:ResponseHandler',
|
|
1980
|
+
self.parent.context.namespaces))
|
|
1981
|
+
if tmp is not None:
|
|
1982
|
+
request['responsehandler'] = tmp.text
|
|
1983
|
+
return request
|
|
1984
|
+
|
|
1985
|
+
def _write_transactionsummary(self, inserted=0, updated=0, deleted=0):
|
|
1986
|
+
''' Write csw:TransactionSummary construct '''
|
|
1987
|
+
node = etree.Element(util.nspath_eval('csw30:TransactionSummary',
|
|
1988
|
+
self.parent.context.namespaces))
|
|
1989
|
+
|
|
1990
|
+
if 'requestid' in self.parent.kvp and self.parent.kvp['requestid'] is not None:
|
|
1991
|
+
node.attrib['requestId'] = self.parent.kvp['requestid']
|
|
1992
|
+
|
|
1993
|
+
etree.SubElement(node, util.nspath_eval('csw30:totalInserted',
|
|
1994
|
+
self.parent.context.namespaces)).text = str(inserted)
|
|
1995
|
+
|
|
1996
|
+
etree.SubElement(node, util.nspath_eval('csw30:totalUpdated',
|
|
1997
|
+
self.parent.context.namespaces)).text = str(updated)
|
|
1998
|
+
|
|
1999
|
+
etree.SubElement(node, util.nspath_eval('csw30:totalDeleted',
|
|
2000
|
+
self.parent.context.namespaces)).text = str(deleted)
|
|
2001
|
+
|
|
2002
|
+
return node
|
|
2003
|
+
|
|
2004
|
+
def _write_acknowledgement(self, root=True):
|
|
2005
|
+
''' Generate csw:Acknowledgement '''
|
|
2006
|
+
node = etree.Element(util.nspath_eval('csw30:Acknowledgement',
|
|
2007
|
+
self.parent.context.namespaces),
|
|
2008
|
+
nsmap = self.parent.context.namespaces, timeStamp=util.get_today_and_now())
|
|
2009
|
+
|
|
2010
|
+
if root:
|
|
2011
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
2012
|
+
self.parent.context.namespaces)] = \
|
|
2013
|
+
'%s %s/cat/csw/3.0/cswAll.xsd' % (self.parent.context.namespaces['csw30'], \
|
|
2014
|
+
self.parent.config['server'].get('ogc_schemas_base'))
|
|
2015
|
+
|
|
2016
|
+
node1 = etree.SubElement(node, util.nspath_eval('csw30:EchoedRequest',
|
|
2017
|
+
self.parent.context.namespaces))
|
|
2018
|
+
if self.parent.requesttype == 'POST':
|
|
2019
|
+
node1.append(etree.fromstring(self.parent.request, self.parent.context.parser))
|
|
2020
|
+
else: # GET
|
|
2021
|
+
node2 = etree.SubElement(node1, util.nspath_eval('ows:Get',
|
|
2022
|
+
self.parent.context.namespaces))
|
|
2023
|
+
|
|
2024
|
+
node2.text = self.parent.request
|
|
2025
|
+
|
|
2026
|
+
if self.parent.asynchronous:
|
|
2027
|
+
etree.SubElement(node, util.nspath_eval('csw30:RequestId',
|
|
2028
|
+
self.parent.context.namespaces)).text = self.parent.kvp['requestid']
|
|
2029
|
+
|
|
2030
|
+
return node
|
|
2031
|
+
|
|
2032
|
+
def _write_verboseresponse(self, insertresults):
|
|
2033
|
+
''' show insert result identifiers '''
|
|
2034
|
+
insertresult = etree.Element(util.nspath_eval('csw30:InsertResult',
|
|
2035
|
+
self.parent.context.namespaces))
|
|
2036
|
+
for ir in insertresults:
|
|
2037
|
+
briefrec = etree.SubElement(insertresult,
|
|
2038
|
+
util.nspath_eval('csw30:BriefRecord',
|
|
2039
|
+
self.parent.context.namespaces))
|
|
2040
|
+
|
|
2041
|
+
etree.SubElement(briefrec,
|
|
2042
|
+
util.nspath_eval('dc:identifier',
|
|
2043
|
+
self.parent.context.namespaces)).text = ir['identifier']
|
|
2044
|
+
|
|
2045
|
+
etree.SubElement(briefrec,
|
|
2046
|
+
util.nspath_eval('dc:title',
|
|
2047
|
+
self.parent.context.namespaces)).text = ir['title']
|
|
2048
|
+
|
|
2049
|
+
return insertresult
|
|
2050
|
+
|
|
2051
|
+
def _write_allowed_values(self, values):
|
|
2052
|
+
''' design pattern to write ows20:AllowedValues '''
|
|
2053
|
+
|
|
2054
|
+
allowed_values = etree.Element(util.nspath_eval('ows20:AllowedValues',
|
|
2055
|
+
self.parent.context.namespaces))
|
|
2056
|
+
|
|
2057
|
+
for value in sorted(values):
|
|
2058
|
+
etree.SubElement(allowed_values,
|
|
2059
|
+
util.nspath_eval('ows20:Value',
|
|
2060
|
+
self.parent.context.namespaces)).text = str(value)
|
|
2061
|
+
return allowed_values
|
|
2062
|
+
|
|
2063
|
+
def exceptionreport(self, code, locator, text):
|
|
2064
|
+
''' Generate ExceptionReport '''
|
|
2065
|
+
self.parent.exception = True
|
|
2066
|
+
self.parent.status = code
|
|
2067
|
+
|
|
2068
|
+
try:
|
|
2069
|
+
language = self.parent.config['server'].get('language')
|
|
2070
|
+
ogc_schemas_base = self.parent.config['server'].get('ogc_schemas_base')
|
|
2071
|
+
except Exception:
|
|
2072
|
+
LOGGER.debug('Dropping to default language and OGC schemas base')
|
|
2073
|
+
language = 'en-US'
|
|
2074
|
+
ogc_schemas_base = self.parent.context.ogc_schemas_base
|
|
2075
|
+
|
|
2076
|
+
node = etree.Element(util.nspath_eval('ows20:ExceptionReport',
|
|
2077
|
+
self.parent.context.namespaces), nsmap=self.parent.context.namespaces,
|
|
2078
|
+
version='3.0.0')
|
|
2079
|
+
|
|
2080
|
+
node.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = language
|
|
2081
|
+
|
|
2082
|
+
node.attrib[util.nspath_eval('xsi:schemaLocation',
|
|
2083
|
+
self.parent.context.namespaces)] = \
|
|
2084
|
+
'%s %s/ows/2.0/owsExceptionReport.xsd' % \
|
|
2085
|
+
(self.parent.context.namespaces['ows20'], ogc_schemas_base)
|
|
2086
|
+
|
|
2087
|
+
exception = etree.SubElement(node, util.nspath_eval('ows20:Exception',
|
|
2088
|
+
self.parent.context.namespaces),
|
|
2089
|
+
exceptionCode=code, locator=locator)
|
|
2090
|
+
|
|
2091
|
+
exception_text = etree.SubElement(exception,
|
|
2092
|
+
util.nspath_eval('ows20:ExceptionText',
|
|
2093
|
+
self.parent.context.namespaces))
|
|
2094
|
+
|
|
2095
|
+
try:
|
|
2096
|
+
exception_text.text = text
|
|
2097
|
+
except ValueError as err:
|
|
2098
|
+
exception_text.text = repr(text)
|
|
2099
|
+
|
|
2100
|
+
return node
|
|
2101
|
+
|
|
2102
|
+
def resolve_nsmap(self, list_):
|
|
2103
|
+
'''' Resolve typename bindings based on default and KVP namespaces '''
|
|
2104
|
+
|
|
2105
|
+
nsmap = {}
|
|
2106
|
+
|
|
2107
|
+
tns = []
|
|
2108
|
+
|
|
2109
|
+
LOGGER.debug('Namespace list pairs: %s', list_)
|
|
2110
|
+
|
|
2111
|
+
# bind KVP namespaces into typenames
|
|
2112
|
+
for ns in self.parent.kvp['namespace'].split(','):
|
|
2113
|
+
nspair = ns.split('(')[1].split(')')[0].split('=')
|
|
2114
|
+
if len(nspair) == 1: # default namespace
|
|
2115
|
+
nsmap['csw'] = nspair[1]
|
|
2116
|
+
else:
|
|
2117
|
+
nsmap[nspair[0]] = nspair[1]
|
|
2118
|
+
|
|
2119
|
+
LOGGER.debug('Namespace pairs: %s', nsmap)
|
|
2120
|
+
|
|
2121
|
+
for tn in list_:
|
|
2122
|
+
LOGGER.debug(tn)
|
|
2123
|
+
if tn.find(':') != -1: # resolve prefix
|
|
2124
|
+
prefix = tn.split(':')[0]
|
|
2125
|
+
if prefix in nsmap.keys(): # get uri
|
|
2126
|
+
uri = nsmap[prefix]
|
|
2127
|
+
newprefix = next(k for k, v in self.parent.context.namespaces.items() if v == uri)
|
|
2128
|
+
LOGGER.debug(uri)
|
|
2129
|
+
LOGGER.debug(prefix)
|
|
2130
|
+
LOGGER.debug(newprefix)
|
|
2131
|
+
#if prefix == 'csw30': newprefix = 'csw'
|
|
2132
|
+
newvalue = tn.replace(prefix, newprefix).replace('csw30', 'csw')
|
|
2133
|
+
else:
|
|
2134
|
+
newvalue = tn
|
|
2135
|
+
else: # default namespace
|
|
2136
|
+
newvalue = tn
|
|
2137
|
+
|
|
2138
|
+
tns.append(newvalue)
|
|
2139
|
+
|
|
2140
|
+
|
|
2141
|
+
LOGGER.debug(tns)
|
|
2142
|
+
return tns
|
|
2143
|
+
|
|
2144
|
+
def write_boundingbox(bbox, nsmap):
|
|
2145
|
+
''' Generate ows20:BoundingBox '''
|
|
2146
|
+
|
|
2147
|
+
if bbox is not None:
|
|
2148
|
+
try:
|
|
2149
|
+
bbox2 = util.wkt2geom(bbox)
|
|
2150
|
+
except Exception as err:
|
|
2151
|
+
LOGGER.debug(f'Geometry parsing error: {err}')
|
|
2152
|
+
return None
|
|
2153
|
+
|
|
2154
|
+
if len(bbox2) == 4:
|
|
2155
|
+
boundingbox = etree.Element(util.nspath_eval('ows20:BoundingBox',
|
|
2156
|
+
nsmap), crs='http://www.opengis.net/def/crs/EPSG/0/4326',
|
|
2157
|
+
dimensions='2')
|
|
2158
|
+
|
|
2159
|
+
etree.SubElement(boundingbox, util.nspath_eval('ows20:LowerCorner',
|
|
2160
|
+
nsmap)).text = '%s %s' % (bbox2[1], bbox2[0])
|
|
2161
|
+
|
|
2162
|
+
etree.SubElement(boundingbox, util.nspath_eval('ows20:UpperCorner',
|
|
2163
|
+
nsmap)).text = '%s %s' % (bbox2[3], bbox2[2])
|
|
2164
|
+
|
|
2165
|
+
return boundingbox
|
|
2166
|
+
else:
|
|
2167
|
+
return None
|
|
2168
|
+
else:
|
|
2169
|
+
return None
|
|
2170
|
+
if nextrecord == 0:
|
|
2171
|
+
searchresult_status = 'complete'
|
|
2172
|
+
elif nextrecord > 0:
|
|
2173
|
+
searchresult_status = 'subset'
|
|
2174
|
+
elif matched == 0:
|
|
2175
|
+
searchresult_status = 'none'
|
|
2176
|
+
|
|
2177
|
+
def get_resultset_status(matched, nextrecord):
|
|
2178
|
+
''' Helper function to assess status of a result set '''
|
|
2179
|
+
|
|
2180
|
+
status = 'subset' # default
|
|
2181
|
+
|
|
2182
|
+
if nextrecord == 0:
|
|
2183
|
+
status = 'complete'
|
|
2184
|
+
elif matched == 0:
|
|
2185
|
+
status = 'none'
|
|
2186
|
+
|
|
2187
|
+
return status
|
|
2188
|
+
|
|
2189
|
+
|
|
2190
|
+
def get_elapsed_time(begin, end):
|
|
2191
|
+
"""Helper function to calculate elapsed time in milliseconds."""
|
|
2192
|
+
|
|
2193
|
+
return int((end - begin) * 1000)
|