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.
Files changed (382) hide show
  1. geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
  2. geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
  3. geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
  4. geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
  5. geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
  6. geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
  7. pycsw/__init__.py +33 -0
  8. pycsw/core/__init__.py +29 -0
  9. pycsw/core/admin.py +703 -0
  10. pycsw/core/config.py +621 -0
  11. pycsw/core/etree.py +43 -0
  12. pycsw/core/formats/__init__.py +29 -0
  13. pycsw/core/formats/fmt_json.py +69 -0
  14. pycsw/core/log.py +71 -0
  15. pycsw/core/metadata.py +1996 -0
  16. pycsw/core/pygeofilter_evaluate.py +83 -0
  17. pycsw/core/repository.py +941 -0
  18. pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
  19. pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
  20. pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
  21. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
  22. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
  23. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
  24. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
  25. pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
  26. pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
  27. pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
  28. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
  29. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
  30. pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
  31. pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
  32. pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
  33. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
  34. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
  35. pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
  36. pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
  37. pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
  38. pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
  39. pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
  40. pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
  41. pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
  42. pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
  43. pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
  44. pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
  45. pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
  46. pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
  47. pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
  48. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
  49. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
  50. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
  51. pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
  52. pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
  53. pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
  54. pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
  55. pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
  56. pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
  57. pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
  58. pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
  59. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
  60. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
  61. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
  62. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
  63. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
  64. pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
  65. pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
  66. pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
  67. pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
  68. pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
  69. pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
  70. pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
  71. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
  72. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
  73. pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
  74. pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
  75. pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
  76. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
  77. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
  78. pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
  79. pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
  80. pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
  81. pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
  82. pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
  83. pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
  84. pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
  85. pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
  86. pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
  87. pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
  88. pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
  89. pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
  90. pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
  91. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
  92. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
  93. pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
  94. pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
  95. pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
  96. pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
  97. pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
  98. pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
  99. pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
  100. pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
  101. pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
  102. pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
  103. pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
  104. pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
  105. pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
  106. pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
  107. pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
  108. pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
  109. pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
  110. pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
  111. pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
  112. pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
  113. pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
  114. pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
  115. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
  116. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
  117. pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
  118. pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
  119. pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
  120. pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
  121. pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
  122. pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
  123. pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
  124. pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
  125. pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
  126. pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
  127. pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
  128. pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
  129. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
  130. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
  131. pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
  132. pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
  133. pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
  134. pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
  135. pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
  136. pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
  137. pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
  138. pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
  139. pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
  140. pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
  141. pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
  142. pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
  143. pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
  144. pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
  145. pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
  146. pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
  147. pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
  148. pycsw/core/util.py +552 -0
  149. pycsw/oaipmh.py +311 -0
  150. pycsw/ogc/__init__.py +29 -0
  151. pycsw/ogc/api/__init__.py +28 -0
  152. pycsw/ogc/api/oapi.py +558 -0
  153. pycsw/ogc/api/records.py +1414 -0
  154. pycsw/ogc/api/templates/_base.html +76 -0
  155. pycsw/ogc/api/templates/collection.html +44 -0
  156. pycsw/ogc/api/templates/collections.html +45 -0
  157. pycsw/ogc/api/templates/conformance.html +21 -0
  158. pycsw/ogc/api/templates/exception.html +8 -0
  159. pycsw/ogc/api/templates/item.html +228 -0
  160. pycsw/ogc/api/templates/items.html +337 -0
  161. pycsw/ogc/api/templates/landing_page.html +27 -0
  162. pycsw/ogc/api/templates/openapi.html +58 -0
  163. pycsw/ogc/api/templates/queryables.html +50 -0
  164. pycsw/ogc/api/templates/stac_items.html +173 -0
  165. pycsw/ogc/api/templates/static/favicon.ico +0 -0
  166. pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
  167. pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
  168. pycsw/ogc/api/util.py +252 -0
  169. pycsw/ogc/csw/__init__.py +29 -0
  170. pycsw/ogc/csw/cql.py +133 -0
  171. pycsw/ogc/csw/csw2.py +2042 -0
  172. pycsw/ogc/csw/csw3.py +2193 -0
  173. pycsw/ogc/fes/__init__.py +29 -0
  174. pycsw/ogc/fes/fes1.py +433 -0
  175. pycsw/ogc/fes/fes2.py +451 -0
  176. pycsw/ogc/gml/__init__.py +29 -0
  177. pycsw/ogc/gml/gml3.py +240 -0
  178. pycsw/ogc/gml/gml32.py +243 -0
  179. pycsw/opensearch.py +857 -0
  180. pycsw/plugins/__init__.py +29 -0
  181. pycsw/plugins/outputschemas/__init__.py +31 -0
  182. pycsw/plugins/outputschemas/atom.py +143 -0
  183. pycsw/plugins/outputschemas/datacite.py +375 -0
  184. pycsw/plugins/outputschemas/dif.py +213 -0
  185. pycsw/plugins/outputschemas/fgdc.py +180 -0
  186. pycsw/plugins/outputschemas/gm03.py +240 -0
  187. pycsw/plugins/profiles/__init__.py +29 -0
  188. pycsw/plugins/profiles/apiso/__init__.py +29 -0
  189. pycsw/plugins/profiles/apiso/apiso.py +757 -0
  190. pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
  191. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
  192. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
  193. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
  194. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
  195. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
  196. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
  197. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
  198. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
  199. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
  200. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
  201. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
  202. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
  203. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
  204. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
  205. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
  206. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
  207. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
  208. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
  209. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
  210. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
  211. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
  212. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
  213. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
  214. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
  215. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
  216. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
  217. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
  218. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
  219. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
  220. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
  221. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
  222. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
  223. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
  224. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
  225. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
  226. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
  227. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
  228. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
  229. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
  230. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
  231. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
  232. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
  233. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
  234. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
  235. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
  236. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
  237. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
  238. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
  239. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
  240. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
  241. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
  242. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
  243. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
  244. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
  245. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
  246. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
  247. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
  248. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
  249. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
  250. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
  251. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
  252. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
  253. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
  254. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
  255. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
  256. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
  257. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
  258. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
  259. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
  260. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
  261. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
  262. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
  263. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
  264. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
  265. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
  266. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
  267. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
  268. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
  269. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
  270. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
  271. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
  272. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
  273. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
  274. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
  275. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
  276. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
  277. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
  278. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
  279. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
  280. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
  281. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
  282. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
  283. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
  284. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
  285. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
  286. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
  287. pycsw/plugins/profiles/ebrim/__init__.py +29 -0
  288. pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
  289. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
  290. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
  291. pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
  292. pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
  293. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
  294. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
  295. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
  296. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
  297. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
  298. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
  299. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
  300. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
  301. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
  302. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
  303. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
  304. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
  305. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
  306. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
  307. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
  308. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
  309. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
  310. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
  311. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
  312. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
  313. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
  314. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
  315. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
  316. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
  317. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
  318. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
  319. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
  320. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
  321. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
  322. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
  323. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
  324. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
  325. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
  326. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
  327. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
  328. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
  329. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
  330. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
  331. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
  332. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
  333. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
  334. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
  335. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
  336. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
  337. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
  338. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
  339. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
  340. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
  341. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
  342. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
  343. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
  344. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
  345. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
  346. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
  347. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
  348. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
  349. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
  350. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
  351. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
  352. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
  353. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
  354. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
  355. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
  356. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
  357. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
  358. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
  359. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
  360. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
  361. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
  362. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
  363. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
  364. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
  365. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
  366. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
  367. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
  368. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
  369. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
  370. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
  371. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
  372. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
  373. pycsw/plugins/profiles/profile.py +141 -0
  374. pycsw/plugins/repository/__init__.py +29 -0
  375. pycsw/plugins/repository/odc/__init__.py +29 -0
  376. pycsw/plugins/repository/odc/odc.py +153 -0
  377. pycsw/server.py +938 -0
  378. pycsw/sru.py +217 -0
  379. pycsw/stac/__init__.py +29 -0
  380. pycsw/stac/api.py +571 -0
  381. pycsw/wsgi.py +233 -0
  382. 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