Improving documentation
This commit is contained in:
parent
8f88be604c
commit
73e7f67d7d
|
@ -33,125 +33,9 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Item is mainly described by the following attributes (* indicate mandatory attributes):
|
* Item is a set of metadata to describe a concept.
|
||||||
|
* An Item can has one or more <a href="./resource_Resource.html">Resource</a> attached.
|
||||||
*
|
*
|
||||||
* <dl>
|
|
||||||
* <dt>name* (string)</dt>
|
|
||||||
* <dd>
|
|
||||||
* the name of the new item, must be between 2 and 100 characters long
|
|
||||||
* and contain only lowercase alphanumeric characters, '-' and '_';
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">title (string)</dt>
|
|
||||||
* <dd>
|
|
||||||
* If not specified it assumes the same value of <em>name</em> attribute.
|
|
||||||
* The title of the item;
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">maintainer (string)</dt>
|
|
||||||
* <dd>the name of the item’s maintainer;</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">maintainer_email (string)</dt>
|
|
||||||
* <dd>the email address of the item’s maintainer;</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">license_id* (string)</dt>
|
|
||||||
* <dd>the id of the item’s license, use <a href="resource_License.html">License listing</a> API to get available values;</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">notes (string)</dt>
|
|
||||||
* <dd>a description of the item;</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">url (string)</dt>
|
|
||||||
* <dd>a URL for the item’s source;</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">version (string)</dt>
|
|
||||||
* <dd>must be between no longer than 100 characters</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">state (string, default='active')</dt>
|
|
||||||
* <dd>
|
|
||||||
* the current state of the item, e.g. 'active' or 'deleted', only active items show up in search results and other lists of items,
|
|
||||||
* this parameter will be ignored if you are not authorized to change the state of the item;
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">groups (list of dictionaries)</dt>
|
|
||||||
* <dd>
|
|
||||||
* the groups to which the item belongs, each group dictionary should have one or more of the
|
|
||||||
* following keys which identify an existing group:
|
|
||||||
* 'id' (the id of the group, string), or 'name' (the name of the group, string).
|
|
||||||
* To see which groups exist use <a href="resource_Group.html#resource_Group_list_limit_offset_countOnly_GET">Group listing</a> API.</dd>
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">tags (list of tag dictionaries)</dt>
|
|
||||||
* <dd>
|
|
||||||
* the item’s tags. The tag is a dictionary in the format:
|
|
||||||
* name : the name for the tag, i.e. a string between 2 and 100 characters long containing only alphanumeric characters and '-', '_' and '.'.
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">resources (list of resource dictionaries)</dt>
|
|
||||||
* <dd>the item’s resources, see <a href="resource_Resource.html">Resource collection</a> for the format of resource dictionaries;</dd>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">extras (list of item extra dictionaries)</dt>
|
|
||||||
* <dd>the item’s extras, extras are arbitrary (key: value) metadata items that can be added to items, each extra dictionary should have keys 'key' (a string), 'value' (a string);</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">owner_org (string)</dt>
|
|
||||||
* <dd>
|
|
||||||
* the id of the item’s owning organization, see <code>supportedOrganizations</code> property in
|
|
||||||
* <a href="resource_Configuration.html#resource_Configuration_read_context_GET">Read Configuration</a>.
|
|
||||||
* The <code>defaultOrganization</code> is used if the author does not specify the organization.
|
|
||||||
* </dd>
|
|
||||||
*
|
|
||||||
* </dl>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <h3>Parameter automatically managed:</h3>
|
|
||||||
* <dl>
|
|
||||||
*
|
|
||||||
* <dt>author (string)</dt>
|
|
||||||
* <dd>the name of the item’s author (the owner of the gcube-token);</dd>
|
|
||||||
*
|
|
||||||
* <dt style="margin-top: 5px;">author_email (string)</dt>
|
|
||||||
* <dd>the email address of the item’s author (the email of the owner of gcube-token);</dd>
|
|
||||||
*
|
|
||||||
* </dl>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <h3>Geo-Indexing your datasets:</h3>
|
|
||||||
* <p>
|
|
||||||
* In order to make an Item searchable by location, it must have a special extra, with its key named ‘spatial’.
|
|
||||||
* The value must be a valid GeoJSON geometry, for example:
|
|
||||||
* </p>
|
|
||||||
* <pre>
|
|
||||||
* {
|
|
||||||
* "type":"Polygon",
|
|
||||||
* "coordinates":[[[2.05827, 49.8625],[2.05827, 55.7447], [-6.41736, 55.7447], [-6.41736, 49.8625], [2.05827, 49.8625]]]
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* or
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* {
|
|
||||||
* "type": "Point",
|
|
||||||
* "coordinates": [-3.145,53.078]
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <h3>Profile</h3>
|
|
||||||
* <p>
|
|
||||||
* If at least one profile has been defined within this context, then you need to specify the profile's
|
|
||||||
* type when creating the item.
|
|
||||||
* You need to insert, among the extras of the JSON object describing the item, a <code>system:type</code>
|
|
||||||
* property with one of the available profile, see <a href="resource_Profile.html#resource_Profile_listOrCount_countOnly_GET">List Profiles</a> API.
|
|
||||||
* The validation of the submitted request will be performed against the profile whose type has been specified.
|
|
||||||
* The profile's properties need to be specified within the extras field as well.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* If no profile has been defined, then no validation will be performed.
|
|
||||||
* Thus you do not need to set any <code>system:type</code> property.
|
|
||||||
* </p>
|
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(Item.ITEMS)
|
@Path(Item.ITEMS)
|
||||||
|
@ -367,6 +251,125 @@ public class Item extends REST<CKANPackage> implements org.gcube.gcat.api.interf
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* The create API allows to create an item.
|
* The create API allows to create an item.
|
||||||
|
* An Item is mainly described by the following attributes (* indicate mandatory attributes):
|
||||||
|
*
|
||||||
|
* <dl>
|
||||||
|
* <dt>name* (string)</dt>
|
||||||
|
* <dd>
|
||||||
|
* the name of the new item, must be between 2 and 100 characters long
|
||||||
|
* and contain only lowercase alphanumeric characters, '-' and '_';
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">title (string)</dt>
|
||||||
|
* <dd>
|
||||||
|
* If not specified it assumes the same value of <em>name</em> attribute.
|
||||||
|
* The title of the item;
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">maintainer (string)</dt>
|
||||||
|
* <dd>the name of the item’s maintainer;</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">maintainer_email (string)</dt>
|
||||||
|
* <dd>the email address of the item’s maintainer;</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">license_id* (string)</dt>
|
||||||
|
* <dd>the id of the item’s license, use <a href="resource_License.html">License listing</a> API to get available values;</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">notes (string)</dt>
|
||||||
|
* <dd>a description of the item;</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">url (string)</dt>
|
||||||
|
* <dd>a URL for the item’s source;</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">version (string)</dt>
|
||||||
|
* <dd>must be between no longer than 100 characters</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">state (string, default='active')</dt>
|
||||||
|
* <dd>
|
||||||
|
* the current state of the item, e.g. 'active' or 'deleted', only active items show up in search results and other lists of items,
|
||||||
|
* this parameter will be ignored if you are not authorized to change the state of the item;
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">groups (list of dictionaries)</dt>
|
||||||
|
* <dd>
|
||||||
|
* the groups to which the item belongs, each group dictionary should have one or more of the
|
||||||
|
* following keys which identify an existing group:
|
||||||
|
* 'id' (the id of the group, string), or 'name' (the name of the group, string).
|
||||||
|
* To see which groups exist use <a href="resource_Group.html#resource_Group_list_limit_offset_countOnly_GET">Group listing</a> API.</dd>
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">tags (list of tag dictionaries)</dt>
|
||||||
|
* <dd>
|
||||||
|
* the item’s tags. The tag is a dictionary in the format:
|
||||||
|
* name : the name for the tag, i.e. a string between 2 and 100 characters long containing only alphanumeric characters and '-', '_' and '.'.
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">resources (list of resource dictionaries)</dt>
|
||||||
|
* <dd>the item’s resources, see <a href="resource_Resource.html">Resource collection</a> for the format of resource dictionaries;</dd>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">extras (list of item extra dictionaries)</dt>
|
||||||
|
* <dd>the item’s extras, extras are arbitrary (key: value) metadata items that can be added to items, each extra dictionary should have keys 'key' (a string), 'value' (a string);</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">owner_org (string)</dt>
|
||||||
|
* <dd>
|
||||||
|
* the id of the item’s owning organization, see <code>supportedOrganizations</code> property in
|
||||||
|
* <a href="resource_Configuration.html#resource_Configuration_read_context_GET">Read Configuration</a>.
|
||||||
|
* The <code>defaultOrganization</code> is used if the author does not specify the organization.
|
||||||
|
* </dd>
|
||||||
|
*
|
||||||
|
* </dl>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h3>Parameter automatically managed:</h3>
|
||||||
|
* <dl>
|
||||||
|
*
|
||||||
|
* <dt>author (string)</dt>
|
||||||
|
* <dd>the name of the item’s author (the owner of the gcube-token);</dd>
|
||||||
|
*
|
||||||
|
* <dt style="margin-top: 5px;">author_email (string)</dt>
|
||||||
|
* <dd>the email address of the item’s author (the email of the owner of gcube-token);</dd>
|
||||||
|
*
|
||||||
|
* </dl>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h3>Geo-Indexing your datasets:</h3>
|
||||||
|
* <p>
|
||||||
|
* In order to make an Item searchable by location, it must have a special extra, with its key named ‘spatial’.
|
||||||
|
* The value must be a valid GeoJSON geometry, for example:
|
||||||
|
* </p>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "type":"Polygon",
|
||||||
|
* "coordinates":[[[2.05827, 49.8625],[2.05827, 55.7447], [-6.41736, 55.7447], [-6.41736, 49.8625], [2.05827, 49.8625]]]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* or
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "type": "Point",
|
||||||
|
* "coordinates": [-3.145,53.078]
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h3>Profile</h3>
|
||||||
|
* <p>
|
||||||
|
* If at least one profile has been defined within this context, then you need to specify the profile's
|
||||||
|
* type when creating the item.
|
||||||
|
* You need to insert, among the extras of the JSON object describing the item, a <code>system:type</code>
|
||||||
|
* property with one of the available profile, see <a href="resource_Profile.html#resource_Profile_listOrCount_countOnly_GET">List Profiles</a> API.
|
||||||
|
* The validation of the submitted request will be performed against the profile whose type has been specified.
|
||||||
|
* The profile's properties need to be specified within the extras field as well.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If no profile has been defined, then no validation will be performed.
|
||||||
|
* Thus you do not need to set any <code>system:type</code> property.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <h3>Social Post</h3>
|
* <h3>Social Post</h3>
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -3,14 +3,18 @@ package org.gcube.gcat.rest;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import org.gcube.gcat.api.GCatConstants;
|
|
||||||
import org.gcube.gcat.persistence.ckan.CKANLicense;
|
import org.gcube.gcat.persistence.ckan.CKANLicense;
|
||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A collection to interact with licenses that are available in the catalogue.
|
||||||
|
* A license is associated with items to define the legal right
|
||||||
|
* to use such an item.
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(License.LICENSES)
|
@Path(License.LICENSES)
|
||||||
|
@ -22,8 +26,14 @@ public class License extends REST<CKANLicense> implements org.gcube.gcat.api.int
|
||||||
super(LICENSES, null, CKANLicense.class);
|
super(LICENSES, null, CKANLicense.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the json array containing the licenses available in the catalogue
|
||||||
|
*
|
||||||
|
* @pathExample /licenses
|
||||||
|
* @responseExample application/json classpath:/api-docs-examples/license/list-license-response.json
|
||||||
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public String list() {
|
public String list() {
|
||||||
return super.list(-1, -1);
|
return super.list(-1, -1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,20 @@ import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
import javax.ws.rs.InternalServerErrorException;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
|
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
|
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.NamespaceCategory;
|
import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.NamespaceCategory;
|
||||||
import org.gcube.gcat.api.GCatConstants;
|
|
||||||
import org.gcube.gcat.profile.MetadataUtility;
|
import org.gcube.gcat.profile.MetadataUtility;
|
||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A namespace defines a logical grouping for metadata contained in items.
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(Namespace.NAMESPACES)
|
@Path(Namespace.NAMESPACES)
|
||||||
|
@ -25,8 +27,14 @@ import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
@ResourceLabel("Namespace APIs")
|
@ResourceLabel("Namespace APIs")
|
||||||
public class Namespace extends BaseREST implements org.gcube.gcat.api.interfaces.Namespace {
|
public class Namespace extends BaseREST implements org.gcube.gcat.api.interfaces.Namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the json array containing the licenses available in the catalogue
|
||||||
|
*
|
||||||
|
* @pathExample /namespaces
|
||||||
|
* @responseExample application/json classpath:/api-docs-examples/namespace/list-namespace-response.json
|
||||||
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public String list() {
|
public String list() {
|
||||||
setCalledMethod("GET /" + NAMESPACES);
|
setCalledMethod("GET /" + NAMESPACES);
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,7 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Profile must comply with the defined <a href="../profiles/SCHEMA">XSD schema</a> .
|
* A profile defines a type and the schema any item of such a type must comply with.
|
||||||
*
|
|
||||||
* Please find the documentation of profile schema at:
|
|
||||||
* at <a href="https://wiki.gcube-system.org/gcube/GCat_Background#Metadata_Profile_v.4">Metadata Profile</>
|
|
||||||
*
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
|
@ -87,10 +84,7 @@ public class Profile extends BaseREST implements org.gcube.gcat.api.interfaces.P
|
||||||
* @return a JSON Array.
|
* @return a JSON Array.
|
||||||
*
|
*
|
||||||
* @pathExample /profiles
|
* @pathExample /profiles
|
||||||
* @responseExample application/json;charset=UTF-8 ["EmptyProfile","TestProfile",...,"ComplexProfile"]
|
* @responseExample application/json ["EmptyProfile","TestProfile","ComplexProfile"]
|
||||||
*
|
|
||||||
* @pathExample /profiles?count=true
|
|
||||||
* @responseExample application/json;charset=UTF-8 {"count":5}
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
|
@ -178,6 +172,16 @@ public class Profile extends BaseREST implements org.gcube.gcat.api.interfaces.P
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A profile must comply with the defined <a href="../profiles/SCHEMA">XSD schema</a> .
|
||||||
|
*
|
||||||
|
* Please find the documentation of profile schema at:
|
||||||
|
* at <a href="https://wiki.gcube-system.org/gcube/GCat_Background#Metadata_Profile_v.4">Metadata Profile</>
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param xml
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@PUT
|
@PUT
|
||||||
@Path("/{" + PROFILE_NAME_PARAMETER + "}")
|
@Path("/{" + PROFILE_NAME_PARAMETER + "}")
|
||||||
@Consumes(MediaType.APPLICATION_XML)
|
@Consumes(MediaType.APPLICATION_XML)
|
||||||
|
|
|
@ -22,6 +22,34 @@ import org.gcube.gcat.persistence.ckan.CKANResource;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Item's Resource is an URL (e.g. URL pointing to a file)
|
||||||
|
* and can exists only attached to an <a href="./resource_Item.html">item</a>.
|
||||||
|
*
|
||||||
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
|
*/
|
||||||
|
@Path(Resource.COLLECTION)
|
||||||
|
@ResourceGroup("Item APIs")
|
||||||
|
@ResourceLabel("Item's Resource APIs")
|
||||||
|
public class Resource extends BaseREST implements org.gcube.gcat.api.interfaces.Resource<Response,Response> {
|
||||||
|
|
||||||
|
protected static final String ITEM_ID_PARAMETER = Item.ITEM_ID_PARAMETER;
|
||||||
|
protected static final String RESOURCE_ID_PARAMETER = "RESOURCE_ID";
|
||||||
|
|
||||||
|
protected static final String COLLECTION = Item.ITEMS + "/{" + ITEM_ID_PARAMETER + "}/" + RESOURCES;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
||||||
|
/* Catalogue-Member is not added to VRE members and is assumed as the default role in the catalogue for the VRE members. So we can't enforce
|
||||||
|
* @AuthorizationControl(allowedRoles={Role.CATALOGUE_MEMBER, Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
||||||
|
*/
|
||||||
|
public String list(@PathParam(ITEM_ID_PARAMETER) String itemID) {
|
||||||
|
setCalledMethod("GET /" + COLLECTION);
|
||||||
|
CKANResource ckanResource = new CKANResource(itemID);
|
||||||
|
ckanResource.setName(itemID);
|
||||||
|
return ckanResource.list();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Resource is mainly described by the following attributes (* indicate mandatory attributes):
|
* An Resource is mainly described by the following attributes (* indicate mandatory attributes):
|
||||||
*
|
*
|
||||||
|
@ -54,31 +82,7 @@ import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
* <dd>resource last update time;</dd>
|
* <dd>resource last update time;</dd>
|
||||||
*
|
*
|
||||||
* </dl>
|
* </dl>
|
||||||
*
|
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
|
||||||
*/
|
*/
|
||||||
@Path(Resource.COLLECTION)
|
|
||||||
@ResourceGroup("Item APIs")
|
|
||||||
@ResourceLabel("Item's Resource APIs")
|
|
||||||
public class Resource extends BaseREST implements org.gcube.gcat.api.interfaces.Resource<Response,Response> {
|
|
||||||
|
|
||||||
protected static final String ITEM_ID_PARAMETER = Item.ITEM_ID_PARAMETER;
|
|
||||||
protected static final String RESOURCE_ID_PARAMETER = "RESOURCE_ID";
|
|
||||||
|
|
||||||
protected static final String COLLECTION = Item.ITEMS + "/{" + ITEM_ID_PARAMETER + "}/" + RESOURCES;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
|
||||||
/* Catalogue-Member is not added to VRE members and is assumed as the default role in the catalogue for the VRE members. So we can't enforce
|
|
||||||
* @AuthorizationControl(allowedRoles={Role.CATALOGUE_MEMBER, Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
|
||||||
*/
|
|
||||||
public String list(@PathParam(ITEM_ID_PARAMETER) String itemID) {
|
|
||||||
setCalledMethod("GET /" + COLLECTION);
|
|
||||||
CKANResource ckanResource = new CKANResource(itemID);
|
|
||||||
ckanResource.setName(itemID);
|
|
||||||
return ckanResource.list();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
@Consumes(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
||||||
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.xml.ws.WebServiceException;
|
import javax.xml.ws.WebServiceException;
|
||||||
|
@ -19,8 +20,12 @@ import org.gcube.gcat.persistence.ckan.CKANPackageTrash;
|
||||||
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This collection allow to interact with thrashed items
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(Trash.TRASH)
|
@Path(Trash.TRASH)
|
||||||
|
@ -29,8 +34,28 @@ import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
public class Trash extends BaseREST implements org.gcube.gcat.api.interfaces.Trash<Response> {
|
public class Trash extends BaseREST implements org.gcube.gcat.api.interfaces.Trash<Response> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the thrashed items.<br/>
|
||||||
|
* By default, it lists only the trashed items of the requesting user.<br/>
|
||||||
|
*
|
||||||
|
* The listed items belong to the supported organizations for the
|
||||||
|
* context of the request (i.e. the context where the token has been generated).<br/>
|
||||||
|
*
|
||||||
|
* See <a href="./resource_Configuration.html">supportedOrganizations parameter in
|
||||||
|
* Configuration</a>.
|
||||||
|
*
|
||||||
|
* If the user specifies <code>own_only=false</code> and the user is
|
||||||
|
* <em>Catalogue-Admin</em> or above it return the thrashed items of all the
|
||||||
|
* users for all the supported organizations.
|
||||||
|
*
|
||||||
|
* @param ownOnly indicates that the user is interested only in its own items or the items of all the users.
|
||||||
|
* @return the json array containing the items in the trash
|
||||||
|
*
|
||||||
|
* @pathExample /trash
|
||||||
|
* @responseExample application/json ["item1","item54"]
|
||||||
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Override
|
@Override
|
||||||
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
||||||
public String list(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
public String list(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
||||||
|
@ -39,7 +64,34 @@ public class Trash extends BaseREST implements org.gcube.gcat.api.interfaces.Tra
|
||||||
return ckanPackageTrash.list();
|
return ckanPackageTrash.list();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete in background all items in the trash.<br/>
|
||||||
|
* The operation returns immediately to the client and continues in background.<br/>
|
||||||
|
* There is no way to monitor or stop the running operation.<br/>
|
||||||
|
* The thrashed items cannot be recovered.</br/>
|
||||||
|
*
|
||||||
|
* By default, it empty only the trashed items of the requesting user.<br/>
|
||||||
|
*
|
||||||
|
* The listed items belong to the supported organizations for the
|
||||||
|
* context of the request (i.e. the context where the token has been generated).<br/>
|
||||||
|
*
|
||||||
|
* See <a href="./resource_Configuration.html">supportedOrganizations parameter in
|
||||||
|
* Configuration</a>.
|
||||||
|
*
|
||||||
|
* If the user specifies <code>own_only=false</code> and the user is
|
||||||
|
* <em>Catalogue-Admin</em> or above it empty the thrash of all the
|
||||||
|
* users for all the supported organizations.
|
||||||
|
*
|
||||||
|
* @param ownOnly indicates that the user is interested only in its own items or the items of all the users.
|
||||||
|
* @return 202 Accepted if the request has been accepted successfully
|
||||||
|
* @throws WebServiceException
|
||||||
|
*
|
||||||
|
* @pathExample /trash
|
||||||
|
*/
|
||||||
@DELETE
|
@DELETE
|
||||||
|
@StatusCodes ({
|
||||||
|
@ResponseCode ( code = 202, condition = "The empty trash operation has been accepted successfully.")
|
||||||
|
})
|
||||||
@Override
|
@Override
|
||||||
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
||||||
public Response empty(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
public Response empty(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
||||||
|
@ -55,7 +107,14 @@ public class Trash extends BaseREST implements org.gcube.gcat.api.interfaces.Tra
|
||||||
return Response.status(Status.ACCEPTED).build();
|
return Response.status(Status.ACCEPTED).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is the same of
|
||||||
|
* <a href="resource_Trash.html#resource_Trash_empty_ownOnly_DELETE">DELETE</a> operation
|
||||||
|
*/
|
||||||
@PURGE
|
@PURGE
|
||||||
|
@StatusCodes ({
|
||||||
|
@ResponseCode ( code = 202, condition = "The empty trash operation has been accepted successfully.")
|
||||||
|
})
|
||||||
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
|
||||||
public Response emptyViaPurge(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
public Response emptyViaPurge(@QueryParam(GCatConstants.OWN_ONLY_QUERY_PARAMETER) @DefaultValue("true") Boolean ownOnly) throws WebServiceException {
|
||||||
return empty(ownOnly);
|
return empty(ownOnly);
|
||||||
|
|
|
@ -45,8 +45,75 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.swagger.OperationId;
|
import com.webcohesion.enunciate.metadata.swagger.OperationId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The catalogue configuration for the context of the request
|
||||||
|
* (i.e. the context where the token has been generated).
|
||||||
*
|
*
|
||||||
* A Configuration is described by the following attributes:
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
|
*/
|
||||||
|
@Path(Configuration.CONFIGURATIONS)
|
||||||
|
@ResourceGroup("Administration APIs")
|
||||||
|
@ResourceLabel("Configuration APIs")
|
||||||
|
public class Configuration extends BaseREST implements org.gcube.gcat.api.interfaces.Configuration<Response,Response> {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(Configuration.class);
|
||||||
|
|
||||||
|
public static final String CONTEXT_FULLNAME_PARAMETER = "CONTEXT_FULLNAME_PARAMETER";
|
||||||
|
|
||||||
|
protected String checkContext(String context) throws WebServiceException {
|
||||||
|
if(context==null || context.compareTo("")==0) {
|
||||||
|
throw new BadRequestException("Please provide a valid context as path parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
String c = SecretManagerProvider.instance.get().getContext();
|
||||||
|
if(context.compareTo(Configuration.CURRENT_CONTEXT_PATH_PARAMETER)==0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(context.compareTo(c)!=0) {
|
||||||
|
throw new BadRequestException("Context provided as path parameter (i.e. " + context + ") does not match with token request context (i.e. " + c + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String checkContext(String context, ServiceCatalogueConfiguration catalogueConfiguration) {
|
||||||
|
String c = checkContext(context);
|
||||||
|
if(c.compareTo(catalogueConfiguration.getContext())!=0) {
|
||||||
|
throw new BadRequestException("Context provided in the configuration (i.e. " + catalogueConfiguration.getContext() + ") does not match with token request context (i.e. " + c + ")");
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkRole(Role required) {
|
||||||
|
CKANUser ckanUser = CKANUserCache.getCurrrentCKANUser();
|
||||||
|
if(ckanUser.getRole().ordinal() < required.ordinal()) {
|
||||||
|
throw new ForbiddenException("To perform such a request you must have " + required.getPortalRole() + " role");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createOrUpdate(ServiceCatalogueConfiguration catalogueConfiguration) throws WebServiceException {
|
||||||
|
try {
|
||||||
|
ServiceCatalogueConfiguration gotCatalogueConfiguration = CatalogueConfigurationFactory.createOrUpdate(catalogueConfiguration);
|
||||||
|
String configuration = gotCatalogueConfiguration.toJsonString();
|
||||||
|
logger.debug("The new configuration in context {} is {}", catalogueConfiguration.getContext(), configuration);
|
||||||
|
return configuration;
|
||||||
|
}catch (WebApplicationException e) {
|
||||||
|
throw e;
|
||||||
|
}catch (Exception e) {
|
||||||
|
throw new InternalServerErrorException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This API allows to create the catalogue configuration for the
|
||||||
|
* context of the request (i.e. the context where the token has been generated)
|
||||||
|
* using the json provided as request body.<br/>
|
||||||
|
*
|
||||||
|
* Only a <a href="../docs/index.html#roles">Catalogue-Manager</a> can invoke this API.<br/>
|
||||||
|
*
|
||||||
|
* The configuration will be persisted in the infrastructure
|
||||||
|
* Information System (IS) in the context of the request.<br/>
|
||||||
|
* * A Configuration is described by the following attributes:
|
||||||
*
|
*
|
||||||
* <dl>
|
* <dl>
|
||||||
*
|
*
|
||||||
|
@ -134,73 +201,6 @@ import com.webcohesion.enunciate.metadata.swagger.OperationId;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
|
||||||
*/
|
|
||||||
@Path(Configuration.CONFIGURATIONS)
|
|
||||||
@ResourceGroup("Administration APIs")
|
|
||||||
@ResourceLabel("Configuration APIs")
|
|
||||||
public class Configuration extends BaseREST implements org.gcube.gcat.api.interfaces.Configuration<Response,Response> {
|
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(Configuration.class);
|
|
||||||
|
|
||||||
public static final String CONTEXT_FULLNAME_PARAMETER = "CONTEXT_FULLNAME_PARAMETER";
|
|
||||||
|
|
||||||
protected String checkContext(String context) throws WebServiceException {
|
|
||||||
if(context==null || context.compareTo("")==0) {
|
|
||||||
throw new BadRequestException("Please provide a valid context as path parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
String c = SecretManagerProvider.instance.get().getContext();
|
|
||||||
if(context.compareTo(Configuration.CURRENT_CONTEXT_PATH_PARAMETER)==0) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(context.compareTo(c)!=0) {
|
|
||||||
throw new BadRequestException("Context provided as path parameter (i.e. " + context + ") does not match with token request context (i.e. " + c + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String checkContext(String context, ServiceCatalogueConfiguration catalogueConfiguration) {
|
|
||||||
String c = checkContext(context);
|
|
||||||
if(c.compareTo(catalogueConfiguration.getContext())!=0) {
|
|
||||||
throw new BadRequestException("Context provided in the configuration (i.e. " + catalogueConfiguration.getContext() + ") does not match with token request context (i.e. " + c + ")");
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void checkRole(Role required) {
|
|
||||||
CKANUser ckanUser = CKANUserCache.getCurrrentCKANUser();
|
|
||||||
if(ckanUser.getRole().ordinal() < required.ordinal()) {
|
|
||||||
throw new ForbiddenException("To perform such a request you must have " + required.getPortalRole() + " role");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String createOrUpdate(ServiceCatalogueConfiguration catalogueConfiguration) throws WebServiceException {
|
|
||||||
try {
|
|
||||||
ServiceCatalogueConfiguration gotCatalogueConfiguration = CatalogueConfigurationFactory.createOrUpdate(catalogueConfiguration);
|
|
||||||
String configuration = gotCatalogueConfiguration.toJsonString();
|
|
||||||
logger.debug("The new configuration in context {} is {}", catalogueConfiguration.getContext(), configuration);
|
|
||||||
return configuration;
|
|
||||||
}catch (WebApplicationException e) {
|
|
||||||
throw e;
|
|
||||||
}catch (Exception e) {
|
|
||||||
throw new InternalServerErrorException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This API allows to create the catalogue configuration for the
|
|
||||||
* context of the request (i.e. the context where the token has been generated)
|
|
||||||
* using the json provided as request body.<br/>
|
|
||||||
*
|
|
||||||
* Only a <a href="../docs/index.html#roles">Catalogue-Manager</a> can invoke this API.<br/>
|
|
||||||
*
|
|
||||||
* The configuration will be persisted in the infrastructure
|
|
||||||
* Information System (IS) in the context of the request.<br/>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param json the configuration representation
|
* @param json the configuration representation
|
||||||
* @return the created configuration
|
* @return the created configuration
|
||||||
* @throws WebServiceException when the request fails
|
* @throws WebServiceException when the request fails
|
||||||
|
|
|
@ -26,6 +26,8 @@ import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This concept is mutated by Ckan which is used as underling technology to persist items.
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(Group.GROUPS)
|
@Path(Group.GROUPS)
|
||||||
|
|
|
@ -26,6 +26,8 @@ import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This concept is mutated by Ckan which is used as underling technology to persist items.
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(Organization.ORGANIZATIONS)
|
@Path(Organization.ORGANIZATIONS)
|
||||||
|
|
|
@ -28,6 +28,8 @@ import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This collection allows to interact with the catalogue users.
|
||||||
|
*
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
@Path(User.USERS)
|
@Path(User.USERS)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "ClassificationInformation",
|
||||||
|
"title": "Classification Information",
|
||||||
|
"name": "ClassificationInformation",
|
||||||
|
"description": "Classification Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LocationInformation",
|
||||||
|
"title": "Location Information",
|
||||||
|
"name": "LocationInformation",
|
||||||
|
"description": "Location Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ContactInformation",
|
||||||
|
"title": "Contact Information",
|
||||||
|
"name": "ContactInformation",
|
||||||
|
"description": "Contact Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "OtherInformation",
|
||||||
|
"title": "Other Information",
|
||||||
|
"name": "OtherInformation",
|
||||||
|
"description": "Other Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AvailabilityInformation",
|
||||||
|
"title": "Availability Information",
|
||||||
|
"name": "AvailabilityInformation",
|
||||||
|
"description": "Availability Information"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue