/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.constellation.rest.api;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.sis.metadata.iso.DefaultMetadata;
import org.apache.sis.util.logging.Logging;
import org.constellation.admin.exception.ConstellationException;
import org.constellation.business.IDataBusiness;
import org.constellation.business.IDatasetBusiness;
import org.constellation.business.IMetadataBusiness;
import org.constellation.configuration.ConfigurationException;
import org.constellation.configuration.DataBrief;
import org.constellation.configuration.DataSetBrief;
import org.constellation.dto.ParameterValues;
import org.constellation.database.api.domain.Page;
import org.constellation.database.api.domain.PageRequest;
import org.constellation.database.api.jooq.tables.pojos.CstlUser;
import org.constellation.database.api.jooq.tables.pojos.Dataset;
import org.constellation.database.api.pojo.DataItem;
import org.constellation.database.api.pojo.DatasetItem;
import org.constellation.database.api.repository.UserRepository;
import org.constellation.json.Sort;
import org.constellation.model.DatasetSearch;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.constellation.utils.RESTfulUtilities.ok;
/**
* RESTful API for dataset metadata.
*
* @author Mehdi Sidhoum (Geomatys).
* @version 0.9
* @since 0.9
*/
@Component
@Path("/1/domain/{domainId}/dataset/")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class DataSetRest {
/**
* Used for debugging purposes.
*/
private static final Logger LOGGER = Logging.getLogger("org.constellation.rest.api");
/**
* Injected dataset business.
*/
@Inject
private IDatasetBusiness datasetBusiness;
/**
* Injected metadata business.
*/
@Inject
private IMetadataBusiness metadataBusiness;
/**
* Injected data business.
*/
@Inject
private IDataBusiness dataBusiness;
/**
* Injected user repository.
*/
@Inject
private UserRepository userRepository;
/**
* Injected security manager
*/
@Inject
private org.constellation.security.SecurityManager securityManager;
// /**
// * Proceed to remove a dataset
// * @param domainId
// * @param datasetIdentifier
// * @return
// */
// @DELETE
// @Path("{datasetIdentifier}")
// public Response removeDataSet(@PathParam("domainId") final int domainId,
// @PathParam("datasetIdentifier") final String datasetIdentifier) {
// try{
// datasetBusiness.removeDataset(datasetIdentifier);
// return Response.ok().type(MediaType.TEXT_PLAIN_TYPE).build();
// }catch(Exception ex){
// LOGGER.log(Level.WARNING, "Failed to remove dataset with identifier "+datasetIdentifier,ex);
// return Response.status(500).entity("failed").build();
// }
// }
@POST
@Path("create")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createDataset(@PathParam("domainId") final int domainId,
final ParameterValues values) {
final String metaPath = values.getValues().get("metadataFilePath");
final String datasetIdentifier = values.getValues().get("datasetIdentifier");
if (datasetIdentifier != null && !datasetIdentifier.isEmpty()) {
try {
Dataset dataset = datasetBusiness.getDataset(datasetIdentifier);
if (dataset != null) {
LOGGER.log(Level.WARNING, "Dataset with identifier " + datasetIdentifier + " already exist");
return Response.status(Response.Status.CONFLICT).entity("failed").build();
}
String metadataXML = null;
if (metaPath != null) {
final File f = new File(metaPath);
DefaultMetadata metadata = null;
if (metadataBusiness.isSpecialMetadataFormat(f)){
metadata = metadataBusiness.getMetadataFromSpecialFormat(f);
} else {
metadata = (DefaultMetadata) metadataBusiness.unmarshallMetadata(f);
}
metadataXML = metadataBusiness.marshallMetadata(metadata);
}
Optional<CstlUser> user = userRepository.findOne(securityManager.getCurrentUserLogin());
Dataset dataSet = datasetBusiness.createDataset(datasetIdentifier, metadataXML, user.get().getId());
return Response.ok().status(Response.Status.CREATED)
.type(MediaType.APPLICATION_JSON)
.entity(dataSet)
.build();
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Failed to create dataset with identifier " + datasetIdentifier, ex);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("failed").build();
}
} else {
LOGGER.log(Level.WARNING, "Can't create dataset with empty identifier");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("failed").build();
}
}
/**
* Returns all dataset in brief format with its own list of data in brief format.
*
* @return Response for client side as json.
* @TODO is it the right place? perhaps it should be moved to DatasetRest facade.
*/
@GET
@Path("all")
public Response getAllDataset(@PathParam("domainId") final int domainId) {
final List<DataSetBrief> datasetBriefs = new ArrayList<>();
final List<Dataset> datasets = datasetBusiness.getAllDataset();
if(datasets!=null){
for(final Dataset ds : datasets){
final DataSetBrief dsb = buildDatsetBrief(ds);
datasetBriefs.add(dsb);
}
}
return Response.ok(datasetBriefs).build();
}
/**
* Return as an attachment file the metadata of data set in xml format.
* @param datasetIdentifier given dataset identifier.
* @return the xml file
*/
@GET
@Path("{datasetIdentifier}")
@Produces(MediaType.APPLICATION_XML)
public Response downloadMetadataForDataSet(@PathParam("domainId") final int domainId,
@PathParam("datasetIdentifier") final String datasetIdentifier) {
try{
final DefaultMetadata metadata = datasetBusiness.getMetadata(datasetIdentifier);
if (metadata != null) {
metadata.prune();
final String xmlStr = metadataBusiness.marshallMetadata(metadata);
return Response.ok(xmlStr, MediaType.APPLICATION_XML_TYPE)
.header("Content-Disposition", "attachment; filename=\"" + datasetIdentifier + ".xml\"").build();
}
}catch(Exception ex){
LOGGER.log(Level.WARNING, "Failed to get xml metadata for dataset with identifier "+datasetIdentifier,ex);
}
return Response.ok("<empty></empty>", MediaType.APPLICATION_XML_TYPE)
.header("Content-Disposition", "attachment; filename=\"" + datasetIdentifier + ".xml\"").build();
}
/**
* Proceed to search dataset for query.
* @param values given parameters
* @return {code Response} that contains all dataset that matches the lucene query.
*/
@POST
@Path("find")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response findDataset(@PathParam("domainId") final int domainId, final ParameterValues values) {
final String search = values.getValues().get("search");
List<DataSetBrief> briefs = new ArrayList<>();
final List<Dataset> datasetList;
try {
datasetList = datasetBusiness.searchOnMetadata(search);
for (final Dataset ds : datasetList) {
final DataSetBrief dsb = buildDatsetBrief(ds);
briefs.add(dsb);
}
return Response.ok(briefs).build();
} catch (ConstellationException | IOException ex) {
return Response.ok("Failed to parse query : "+ex.getMessage()).status(500).build();
}
}
/**
* Build {@link DataSetBrief} instance from {@link Dataset}.
* @param dataset given dataset object.
* @return {@link DataSetBrief} built from the given dataset.
*/
private DataSetBrief buildDatsetBrief(final Dataset dataset){
final Integer dataSetId = dataset.getId();
final List<DataBrief> dataBriefList = dataBusiness.getDataBriefsFromDatasetId(dataSetId);
final DataSetBrief dsb = datasetBusiness.getDatasetBrief(dataSetId, dataBriefList);
return dsb;
}
/**
* Returns a page of datasets matching the specified search criteria.
*
* @param search the search information.
* @return the {@link Page} of {@link DatasetItem}s.
*/
@POST
public Response searchDatasets(DatasetSearch search) {
PageRequest pageRequest = new PageRequest(search.getPage(), search.getSize());
// Apply sort criteria.
Sort sort = search.getSort();
if (sort != null) {
switch (sort.getOrder()) {
case DESC:
pageRequest.desc(sort.getField());
break;
case ASC:
default:
pageRequest.asc(sort.getField());
}
}
// Perform search.
Page<DatasetItem> result = datasetBusiness.fetchPage(pageRequest,
search.isExcludeEmpty(),
search.getText(),
search.getHasVectorData(),
search.getHasCoverageData(),
search.getHasLayerData(),
search.getHasSensorData());
// Extract ids of datasets that contain only one data ("singleton").
Collection<Integer> singletonIds = Lists.transform(result.getContent(), new Function<DatasetItem, Integer>() {
@Override
public Integer apply(DatasetItem dataset) {
return (dataset.getDataCount() == 1) ? dataset.getId() : null;
}
});
if (singletonIds.isEmpty()) { // no "singleton" datasets
return ok(result);
}
// Query the single data of these datasets.
List<DataItem> dataItems = dataBusiness.fetchByDatasetIds(singletonIds);
final Map<Integer, DataItem> indexedData = Maps.uniqueIndex(dataItems, new Function<DataItem, Integer>() {
@Override
public Integer apply(DataItem data) {
return data.getDatasetId();
}
});
// Map single data of each "singleton" dataset in response.
return ok(result.transform(new Function<DatasetItem, DatasetItem>() {
@Override
public DatasetItem apply(DatasetItem dataset) {
if (dataset.getDataCount() == 1 && indexedData.containsKey(dataset.getId())) {
return datasetBusiness.getSingletonDatasetItem(dataset, Arrays.asList(indexedData.get(dataset.getId())));
}
return dataset;
}
}));
}
/**
* Deletes the dataset with the specified {@literal datasetId}.
*
* @param datasetId the dataset id.
* @return a {@link Response} with the appropriate HTTP status (and entity).
* @throws ConfigurationException if the dataset deletion has failed for any reason.
*/
@DELETE
@Path("/{datasetId}")
public Response deleteDataset(@PathParam("datasetId") int datasetId) throws ConfigurationException {
if (datasetBusiness.existsById(datasetId)) {
datasetBusiness.removeDataset(datasetId);
return Response.noContent().build();
}
return Response.status(404).build();
}
/**
* Lists the data of the dataset with the specified {@literal datasetId}.
*
* @param datasetId the dataset id.
* @return the {@link List} of {@link DataItem}s.
*/
@GET
@Path("/{datasetId}/data")
public Response getDatasetData(@PathParam("datasetId") int datasetId) {
if (datasetBusiness.existsById(datasetId)) {
return ok(dataBusiness.fetchByDatasetId(datasetId));
}
return Response.status(404).build();
}
}