/*
* 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.admin;
import com.google.common.base.Optional;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.sis.metadata.iso.DefaultMetadata;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.logging.Logging;
import org.constellation.admin.exception.ConstellationException;
import org.constellation.admin.index.IndexEngine;
import org.constellation.admin.listener.DefaultDataBusinessListener;
import org.constellation.admin.listener.IDataBusinessListener;
import org.constellation.admin.util.MetadataUtilities;
import org.constellation.business.IDatasetBusiness;
import org.constellation.business.IMetadataBusiness;
import org.constellation.configuration.ConfigDirectory;
import org.constellation.configuration.ConfigurationException;
import org.constellation.configuration.DataBrief;
import org.constellation.configuration.DataSetBrief;
import org.constellation.configuration.TargetNotFoundException;
import org.constellation.database.api.jooq.tables.pojos.CstlUser;
import org.constellation.database.api.jooq.tables.pojos.Data;
import org.constellation.database.api.jooq.tables.pojos.Dataset;
import org.constellation.database.api.jooq.tables.pojos.Provider;
import org.constellation.database.api.repository.DataRepository;
import org.constellation.database.api.repository.DatasetRepository;
import org.constellation.database.api.repository.ProviderRepository;
import org.constellation.database.api.repository.UserRepository;
import org.constellation.provider.DataProvider;
import org.constellation.provider.DataProviders;
import org.constellation.security.SecurityManagerHolder;
import org.constellation.utils.CstlMetadatas;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.temporal.object.TemporalUtilities;
import org.geotoolkit.util.FileUtilities;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.NoSuchIdentifierException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.constellation.database.api.domain.Page;
import org.constellation.database.api.domain.Pageable;
import org.constellation.database.api.pojo.DataItem;
import org.constellation.database.api.pojo.DatasetItem;
import org.constellation.database.api.pojo.DatasetItemWithData;
/**
*
* Business facade for dataset.
*
* @author Guilhem Legal (Geomatys)
* @author Mehdi Sidhoum (Geomatys).
* @since 0.9
*/
@Component("cstlDatasetBusiness")
@Primary
public class DatasetBusiness implements IDatasetBusiness {
/**
* Used for debugging purposes.
*/
protected static final Logger LOGGER = Logging.getLogger("org.constellation.admin");
/**
* w3c document builder factory.
*/
protected final DocumentBuilderFactory dbf;
/**
* Injected user repository.
*/
@Inject
protected UserRepository userRepository;
/**
* Injected dataset repository.
*/
@Inject
protected DatasetRepository datasetRepository;
/**
* Injected data repository.
*/
@Inject
protected DataRepository dataRepository;
/**
* Injected provider repository.
*/
@Inject
private ProviderRepository providerRepository;
/**
* Injected metadata repository.
*/
@Inject
protected IMetadataBusiness metadataBusiness;
/**
* Injected lucene index engine.
*/
@Inject
protected IndexEngine indexEngine;
@Autowired(required = false)
private IDataBusinessListener dataBusinessListener = new DefaultDataBusinessListener();
/**
* Creates a new instance of {@link DatasetBusiness}.
*/
public DatasetBusiness() {
dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
}
/**
* {@inheritDoc}
*/
@Override
public List<Dataset> getAllDataset() {
return datasetRepository.findAll();
}
/**
* {@inheritDoc}
*/
@Override
public Dataset getDataset(final String identifier) {
return datasetRepository.findByIdentifier(identifier);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public Dataset createDataset(final String identifier, final String metadataXml, final Integer owner) throws ConfigurationException {
Dataset ds = new Dataset();
ds.setIdentifier(identifier);
ds.setOwner(owner);
ds.setDate(System.currentTimeMillis());
ds = datasetRepository.insert(ds);
if (metadataXml != null) {
final DefaultMetadata meta = (DefaultMetadata) metadataBusiness.unmarshallMetadata(metadataXml);
updateMetadata(identifier, meta);
}
return ds;
}
/**
* {@inheritDoc}
*/
@Override
public DefaultMetadata getMetadata(final String datasetIdentifier) throws ConfigurationException {
final Dataset dataset = getDataset(datasetIdentifier);
if (dataset != null) {
return metadataBusiness.getIsoMetadataForDataset(dataset.getId());
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public void updateMetadata(final String datasetIdentifier,
final DefaultMetadata metadata) throws ConfigurationException {
final Dataset dataset = datasetRepository.findByIdentifier(datasetIdentifier);
if (dataset != null) {
metadataBusiness.updateMetadata(metadata.getFileIdentifier(), metadata, null, dataset.getId(), null);
indexEngine.addMetadataToIndexForDataset(metadata, dataset.getId());
} else {
throw new TargetNotFoundException("Dataset :" + datasetIdentifier + " not found");
}
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public void saveMetadata(final String providerId, final String dataType) throws ConfigurationException {
final DataProvider dataProvider = DataProviders.getInstance().getProvider(providerId);
DefaultMetadata extractedMetadata;
String crsName = null;
if (dataType != null) {
switch (dataType) {
case "raster":
try {
extractedMetadata = MetadataUtilities.getRasterMetadata(dataProvider);
crsName = MetadataUtilities.getRasterCRSName(dataProvider);
} catch (DataStoreException e) {
LOGGER.log(Level.WARNING, "Error when trying to get raster metadata", e);
extractedMetadata = new DefaultMetadata();
}
break;
case "vector":
try {
extractedMetadata = MetadataUtilities.getVectorMetadata(dataProvider);
crsName = MetadataUtilities.getVectorCRSName(dataProvider);
} catch (DataStoreException | TransformException e) {
LOGGER.log(Level.WARNING, "Error when trying to get metadata for a shape file", e);
extractedMetadata = new DefaultMetadata();
}
break;
default:
extractedMetadata = new DefaultMetadata();
}
} else {
extractedMetadata = new DefaultMetadata();
}
//Update metadata
final Properties prop = getMetadataTemplateProperties();
final String metadataID = CstlMetadatas.getMetadataIdForDataset(providerId);
prop.put("fileId", metadataID);
prop.put("dataTitle", metadataID);
prop.put("dataAbstract", "");
final String dateIso = TemporalUtilities.toISO8601Z(new Date(), TimeZone.getTimeZone("UTC"));
prop.put("isoCreationDate", dateIso);
prop.put("creationDate", dateIso);
if("raster".equalsIgnoreCase(dataType)){
prop.put("dataType", "grid");
}else if("vector".equalsIgnoreCase(dataType)){
prop.put("dataType", "vector");
}
if(crsName != null){
prop.put("srs", crsName);
}
// get current user name and email and store into metadata contact.
final String login = SecurityManagerHolder.getInstance().getCurrentUserLogin();
final Optional<CstlUser> optUser = userRepository.findOne(login);
if(optUser!=null && optUser.isPresent()){
final CstlUser user = optUser.get();
if (user != null) {
prop.put("contactName", user.getFirstname()+" "+user.getLastname());
prop.put("contactEmail", user.getEmail());
}
}
//fill in keywords all data name of dataset children.
final Dataset dataset = getDataset(providerId);
if(dataset!= null){
final List<Data> dataList = dataRepository.findAllByDatasetId(dataset.getId());
if(dataList != null){
final List<String> keywords = new ArrayList<>();
for(final Data d : dataList){
final String dataName = d.getName();
if(!keywords.contains(dataName)){
keywords.add(dataName);
}
}
if(!keywords.isEmpty()){
prop.put("keywords",keywords);
}
}
}
final String xml = MetadataUtilities.getTemplateMetadata(prop, "org/constellation/engine/template/mdTemplDataset.xml");
final DefaultMetadata templateMetadata = (DefaultMetadata) metadataBusiness.unmarshallMetadata(xml);
DefaultMetadata mergedMetadata;
if (extractedMetadata != null) {
mergedMetadata = new DefaultMetadata();
try {
mergedMetadata = MetadataUtilities.mergeMetadata(templateMetadata, extractedMetadata);
} catch (NoSuchIdentifierException | ProcessException ex) {
LOGGER.log(Level.WARNING, "error while merging metadata", ex);
}
} else {
mergedMetadata = templateMetadata;
}
//merge with uploaded metadata
DefaultMetadata uploadedMetadata;
try {
uploadedMetadata = getMetadata(providerId);
} catch (Exception ex) {
uploadedMetadata = null;
}
if (uploadedMetadata != null) {
try {
mergedMetadata = MetadataUtilities.mergeMetadata(uploadedMetadata,mergedMetadata);
} catch (NoSuchIdentifierException | ProcessException ex) {
LOGGER.log(Level.WARNING, "error while merging built metadata with uploaded metadata!", ex);
}
}
mergedMetadata.prune();
//Save metadata
updateMetadata(providerId, mergedMetadata);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public void linkDataTodataset(final Dataset dataset, final List<Data> datas) {
for (final Data data : datas) {
data.setDatasetId(dataset.getId());
dataRepository.update(data);
}
}
/**
* {@inheritDoc}
*/
@Override
public List<Dataset> searchOnMetadata(final String queryString) throws IOException, ConstellationException {
final List<Dataset> result = new ArrayList<>();
final Set<Integer> ids;
try {
ids = indexEngine.searchOnMetadata(queryString, "datasetId");
} catch( ParseException ex) {
throw new ConstellationException(ex);
}
for (final Integer datasetId : ids){
final Dataset d = datasetRepository.findById(datasetId);
if(d!=null){
result.add(d);
}
}
return result;
}
@Override
@Transactional
public void addProviderDataToDataset(final String datasetId, final String providerId) throws ConfigurationException {
final Dataset ds = datasetRepository.findByIdentifier(datasetId);
if (ds != null) {
final Provider p = providerRepository.findByIdentifier(providerId);
if (p != null) {
final List<Data> datas = dataRepository.findByProviderId(p.getId());
for (Data data : datas) {
data.setDatasetId(ds.getId());
dataRepository.update(data);
}
} else {
throw new TargetNotFoundException("Unable to find a profile: " + providerId);
}
} else {
throw new TargetNotFoundException("Unable to find a dataset: " + datasetId);
}
}
@Transactional
@Override
public void removeDataset(int datasetId) throws ConfigurationException {
removeDataset(datasetRepository.findById(datasetId));
}
@Transactional
@Override
public void removeDataset(String datasetIdentifier) throws ConfigurationException {
removeDataset(datasetRepository.findByIdentifier(datasetIdentifier));
}
@Transactional
@Override
public void removeAllDatasets() throws ConfigurationException {
final List<Dataset> all = datasetRepository.findAll();
for (Dataset dataset : all) {
removeDataset(dataset.getId());
}
}
private void removeDataset(Dataset ds) throws ConfigurationException {
if (ds != null) {
final Set<Integer> involvedProvider = new HashSet<>();
final Set<Data> linkedData = new HashSet<>();
// 1. hide data
linkedData.addAll(dataRepository.findAllByDatasetId(ds.getId()));
//find provider with same identifier as dataset
final Provider linkedProvider = providerRepository.findByIdentifier(ds.getIdentifier());
if (linkedProvider != null) {
final List<Data> providerData = dataRepository.findByProviderId(linkedProvider.getId());
linkedData.addAll(providerData);
if (providerData.isEmpty()) {
//handle empty provider
involvedProvider.add(linkedProvider.getId());
}
}
for (Data data : linkedData) {
data.setIncluded(false);
data.setDatasetId(null);
dataRepository.update(data);
involvedProvider.add(data.getProvider());
metadataBusiness.deleteDataMetadata(data.getId());
dataRepository.removeDataFromAllCSW(data.getId());
}
// 2. cleanup provider if empty
for (Integer providerID : involvedProvider) {
boolean remove = true;
List<Data> providerData = dataRepository.findByProviderId(providerID);
for (Data pdata : providerData) {
if (pdata.getIncluded()) {
remove = false;
break;
}
}
if (remove) {
final Provider p = providerRepository.findOne(providerID);
final DataProvider dp = DataProviders.getInstance().getProvider(p.getIdentifier());
//will remove provider from cache, delete data and delete provider from database.
DataProviders.getInstance().removeProvider(dp);
final File provDir = ConfigDirectory.getDataIntegratedDirectory(p.getIdentifier());
FileUtilities.deleteDirectory(provDir);
}
}
// 3. remove metadata
metadataBusiness.deleteDatasetMetadata(ds.getId());
// 4. remove internal csw link
datasetRepository.removeDatasetFromAllCSW(ds.getId());
// 5. remove dataset
indexEngine.removeDatasetMetadataFromIndex(ds.getId());
datasetRepository.remove(ds.getId());
}
}
@Override
public DataSetBrief getDatasetBrief(Integer dataSetId, List<DataBrief> children) {
final Dataset dataset = datasetRepository.findById(dataSetId);
Integer completion = metadataBusiness.getCompletionForDataset(dataSetId);
String type = null;
if (!children.isEmpty()) {
type = children.get(0).getType();
}
final Optional<CstlUser> optUser = userRepository.findById(dataset.getOwner());
String owner = null;
if(optUser!=null && optUser.isPresent()){
final CstlUser user = optUser.get();
if(user != null){
owner = user.getLogin();
}
}
final DataSetBrief dsb = new DataSetBrief(dataset.getId(),
dataset.getIdentifier(),
type, owner, children,
dataset.getDate(),
completion);
return dsb;
}
@Override
public Page<DatasetItem> fetchPage(Pageable pageable, boolean excludeEmpty, String termFilter, Boolean hasVectorData, Boolean hasCoverageData, Boolean hasLayerData, Boolean hasSensorData) {
return datasetRepository.fetchPage(pageable, excludeEmpty, termFilter, hasVectorData, hasCoverageData, hasLayerData, hasSensorData);
}
@Override
public boolean existsById(int datasetId) {
return datasetRepository.existsById(datasetId);
}
@Override
public DatasetItemWithData getSingletonDatasetItem(DatasetItem dsItem, List<DataItem> items) {
return new DatasetItemWithData(dsItem, items);
}
private Properties getMetadataTemplateProperties() {
final File cstlDir = ConfigDirectory.getConfigDirectory();
final File propFile = new File(cstlDir, "metadataTemplate.properties");
final Properties prop = new Properties();
if (propFile.exists()) {
try {
prop.load(new FileReader(propFile));
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "IOException while loading metadata template properties file", ex);
}
}
return prop;
}
}