<resource schema="ivoa" resdir="__system">
	<meta name="creationDate">2011-03-25T10:23:00</meta>
	<meta name="subject">observational-astronomy</meta>
	<meta name="description">Definition and support code for the ObsCore
		data model and table.</meta>
	<meta name="schema-rank">5</meta>

	<!-- pull the data product types from the gathered statistics if
	available -->
	<LOOP>
		<codeItems>
		try:
			with base.getTableConn() as conn:
				# don't try this if there is no obscore table
				if not base.UnmanagedQuerier(conn).getTableType("ivoa.obscore"):
					return
				res = conn.query("select vals from dc.discrete_string_values"
					" where tablename='ivoa.ObsCore'"
					" and column_name='dataproduct_type'")

				dptypes = next(res)[0]
				def addDptypes(rd, context):
					for dptype in dptypes:
						rd.addMeta("productTypeServed", dptype)

				context.addExitFunc(addDptypes)
		except Exception as msg:
			base.ui.notifyInfo("Cannot publish product types served in"
				f" obscore ({msg});"
				" you can probably fix this by running dachs limits //obscore")
		if False:
			yield
		</codeItems>
		<events/>
	</LOOP>

	<STREAM id="obscore-columns">
		<doc>
			The columns of a (standard) obscore table.  This can be used
			to define a "native" obscore table (as opposed to the more usual
			mixins below that expose standard products via obscore.

			Even if you are sure you want to do this, better ask again...
		</doc>

		<index columns="s_ra,s_dec" kind="q3c" metaOnly="True"/>
		<index columns="obs_publisher_did" metaOnly="True"/>

		<stc>
			Polygon ICRS [s_region]
		</stc>

		<column name="dataproduct_type" type="text"
			utype="obscore:obsdataset.dataproducttype" ucd="meta.code.class"
			tablehead="Product type"
			description="High level scientific classification of the data product,
				taken from an enumeration"
			verbLevel="5">
			<property name="std">1</property>
			<property name="statistics">enumerate</property>
		</column>

		<column name="dataproduct_subtype" type="text"
			utype="obscore:obsdataset.dataproductsubtype" ucd="meta.code.class"
			tablehead="Subtype"
			description="Data product specific type"
			verbLevel="25"/>
	
		<column name="calib_level" type="smallint" required="True"
			utype="obscore:obsdataset.caliblevel" ucd="meta.code;obs.calib"
			tablehead="Calibration level"
			description="Amount of data processing that has been
				applied to the data"
			verbLevel="10" note="calib">
			<property name="std">1</property>
			<values>
				<option title="raw/custom">0</option>
				<option title="raw/standard format">1</option>
				<option title="calibrated">2</option>
				<option title="derived">3</option>
			</values>
		</column>

		<meta name="note" tag="calib">
			The calib_level flag takes the following values:

			=== ===========================================================
			 0  Raw Instrumental data requiring instrument-specific tools
			 1  Instrumental data processable with standard tools
			 2  Calibrated, science-ready data without instrument signature
			 3  Enhanced data products (e.g., mosaics)
			=== ===========================================================
		</meta>

		<column name="obs_collection" type="text"
				utype="obscore:dataid.collection" ucd="meta.id"
				tablehead="Collection"
				description="Name of a data collection (e.g., project name) this
					data belongs to"
				verbLevel="15">
			<property name="std">1</property>
			<property name="statistics">enumerate</property>
		</column>

		<column name="obs_id" type="text"
			utype="obscore:DataID.observationID" ucd="meta.id"
			tablehead="Obs. Id"
			description="Unique identifier for an observation"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_title" type="text"
			utype="obscore:dataid.title" ucd="meta.title;obs"
			tablehead="Title"
			description="Free-from title of the data set"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_publisher_did" type="text"
			utype="obscore:curation.publisherdid" ucd="meta.ref.ivoid"
			tablehead="Pub. DID"
			description="Dataset identifier assigned by the publisher."
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_creator_did" type="text"
			utype="obscore:dataid.creatordid" ucd="meta.id"
			tablehead="Creator DID"
			description="Dataset identifier assigned by the creator."
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="access_url" type="text"
			utype="obscore:access.reference" ucd="meta.ref.url"
			tablehead="Access URL"
			description="The URL at which to obtain the data set."
			verbLevel="1" displayHint="type=product">
			<property name="std">1</property>
		</column>

		<column name="access_format" type="text"
			tablehead="Media Type"
			description="MIME type of the resource at access_url"
			utype="obscore:access.format" ucd="meta.code.mime"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="access_estsize" type="bigint"
			description="Estimated size of data product"
			tablehead="Size"
			unit="kbyte" utype="obscore:access.size" ucd="phys.size;meta.file"
			verbLevel="5">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="target_name" type="text"
			description="Object a targeted observation targeted"
			tablehead="Target Name"
			utype="obscore:Target.Name" ucd="meta.id;src"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="target_class" type="text"
			description="Class of the target object (star, QSO, ...)"
			tablehead="Target Class"
			utype="obscore:target.class" ucd="src.class"
			verbLevel="20">
			<property name="std">1</property>
			<property name="statistics">enumerate</property>
		</column>

		<column name="s_ra" type="double precision"
			unit="deg"  ucd="pos.eq.ra"
			utype="obscore:char.spatialaxis.coverage.location.coord.position2d.value2.c1"
			tablehead="RA"
			description="RA of (center of) observation, ICRS"
			verbLevel="1">
			<property name="std">1</property>
		</column>

		<column name="s_dec" type="double precision"
			unit="deg" ucd="pos.eq.dec"
			utype="obscore:char.spatialaxis.coverage.location.coord.position2d.value2.c2"
			tablehead="Dec"
			description="Dec of (center of) observation, ICRS"
			verbLevel="1">
			<property name="std">1</property>
		</column>

		<column name="s_fov"
			unit="deg" ucd="phys.angSize;instr.fov"
			utype="obscore:char.spatialaxis.coverage.bounds.extent.diameter"
			tablehead="FoV"
			description="Approximate spatial extent for the region covered by the
				observation"
			verbLevel="5">
			<property name="std">1</property>
		</column>
			
		<column name="s_region" type="spoly" xtype="adql:REGION"
			utype="obscore:char.spatialaxis.coverage.support.area"
			ucd="pos.outline;obs.field"
			tablehead="coverage"
			description="Region covered by the observation, as a polygon"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="s_resolution"
			unit="arcsec"  ucd="pos.angResolution"
			utype="obscore:Char.SpatialAxis.Resolution.refval.value"
			tablehead="Space Res."
			description="Best spatial resolution within the data set"
			verbLevel="15">
			<property name="std">1</property>
		</column>
		
		<column name="t_min" type="double precision" xtype="mjd"
			unit="d" ucd="time.start;obs.exposure"
			utype="obscore:char.timeaxis.coverage.bounds.limits.starttime"
			tablehead="Min. t"
			description="Lower bound of times represented in the data set"
			verbLevel="10"
			displayHint="type=humanDate">
			<property name="std">1</property>
		</column>
		
		<column name="t_max" type="double precision" xtype="mjd"
			unit="d" ucd="time.end;obs.exposure"
			utype="obscore:char.timeaxis.coverage.bounds.limits.stoptime"
			description="Upper bound of times represented in the data set"
			tablehead="Max. t"
			verbLevel="10"
			displayHint="type=humanDate">
			<property name="std">1</property>
		</column>

		<column name="t_exptime" type="real"
			unit="s" ucd="time.duration;obs.exposure"
			utype="obscore:char.timeaxis.coverage.support.extent"
			tablehead="Exp. Time"
			description="Total exposure time"
			verbLevel="10">
			<property name="std">1</property>
		</column>
	
		<column name="t_resolution"
			unit="s" ucd="time.resolution"
			utype="obscore:char.timeaxis.resolution.refval.value"
			tablehead="Res. t"
			description="Minimal significant time interval along the time axis"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="em_min"
			unit="m" ucd="em.wl;stat.min"
			utype="obscore:char.spectralaxis.coverage.bounds.limits.lolimit"
			tablehead="λ_min"
			description="Minimal wavelength represented within the data set"
			verbLevel="10">
			<property name="std">1</property>
		</column>
		
		<column name="em_max"
			unit="m" ucd="em.wl;stat.max"
			utype="obscore:char.spectralaxis.coverage.bounds.limits.hilimit"
			tablehead="λ_max"
			description="Maximal wavelength represented within the data set"
			verbLevel="10">
			<property name="std">1</property>
		</column>

		<column name="em_res_power"
			ucd="spect.resolution"
			utype="obscore:char.spectralaxis.resolution.resolpower.refval"
			tablehead="λ/Δλ"
			description="Spectral resolving power lambda/delta lambda"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="o_ucd" type="text"
			ucd="meta.ucd"
			utype="obscore:char.observableaxis.ucd"
			tablehead="Obs. UCD"
			description="UCD for the product's observable"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="pol_states" type="text"
			ucd="meta.code;phys.polarization"
			utype="obscore:Char.PolarizationAxis.stateList"
			tablehead="Pol. States"
			description="List of polarization states in the data set"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="facility_name" type="text"
			ucd="meta.id;instr.tel"
			utype="obscore:Provenance.ObsConfig.facility.name"
			tablehead="Facility"
			description="Name of the facility at which data was taken"
			verbLevel="15">
			<property name="std">1</property>
			<property name="statistics">enumerate</property>
		</column>

		<column name="instrument_name" type="text"
			ucd="meta.id;instr"
			utype="obscore:Provenance.ObsConfig.instrument.name"
			tablehead="Instrument"
			description="Name of the instrument that produced the data"
			verbLevel="15">
			<property name="std">1</property>
			<property name="statistics">enumerate</property>
		</column>

		<!-- obscore 1.1 additions -->

		<column name="s_xel1" type="integer"
			ucd="meta.number"
			utype="obscore:Char.SpatialAxis.numBins1"
			tablehead="|X|"
			description="Number of elements (typically pixels) along the
				first spatial axis."
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="s_xel2" type="integer"
			ucd="meta.number"
			utype="obscore:Char.SpatialAxis.numBins2"
			tablehead="|Y|"
			description="Number of elements (typically pixels) along the
				second spatial axis."
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="t_xel" type="integer"
			ucd="meta.number"
			utype="obscore:Char.TimeAxis.numBins"
			tablehead="|t|"
			description="Number of elements (typically pixels) along the
				time axis."
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="em_xel" type="integer"
			ucd="meta.number"
			utype="obscore:Char.SpectralAxis.numBins"
			tablehead="|λ|"
			description="Number of elements (typically pixels) along the
				spectral axis."
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="pol_xel" type="integer"
			ucd="meta.number"
			utype="obscore:Char.PolarizationAxis.numBins"
			tablehead="|Pol|"
			description="Number of elements (typically pixels) along the
				polarization axis."
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="s_pixel_scale"
			unit="arcsec" ucd="phys.angSize;instr.pixel"
			utype="obscore:Char.SpatialAxis.Sampling.RefVal.SamplingPeriod"
			tablehead="Pix. Scale"
			description="Sampling	period in world	coordinate units along	
				the	spatial	axis"
			verbLevel="10">
			<property name="std">1</property>
		</column>

		<column name="em_ucd" type="text"
			ucd="meta.ucd"
			utype="obscore:Char.SpectralAxis.ucd"
			tablehead="Spect. UCD"
			description="Nature of the product's spectral axis (typically, em.freq,
				em.wl, or em.energy)"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="preview" type="text"
			ucd="meta.ref.url;meta.preview"
			description="URL of a preview (low-resolution, quick-to-retrieve
				representation) of the data."
			verbLevel="15"
			displayHint="type=product"/>

		<column name="source_table" type="text"
			ucd="meta.id;meta.table"
			tablehead="Source Table"
			description="Name of a TAP-queriable table this data originates from.
				This source table usually provides more information on the
				the data than what is given in obscore.  See the TAP_SCHEMA of
				the originating TAP server for details."
			verbLevel="25"/>

		<FEED source="%#obscore-extracolumns"/>
	</STREAM>

	<table id="ObsCore" adql="True" onDisk="True">
		<!-- the non-materialised obscore table is a system table, mainly to
		reduce spam from when dependent tables build it. -->
		<LOOP>
			<codeItems>
				if not base.getConfig("ivoa", "materialiseObscore"):
					yield {"materialise": "no"}
			</codeItems>
			<events>
				<system>True</system>
			</events>
		</LOOP>

		<property key="supportsModel">Obscore-1.1</property>
		<property key="supportsModelURI"
			>ivo://ivoa.net/std/obscore#core-1.1</property>
		<property key="forceStats">True</property>
		<FEED source="%#obscore-registration"/>

		<meta name="utype">ivo://ivoa.net/std/obscore#table-1.1</meta>
		<meta name="title">\getConfig{web}{sitename} Obscore Table</meta>
		<meta name="shortName">\metaString{authority.shortName} Obscore</meta>

		<meta name="_associatedDatalinkService">
			<meta name="serviceId">//obscore#dl</meta>
			<meta name="idColumn">obs_publisher_did</meta>
		</meta>

		<meta name="description">The IVOA-defined obscore table, containing
		generic metadata for datasets within this data centre.</meta>

		<LOOP>
			<codeItems>
				if base.getConfig("ivoa", "materialiseObscore"):
					yield {"materialise": "materialise"}
			</codeItems>
			<events>
				<FEED source="%#obscore-materialised-indexes"/>
			</events>
		</LOOP>

		<!-- the view creation statement is bogus (a fallback used
		in the initial creation, actually; in reality, we create
		the view creation statement from the _obscoresources table in the
		data create -->

		<viewStatement>
			create view \qName (\colNames) as (select * from (VALUES(
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL)) as q WHERE 0=1);
		</viewStatement>

		<FEED source="obscore-columns"/>
	</table>
	
	<coverage><updater sourceTable="ObsCore"/></coverage>

	<table id="_obscoresources" onDisk="True"
			dupePolicy="overwrite" primary="tableName" system="True">
		<meta name="description">
			This table contains the SQL fragments that make up this installation's
			ivoa.obscore view.  Whenever a participating table is re-made, the
			view definition is renewed with a statement made up of a union of
			all sqlFragments present in this table.

			Manipulate this table through gavo imp on tables that have an obscore
			mixin, or by dropping RDs or purging tables that are part of obscore.
		</meta>
		<column name="tableName" type="text"
			description="Name of the table that generated this fragment (usually
				through a mixin)"/>
		<column name="sqlFragment" type="text"
			description="The SQL fragment contributed from tableName"/>
		<column name="sourcerd" type="text"
			description="The RD the table was found in at input (this is
				mainly to support dachs drop -f)"/>
	</table>

	<STREAM id="create-obscore-view">
		<doc>
		This stream has the code to turn ivoa._obscoresources into a
		view definition.  This is always a preImport script on ivoa.obscore
		and overrides its built-in trivial view creation.  For a non-materialised
		obscore view, this is also a postCreation script on anything that
		contributes to ivoa.obscore.
		</doc>
		<script name="createObscoreView" type="\scriptType" lang="python">
			from gavo.protocols import obscore
			obscore.createObscoreView(table.connection)
		</script>
	</STREAM>

	<STREAM id="maintenance-scripts">
		<doc>
			These scripts must be included in any table that eventually
			ends up as a part of ivoa.obscore.
		</doc>

		<script id="addTableToObscoreSources" name="addTableToObscore"
				lang="python" type="afterMeta">
			obscoreClause = table.tableDef.expand(
				table.tableDef.getProperty("obscoreClause"))
		
			for srcRE, replacement in [
				# The following is copied in the mixin's processLate
				(r"CAST\(__COMPUTE__ AS text\) AS obs_publisher_did",
					"('ivo://\getConfig{ivoa}{authority}/~?' ||"
								" gavo_urlescape(accref)) AS obs_publisher_did")]:
				obscoreClause = re.sub(srcRE, replacement, obscoreClause)

			from gavo import rsc
			ots = rsc.TableForDef(
				base.caches.getRD("//obscore").getById("_obscoresources"),
				connection=table.connection)
			ots.addRow({"tableName": table.tableDef.getQName(),
				"sourcerd": table.tableDef.rd.sourceId,
				"sqlFragment": "SELECT %s FROM %s"%(
					obscoreClause, table.tableDef.getQName())})
		</script>

		<script id="removeTableFromObscoreSources"
				lang="python"
				type="beforeDrop"
				name="removeTableFromObscoreSources">
			from gavo import rsc
			if table.getTableType("ivoa._obscoresources"):
				table.connection.execute(
					"DELETE FROM ivoa._obscoresources WHERE tableName=%(name)s",
					{"name": table.tableDef.getQName()})
				# importing this table may take a long time, and we don't want
				# to have obscore offline for so long; so, we immediately recreate
				# it; but only if we're not materialised
				if not base.getConfig("ivoa", "materialiseObscore"):
					rsc.makeData(base.caches.getRD("//obscore").getById("create"),
						connection=table.connection)
					table.connection.commit()
		</script>

		<LOOP>
			<codeItems>
				if not base.getConfig("ivoa", "materialiseObscore"):
					yield {"materialise": "no"}
			</codeItems>
			<events>
				<FEED source="//obscore#create-obscore-view" scriptType="postCreation"/>
			</events>
		</LOOP>

	</STREAM>

	<STREAM id="_manageObscoreClause">
		<doc>
			Material for all mixins contributing to ivoa.obscore.

			At its centre, this gives a table an obscoreClause property.
			The stuff around it is just means to feed it; see the obscoreClause
			for what macros anything embedding this will have to define.
		</doc>

		<mixinPar name="source_table"
			alias="sourceTable"
			description="The table this is mixed into (you probably don't want
			to change this)."
		>'\qName'</mixinPar>

		<mixinPar name="createDIDIndex" description="Index whatever expression
			you give for the dataset identifier?  You probably want this
			on tables with more than a few hundred datasets unless the DID is
			in a column that you already index anyway."
		>False</mixinPar>

		<FEED source="%#obscore-extrapars"/>

		<events>
			<adql>True</adql>
			<meta name="isServedBy"
				ivoId="ivo://\getConfig{ivoa}{authority}/__system__/obscore/obscore"
				>\getConfig{web}{siteName} Obscore</meta>
			<!-- the casts in the following table are there to keep postgres
			from inferring weird types when parts of the union have NULL
			entries-->
			<property name="obscoreClause">
						CAST(\dataproduct_type AS text) AS dataproduct_type,
						CAST(\dataproduct_subtype AS text) AS dataproduct_subtype,
						CAST(\calib_level AS smallint) AS calib_level,
						CAST(\obs_collection AS text) AS obs_collection,
						CAST(\obs_id AS text) AS obs_id,
						CAST(\obs_title AS text) AS obs_title,
						CAST(\obs_publisher_did AS text) AS obs_publisher_did,
						CAST(\obs_creator_did AS text) AS obs_creator_did,
						CAST(\access_url AS text) AS access_url,
						CAST(\access_format AS text) AS access_format,
						CAST(\access_estsize AS bigint) AS access_estsize,
						CAST(\target_name AS text) AS target_name,
						CAST(\target_class AS text) AS target_class,
						CAST(\s_ra AS double precision) AS s_ra,
						CAST(\s_dec AS double precision) AS s_dec,
						CAST(\s_fov AS real) AS s_fov,
						CAST(\s_region AS spoly) AS s_region,
						CAST(\s_resolution AS real) AS s_resolution,
						CAST(\t_min AS double precision) AS t_min,
						CAST(\t_max AS double precision) AS t_max,
						CAST(\t_exptime AS double precision) AS t_exptime,
						CAST(\t_resolution AS double precision) AS t_resolution,
						CAST(\em_min AS real) AS em_min,
						CAST(\em_max AS real) AS em_max,
						CAST(\em_res_power AS real) AS em_res_power,
						CAST(\o_ucd AS text) AS o_ucd,
						CAST(\pol_states AS text) AS pol_states,
						CAST(\facility_name AS text) AS facility_name,
						CAST(\instrument_name AS text) AS instrument_name,
						CAST(\s_xel1 AS integer) AS s_xel1,
						CAST(\s_xel2 AS integer) AS s_xel2,
						CAST(\t_xel AS integer) AS t_xel,
						CAST(\em_xel AS integer) AS em_xel,
						CAST(\pol_xel AS integer) AS pol_xel,
						CAST(\s_pixel_scale AS real) AS s_pixel_scale,
						CAST(\em_ucd AS text) AS em_ucd,
						CAST(\preview AS text) AS preview,
						CAST(\source_table AS text) AS source_table
			</property>
			<FEED source="%#obscore-extraevents"/>
			<FEED source="maintenance-scripts"/>
		</events>

		<processLate id="obscore-processLate">
			<doc>
				Minor housekeeping chores for obscore tables.
			</doc>
			<setup imports="gavo.rscdef"/>
			<code><![CDATA[
				# delay this so we're not raising false alarm when onDisk
				# is declared after the mixin
				if not substrate.onDisk:
					raise base.StructureError("Only onDisk tables can be obscore"
						" published, but %s is not."%substrate.id)

				# delay this so everything is already bound.
				if base.parseBooleanLiteral(mixinPars["createDIDIndex"]):
					content = mixinPars["obs_publisher_did"]
					if content=='__COMPUTE__':
						# The following is copied in addTableToObscoreSources
						content = ("('ivo://\getConfig{ivoa}{authority}/~?' ||"
							"gavo_urlescape(accref))")
					substrate.feedObject("index",
						base.makeStruct(rscdef.DBIndex,
							content_=content,
							name="obscore_%s_did_index"%substrate.id))

				# This is a bit of a hack to enable scripts like
				# analyze-obscore.py: If someone puts an attribute
				# obscore_mixin_hook into gavo.rscdef (which is totally
				# random but something all the various mixins see)
				# call it with all the information we have.
				if hasattr(rscdef, "obscore_mixin_hook"):
					rscdef.obscore_mixin_hook(mixinPars, root, substrate, context)
			]]></code>
		</processLate>
	</STREAM>

	<STREAM id="_publishCommon">
		<doc>
			Common elements for (almost all) ObsTAP publication mixins.
		</doc>

		<mixinPar name="dataproduct_subtype"
			alias="productSubtype"
			description="File subtype.  Details pending"
			>NULL</mixinPar>
		<mixinPar name="calib_level"
			alias="calibLevel"
			description="Calibration level of data, a number between 0 and 3;
				for details, see
				http://dc.g-vo.org/tableinfo/ivoa.obscore#note-calib"
			>2</mixinPar>
		<mixinPar name="obs_collection"
			alias="collectionName"
			description="A human-readable name for this collection.  This should
				be short, so don't just use the resource title"
				>'unnamed'</mixinPar>
		<mixinPar name="target_name"
			alias="targetName"
			description="Name of the target object."
			>NULL</mixinPar>
		<mixinPar name="target_class"
			alias="targetClass"
			description="Class of target object(s).  You should take
				whatever you put here from
				http://simbad.u-strasbg.fr/guide/chF.htx">NULL</mixinPar>
		<mixinPar name="t_resolution"
			alias="tResolution"
			description="Temporal resolution (in seconds)"
			>NULL</mixinPar>
		<mixinPar name="em_res_power"
			alias="emResPower"
			description="Spectral resolution as lambda/delta lambda"
			>NULL</mixinPar>
		<mixinPar name="t_exptime"
			alias="expTime"
			description="Total time of event counting.  This simply is
				t_max-t_min for simple exposures.">NULL</mixinPar>
		<mixinPar name="pol_states"
			alias="polStates"
			description="List of polarization states present in the data;
				if you give something, use the convention of choosing the appropriate
				from  {I Q U V RR LL RL LR XX YY XY YX POLI POLA} and write them
				*in alphabetical order* with / separators, e.g. /I/Q/XX/."
			>NULL</mixinPar>
		<mixinPar name="facility_name"
			alias="facilityName"
			description="The institute or observatory at which the data was produced"
			>NULL</mixinPar>
		<mixinPar name="s_xel1"
			alias="sXel1"
			description="Number of pixels along the first spatial axis"
			>NULL</mixinPar>
		<mixinPar name="s_xel2"
			alias="sXel2"
			description="Number of pixels along the second spatial axis"
			>NULL</mixinPar>
		<mixinPar name="t_xel"
			alias="tXel"
			description="Number of samples along the time axis"
			>NULL</mixinPar>
		<mixinPar name="em_xel"
			alias="emXel"
			description="Number of samples along the spectral axis"
			>NULL</mixinPar>
		<mixinPar name="pol_xel"
			alias="polXel"
			description="Number of polarisation states in this product"
			>NULL</mixinPar>
		<mixinPar name="s_pixel_scale"
			alias="sPixelScale"
			description="Size of a spatial pixel (in arcsec)"
			>NULL</mixinPar>
		<mixinPar name="em_ucd"
			alias="emUCD"
			description="UCD of the spectral axis as defined by the spectrum DM,
				plus a few values defined in obscore 1.1 for Doppler axes"
			>NULL</mixinPar>
		<mixinPar name="preview"
			description="URL of a preview for the data.  Set to NULL if you
				don't have any previews."
			>NULL</mixinPar>
		
		<LFEED source="_manageObscoreClause"/>
	</STREAM>

	<STREAM id="_publishProduct">
		<doc>
			publish mixin parameters deriving from products#table interface
			fields.
		</doc>
		<mixinPar name="obs_id"
			alias="obsId"
			description="Identifier of the data set.  Only change this when
				you do not mix in products."
			>accref</mixinPar>
		<mixinPar name="obs_publisher_did"
			alias="did"
			description="Global identifier of the data set.  Leave __COMPUTE__
				for tables mixing in products."
			>__COMPUTE__</mixinPar>
		<mixinPar name="access_url"
			alias="accessURL"
			description="URL at which the product can be obtained.  Leave as is
				for tables mixing in products."
			>accref</mixinPar>
		<mixinPar name="access_format"
			alias="mime"
			description="The MIME type of the product file.  Only touch if
				you do not mix in products."
			>mime</mixinPar>
		<mixinPar name="access_estsize"
			alias="size"
			description="The estimated size of the product in kilobytes.
				Only touch when you do not mix in products#table."
			>accsize/1024</mixinPar>
		<mixinPar name="preview" description="The preview URL will
			point to the products service; if you don't have any previews,
			you will have to set this to NULL manually."
				>accref || '?preview=True'</mixinPar>
	</STREAM>

	<mixinDef id="publish">
		<doc>
			Publish this table to ObsTAP.

			This means mapping or giving quite a bit of data from the present
			table to ObsCore rows.  Internally, this information is converted
			to an SQL select statement used within a create view statement.
			In consequence, you must give *SQL* expressions in the parameter
			values; just naked column names from your input table are ok,
			of course.  Most parameters are set to NULL or appropriate
			defaults for tables mixing in //products#table.

			Since the mixin generates script elements, it cannot be used
			in untrusted RDs.  The fact that you can enter raw SQL also
			means you will get ugly error messages if you give invalid
			parameters.

			Some items are filled from product interface fields automatically.
			You must change these if you obscore-publish tables not mixin
			in products.

			Note: you must say ``dachs imp //obscore`` before anything
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="dataproduct_type"
			alias="productType"
			description="Data product type; one of image, cube, spectrum, sed,
				timeseries, visibility, event, or NULL if None of the above"/>
		<mixinPar name="obs_title"
			alias="title"
			description="A human-readable title of the data set."
			>NULL</mixinPar>
		<mixinPar name="s_ra"
			alias="ra"
			description="Center RA"
			>NULL</mixinPar>
		<mixinPar name="s_dec"
			alias="dec"
			description="Center Dec"
			>NULL</mixinPar>
		<mixinPar name="s_fov"
			alias="fov"
			description="Approximate diameter of region covered"
			>NULL</mixinPar>
		<mixinPar name="s_region"
			alias="coverage"
			description="A polygon giving the spatial coverage of the data
				set; this must always be in ICRS.  This is cast to an pgsphere
				spoly, which currently means that you have to provide an spoly
				(reference), too."
			>NULL</mixinPar>
		<mixinPar name="t_min"
			alias="tMin"
			description="MJD for the lower bound of times covered in the data
				set (e.g. start of exposure).  Use ts_to_mjd(ts) to get this
				from a postgres timestamp."
			>NULL</mixinPar>
		<mixinPar name="t_max"
			alias="tMax"
			description="MJD for the upper bound of times covered in the data
			set.  See tMin"
			>NULL</mixinPar>
		<mixinPar name="em_min"
			alias="emMin"
			description="Lower bound of wavelengths represented in the
			data set, in meters."
			>NULL</mixinPar>
		<mixinPar name="em_max"
			alias="emMax"
			description="Upper bound of wavelengths represented in the data
				set, in meters."
			>NULL</mixinPar>
		<mixinPar name="o_ucd"
			alias="oUCD"
			description="UCD of the observable quantity, e.g., em.opt for
				wide-band optical frames."
			>NULL</mixinPar>
		<mixinPar name="s_resolution"
			alias="sResolution" description="The (best) angular resolution
				within the data set, in arcsecs"
			>NULL</mixinPar>
		<mixinPar name="instrument_name"
			alias="instrumentName"
			description="The instrument that produced the data"
			>NULL</mixinPar>
		<mixinPar name="obs_creator_did"
			alias="creatorDID"
			description="Global identifier of the data set assigned by the
				creator.  Leave NULL unless the creator actually assigned an
				IVO id themselves.">NULL</mixinPar>
	</mixinDef>

	<mixinDef id="publishSIAP">
		<doc>
			Publish a PGS SIAP table to ObsTAP.

			This works like //obscore#publish except some defaults apply
			that copy fields that work analogously in SIAP and in ObsTAP.

			For special situations, you can, of course, override any
			of the parameters, but most of them should already be all right.
			To find out what the parameters described as "preset for SIAP"
			mean, refer to //obscore#publish.

			Note: you must say ``dachs imp //obscore`` before anything
			obscore-related will work.
		</doc>
		
		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="dataproduct_type"
			alias="productType"
			description="preset for SIAP"
			>'image'</mixinPar>
		<mixinPar name="obs_title"
			alias="title"
			description="preset for SIAP"
			>imageTitle</mixinPar>
		<mixinPar name="s_ra"
			alias="ra"
			description="preset for SIAP"
			>centerAlpha</mixinPar>
		<mixinPar name="s_dec"
			alias="dec"
			description="preset for SIAP"
			>centerDelta</mixinPar>
		<mixinPar name="s_fov"
			alias="fov"
			description="preset for SIAP; we use the
			extent along the X axis as a very rough estimate for the size.  If
			you can do better, by all means do."
			>pixelScale[1]*pixelSize[1]</mixinPar>
		<mixinPar name="s_region"
			alias="coverage"
			description="preset for SIAP"
			>coverage</mixinPar>
		<mixinPar name="t_min"
			alias="tMin"
			description="preset for SIAP; if you want, change this to start
				of observation as available."
			>dateObs</mixinPar>
		<mixinPar name="t_max"
			alias="tMax"
			description="preset for SIAP; if you want, change this to end of
				observation as available."
			>dateObs</mixinPar>
		<mixinPar name="em_min"
			alias="emMin"
			description="preset for SIAP"
			>bandpassLo</mixinPar>
		<mixinPar name="em_max"
			alias="emMax"
			description="preset for SIAP"
			>bandpassHi</mixinPar>
		<mixinPar name="o_ucd"
			alias="oUCD"
			description="preset for SIAP; fix if you either know more about
			the band or if your images are not in the optical."
			>'em.opt'</mixinPar>
		<mixinPar name="s_resolution"
			alias="sResolution"
			description="preset for SIAP; this is just the pixel scale in
				one dimension.  If that's seriously wrong or you have uncalibrated
				images in your collection, you may need to be more careful here."
			>pixelScale[1]*3600</mixinPar>
		<mixinPar name="instrument_name"
			alias="instrumentName"
			description="The instrument that produced the data"
			>instId</mixinPar>
		<mixinPar name="obs_creator_did"
			alias="creatorDID"
			description="Global identifier of the data set assigned by
				the creator.  Leave NULL unless the creator actually assigned
				an IVO id themselves.">NULL</mixinPar>
		<mixinPar name="s_xel1"
			alias="sXel1"
			description="preset for SIAP"
			>pixelSize[1]</mixinPar>
		<mixinPar name="s_xel2"
			alias="sXel2"
			description="preset for SIAP"
			>pixelSize[2]</mixinPar>
		<mixinPar name="s_pixel_scale"
			alias="sPixelScale"
			description="preset for SIAP"
			>pixelScale[1]*3600</mixinPar>
		<mixinPar name="facility_name"
			alias="facilityName"
			description="Default is to take from resource meta"
			>\sqlquote{\metaSeq{facility}{NULL}}</mixinPar>
	</mixinDef>

	<mixinDef id="publishSSAPHCD">
		<doc>
			Publish a table mixing in //ssap#hcd to ObsTAP.  Since //ssap#hcd
			is deprecated, this should not be used in new RDs, either.  For
			//ssap#mixc tables, use publishSSAPMIXC.

			This works like `the //obscore#publish mixin`_ except some defaults apply
			that copy fields that work analogously in SSAP and in ObsTAP.

			The columns already set in SSAP are marked as UNDOCUMENTED in the
			parameter list below.  For special situations, you can, of course,
			override any of the parameters.  To find out what they actually mean,
			mean, refer to `the //obscore#publish mixin`_.

			Note that this mixin does *not* set coverage (obscore: s_region).
			This is because although we could make a circle from ssa_location
			and ssa_aperture, circles are not allowed in DaCHS' s_region (which
			has a fixed type of spoly).  The recommended solution to still
			have s_region is to add (and index) a custom field in the ssa table
			and compute some sort of spolys for the coverage.

			Note: you must say ``dachs imp //obscore`` before anything
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="s_region" alias="coverage"
			description="Use ssa_region when the
			table also mixes in //ssap#simpleCoverage"
			>NULL</mixinPar>
		<mixinPar name="obs_collection" alias="collectionName"
			>\sqlquote{\getParam{ssa_collection}{NULL}}</mixinPar>
		<mixinPar name="obs_creator_did" alias="creatorDID"
			>ssa_creatorDID</mixinPar>
		<mixinPar name="s_dec" alias="dec">degrees(lat(ssa_location))</mixinPar>
		<mixinPar name="s_ra" alias="ra">degrees(long(ssa_location))</mixinPar>
		<mixinPar name="em_max" alias="emMax">ssa_specend</mixinPar>
		<mixinPar name="em_min" alias="emMin">ssa_specstart</mixinPar>
		<mixinPar name="t_exp_time" alias="expTime">ssa_timeExt</mixinPar>
		<mixinPar name="s_fov" alias="fov">ssa_aperture</mixinPar>
		<mixinPar name="instrument_name" alias="instrumentName"
			>\sqlquote{\getParam{ssa_instrument}{NULL}}</mixinPar>
		<mixinPar name="o_ucd" alias="oUCD"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar>
		<mixinPar name="dataproduct_type" alias="productType">'spectrum'</mixinPar>
		<mixinPar name="s_resolution" alias="sResolution"
			>\getParam{ssa_spaceRes}{NULL}/3600.</mixinPar>
		<mixinPar name="t_max" alias="tMax"
			>ssa_dateObs+ssa_timeExt/43200.</mixinPar>
		<mixinPar name="t_min" alias="tMin"
			>ssa_dateObs-ssa_timeExt/43200.</mixinPar>
		<mixinPar name="target_name" alias="targetName">ssa_targname</mixinPar>
		<mixinPar name="target_class" alias="targetClass">ssa_targclass</mixinPar>
		<mixinPar name="obs_title" alias="title">ssa_dstitle</mixinPar>
		<mixinPar name="em_ucd" alias="emUCD"
			>\sqlquote{\getParam{ssa_spectralucd}}</mixinPar>
		<mixinPar name="obs_publisher_did" alias="did">ssa_pubDID</mixinPar>
		<mixinPar name="em_xel">ssa_length</mixinPar>
	</mixinDef>

	<mixinDef id="publishSSAPMIXC">
		<doc>
			Publish a table mixing in //ssap#view (or the deprecated //ssap#mixc)
			to ObsTAP.

			This works like `the //obscore#publish mixin`_ except some defaults apply
			that copy fields that work analogously in SSAP and in ObsTAP.

			The columns already set in SSAP are marked as UNDOCUMENTED in the
			parameter list below.  For special situations, you can, of course,
			override any of the parameters.  To find out what they actually mean,
			mean, refer to `the //obscore#publish mixin`_.

			Note that this mixin does *not* set coverage (obscore: s_region).
			This is because although we could make a circle from ssa_location
			and ssa_aperture, circles are not allowed in DaCHS' s_region (which
			has a fixed type of spoly).  The recommended solution to still
			have s_region is to add (and index) a custom field; the
			//ssap#simpleCoverage will do this.

			Note: you must say ``dachs imp //obscore`` before anything
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="s_region" alias="coverage"
			description="Use ssa_region when the
			table also mixes in //ssap#simpleCoverage"
			>NULL</mixinPar>
		<mixinPar name="obs_collection" alias="collectionName"
			description="Default should work for you"
			>ssa_collection</mixinPar>
		<mixinPar name="obs_creator_did" alias="creatorDID"
			description="Default should work for you"
			>ssa_creatorDID</mixinPar>
		<mixinPar name="s_dec" alias="dec"
			description="Default should work for you; for large data collections,
				consider having a separate Dec column with a q3c index."
			>degrees(lat(ssa_location))</mixinPar>
		<mixinPar name="s_ra" alias="ra"
			description="Default should work for you; for large data collections,
				consider having a separate RA column with a q3c index."
			>degrees(long(ssa_location))</mixinPar>
		<mixinPar name="em_max" alias="emMax"
			description="Default should work for you"
			>ssa_specend</mixinPar>
		<mixinPar name="em_min" alias="emMin"
			description="Default should work for you"
			>ssa_specstart</mixinPar>
		<mixinPar name="em_xel" alias="emXel"
			description="Default should work for you"
			>ssa_length</mixinPar>
		<mixinPar name="t_exp_time" alias="expTime"
			description="Default should work for you"
			>ssa_timeExt</mixinPar>
		<mixinPar name="s_fov" alias="fov"
			description="Default should work for you"
			>ssa_aperture</mixinPar>
		<mixinPar name="instrument_name" alias="instrumentName"
			description="Default should work for you"
			>ssa_instrument</mixinPar>
		<mixinPar name="facility_name" alias="facilityName"
			description="Default should work for you"
			>\sqlquote{\metaSeq{facility}{NULL}}</mixinPar>
		<mixinPar name="o_ucd" alias="oUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar>
		<mixinPar name="dataproduct_type" alias="productType"
			description="Default should work for you"
			>ssa_dstype</mixinPar>
		<mixinPar name="s_resolution" alias="sResolution"
			description="Default should work for you"
			>\getParam{ssa_spaceRes}{NULL}/3600.</mixinPar>
		<mixinPar name="t_max" alias="tMax"
			description="Default should work for you"
			>ssa_dateObs+ssa_timeExt/43200.</mixinPar>
		<mixinPar name="t_min" alias="tMin"
			description="Default should work for you"
			>ssa_dateObs-ssa_timeExt/43200.</mixinPar>
		<mixinPar name="target_name" alias="targetName"
			description="Default should work for you"
			>ssa_targname</mixinPar>
		<mixinPar name="target_class" alias="targetClass"
			description="Default should work for you"
			>ssa_targclass</mixinPar>
		<mixinPar name="obs_title" alias="title"
			description="Default should work for you"
			>ssa_dstitle</mixinPar>
		<mixinPar name="em_ucd" alias="emUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_spectralucd}}</mixinPar>
		<mixinPar name="o_ucd" alias="oUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar>
		<mixinPar name="em_res_power" alias="emResPower"
			description="Default should work for you"
			>ssa_specstart/ssa_specres</mixinPar>
		<mixinPar name="obs_publisher_did" alias="did"
			description="Default should work for you."
			>ssa_pubDID</mixinPar>
		<mixinPar name="s_xel1"
			description="You shouldn't use SSA for cubes.">1</mixinPar>
		<mixinPar name="s_xel2"
			description="You shouldn't use SSA for cubes.">1</mixinPar>
	</mixinDef>

	<mixinDef id="publishObscoreLike">
		<doc>
			Publish a table that already has large parts of the obscore schema.

			This has parameters called like the corresponding obscore columns
			that also default to taking their data columns named like them.
			Use this when you already have, by and large, and obscore structure
			in your source table.
			
			Note that this will *not* do the right thing with product#table
			instances by default.  For these, you will have to manually
			map access_url, access_format, and access_estsize.
		</doc>

		<LOOP>
			<codeItems>
				for column in context.getById("ObsCore").columns:
					yield {'name': column.name, 'description': column.description}
			</codeItems>
			<events>
				<mixinPar name="\name"
					description="\description">\name</mixinPar>
			</events>
		</LOOP>

		<LFEED source="_manageObscoreClause"/>
	</mixinDef>

	<table id="emptyobscore" onDisk="True" adql="hidden" system="True">
		<meta name="description">An empty table having all columns of the
		obscore table.  Useful internally, and sometimes for tricky queries.
		</meta>
		<mixin
			access_url="access_url"
			calib_level="calib_level"
			obs_collection="obs_collection"
			s_region="s_region"
			obs_creator_did="obs_creator_did"
			obs_publisher_did="obs_publisher_did"
			access_format="access_format"
			dataproduct_type="dataproduct_type"
			obs_id="obs_id"
			preview="NULL"
			access_estsize="access_estsize">publish</mixin>
		<FEED source="obscore-columns"/>
		<adql>hidden</adql>
	</table>

	<data id="makeSources" updating="True">
		<make table="_obscoresources"/>
		<make table="emptyobscore"/>
	</data>

	<data id="create">
		<!-- the view is created from prescriptions in _obscoresources -->
		<make table="ObsCore">
			<LOOP>
				<codeItems>
					if base.getConfig("ivoa", "materialiseObscore"):
						yield {"createObscoreType": "preImport"}
					else:
						yield {"createObscoreType": "postCreation"}
				</codeItems>
				<events passivate="True">
					<FEED source="create-obscore-view" scriptType="\createObscoreType"/>
				</events>
			</LOOP>

			<script name="unpublish obscore DM" type="beforeDrop" lang="SQL">
					delete from tap_schema.supportedmodels
						where dmivorn ilike 'ivo://ivoa.net/std/obscore#%'
			</script>
		</make>
	</data>

	<data id="recover" updating="True" recreateAfter="create" auto="False">
		<!-- removes "bad" entries from obscoresources -->
		<sources items="cleanup"/>
		<nullGrammar/>
		<make table="_obscoresources">
			<script name="Clean bad entries from obscore sources"
					type="newSource" lang="python">
				q = base.UnmanagedQuerier(table.connection)

				# first, remove all tables that don't exist any more
				for tn, in q.connection.query(
						"select tablename from ivoa._obscoresources"):
					if not q.getTableType(tn):
						q.connection.execute("delete from ivoa._obscoresources where"
							" tablename=%(tn)s", locals())

				# then, remove tables with bad view contributions
				for tn, statement in q.connection.query(
						"select tablename, sqlfragment from ivoa._obscoresources"):
					with base.getUntrustedConn() as subconn:
						try:
							_ = list(subconn.query(statement+" LIMIT 1"))
						except base.DBError:
							q.connection.execute("delete from ivoa._obscoresources where"
							" tablename=%(tn)s", locals())
			</script>
		</make>
	</data>

	<data id="refreshAfterSchemaUpdate" auto="False" updating="True">
		<recreateAfter>create</recreateAfter>
		<sources item="dummy"/>
		<nullGrammar/>
		<make table="ObsCore">
			<script name="update all obscore definitions" type="newSource"
					lang="python">
				from gavo import rsc
				from gavo.rscdef import scripting

				def updateObscore(conn, tableDefs):
					obscoreRD = base.caches.getRD("//obscore")
					runner = scripting.PythonScriptRunner(
						obscoreRD.getById("addTableToObscoreSources"))

					for tableDef in tableDefs:
						try:
							base.ui.notifyNewSource(tableDef.getQName())
							depTable = rsc.TableForDef(tableDef, connection=conn)
							runner.run(depTable)
							base.ui.notifySourceFinished()
						except Exception as msg:
							base.ui.notifyWarning(f"Obscore update failed: {msg}")
				
				if table.getTableType("ivoa._obscoresources") is not None:
					srcTables, mth = [], base.caches.getMTH(None)
					for r in table.connection.query(
							"select tablename from ivoa._obscoresources"):
						try:
							srcTables.append(mth.getTableDefForTable(r[0]))
						except base.RDNotFound as exc:
							base.ui.notifyWarning("While updating obscore: table"
								" %s declared in vanished RD %s purged from"
								" ivoa.obscore."%(r[0], exc.args[0]))
					updateObscore(table.connection, srcTables)
			</script>
		</make>
	</data>

	<service id="dl" allowed="dlmeta">
		<meta name="title">\getConfig{web}{sitename} Obscore Datalink</meta>
		<meta name="description">
			A datalink service accompanying obscore.  This will redirect to data
			collection-specific datalink services if they exist or return extremely
			basic datalinks otherwise.
		</meta>
		
		<datalinkCore>
			<descriptorGenerator>
				<setup>
					<code>
						from gavo import svcs
						from gavo.protocols import products

						class ObscoreDescriptor(ProductDescriptor):
							def __init__(self,
									pubDID, accessURL, accessSize,
									mime, preview, sourceTable):
								ProductDescriptor.__init__(self, pubDID, None,
									accessURL, mime,
									sourceTable=sourceTable, preview=preview)
								self.accessSize = accessSize
								self.suppressAutoLinks = True

						currentHost = base.getCurrentServerURL()
					</code>
				</setup>
				<code>
					with base.getTableConn() as conn:
						res = list(conn.queryToDicts(
							"SELECT"
							"   access_url, access_format, access_estsize,"
							" preview, source_table"
							" FROM ivoa.obscore"
							" WHERE obs_publisher_did=%(pubdid)s",
							{"pubdid": pubDID}))

						if not res:
							return DatalinkFault.NotFoundFault(pubDID,
								"No data set with pubDID %s known here"%pubDID)
						ocrow = res[0]

						td = base.getTableDefForTable(conn, ocrow["source_table"])
						dlid = base.getMetaText(td,
							"_associatedDatalinkService.serviceId", default=None)
						if dlid:
							dlsvc = td.rd.getById(dlid)
							dlurl = dlsvc.getURL("dlmeta")
							raise svcs.Found(dlurl+"?ID="+urllib.parse.quote(pubDID))

						return ObscoreDescriptor(
							pubDID,
							products.formatProductLink(ocrow["access_url"], currentHost),
							ocrow["access_estsize"]*1024,
							ocrow["access_format"],
							products.formatProductLink(ocrow["preview"], currentHost),
							ocrow["source_table"])
				</code>
			</descriptorGenerator>

			<metaMaker>
				<code>
					yield descriptor.makeLink(
						descriptor.accessPath,
						description="The main data product",
						semantics="#this",
						contentType=descriptor.mime,
						contentLength=descriptor.accessSize)

					if descriptor.preview:
						yield descriptor.makeLink(
							descriptor.preview,
							description="A preview or thumbnail of the data",
							semantics="#preview")
				</code>
			</metaMaker>
		</datalinkCore>
	</service>

	<FEED source="%#obscore-overrides"/>
</resource>
