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