/*
* 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 static org.geotoolkit.parameter.Parameters.value;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.apache.sis.storage.DataStore;
import org.apache.sis.util.logging.Logging;
import org.constellation.admin.util.ImageStatisticSerializer;
import org.constellation.api.DataType;
import org.constellation.business.IDataCoverageJob;
import org.constellation.database.api.jooq.tables.pojos.Data;
import org.constellation.database.api.jooq.tables.pojos.Provider;
import org.constellation.database.api.repository.DataRepository;
import org.constellation.database.api.repository.ProviderRepository;
import org.constellation.provider.DataProvider;
import org.constellation.provider.DataProviders;
import org.geotoolkit.metadata.ImageStatistics;
import org.geotoolkit.process.ProcessEvent;
import org.opengis.parameter.ParameterValueGroup;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.geotoolkit.util.NamesExt;
import org.geotoolkit.processing.ProcessListenerAdapter;
import org.geotoolkit.processing.coverage.statistics.Statistics;
import static org.geotoolkit.processing.coverage.statistics.StatisticsDescriptor.OUTCOVERAGE;
import org.geotoolkit.storage.coverage.CoverageReference;
import org.geotoolkit.storage.coverage.CoverageStore;
import org.opengis.util.GenericName;
/**
*
* @author Quentin Boileau (Geomatys)
*/
@Component
@Primary
public class DataCoverageJob implements IDataCoverageJob {
/**
* Used for debugging purposes.
*/
protected static final Logger LOGGER = Logging.getLogger("org.constellation.admin");
public static final String STATE_PENDING = "PENDING";
public static final String STATE_ERROR = "ERROR";
public static final String STATE_COMPLETED = "COMPLETED";
public static final String STATE_PARTIAL = "PARTIAL";
/**
* Injected data repository.
*/
@Inject
private DataRepository dataRepository;
/**
* Injected data repository.
*/
@Inject
private ProviderRepository providerRepository;
/**
* {@inheritDoc}
*/
@Override
@Async
public Future<ImageStatistics> asyncUpdateDataStatistics(final int dataId) {
Data data = dataRepository.findById(dataId);
if (data == null) {
LOGGER.log(Level.WARNING, "Can't compute coverage statistics on data id " + dataId +
" because data is not found in database.");
return null;
}
try {
if (DataType.COVERAGE.name().equals(data.getType())
&& (data.getRendered() == null || !data.getRendered())
&& data.getStatsState() == null) {
LOGGER.log(Level.INFO, "Start computing data " + dataId + " "+data.getName()+" coverage statistics.");
data.setStatsState(STATE_PENDING);
updateData(data);
final Provider provider = providerRepository.findOne(data.getProvider());
final DataProvider dataProvider = DataProviders.getInstance().getProvider(provider.getIdentifier());
final DataStore store = dataProvider.getMainStore();
if (store instanceof CoverageStore) {
final CoverageStore coverageStore = (CoverageStore) store;
GenericName name = NamesExt.create(data.getNamespace(), data.getName());
if (data.getNamespace() == null || data.getNamespace().isEmpty()) {
name = NamesExt.create(data.getName());
}
final CoverageReference covRef = coverageStore.getCoverageReference(name);
final org.geotoolkit.process.Process process = new Statistics(covRef, false);
process.addListener(new DataStatisticsListener(dataId));
final ParameterValueGroup out = process.call();
return new AsyncResult<>(value(OUTCOVERAGE, out));
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error during coverage statistic update for data " + dataId + " "+data.getName() + " : " + e.getMessage(), e);
//update data
Data lastData = dataRepository.findById(dataId);
if (lastData != null && !lastData.getStatsState().equals(STATE_ERROR)) {
data.setStatsState(STATE_ERROR);
//data.setStatsResult(Exceptions.formatStackTrace(e));
updateData(data);
}
}
return null;
}
private void updateData(final Data data) {
SpringHelper.executeInTransaction(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
dataRepository.update(data);
// forward original data statistic result and state to pyramid conform child.
final List<Data> dataChildren = dataRepository.getDataLinkedData(data.getId());
for (Data dataChild : dataChildren) {
if (dataChild.getSubtype().equalsIgnoreCase("pyramid") && !dataChild.getRendered()) {
dataChild.setStatsResult(data.getStatsResult());
dataChild.setStatsState(data.getStatsState());
dataRepository.update(dataChild);
}
}
}
});
}
/**
* ProcessListener that will update data record in database.
*/
private class DataStatisticsListener extends ProcessListenerAdapter {
private int dataId;
public DataStatisticsListener(int dataId) {
this.dataId = dataId;
}
@Override
public void progressing(ProcessEvent event) {
if (event.getOutput() != null) {
final Data data = getData();
if (data != null) {
try {
data.setStatsState(STATE_PARTIAL);
data.setStatsResult(statisticsAsString(event));
updateData(data);
} catch (JsonProcessingException e) {
data.setStatsState(STATE_ERROR);
data.setStatsResult("Error during statistic serializing.");
updateData(data);
}
}
}
}
@Override
public void completed(ProcessEvent event) {
final Data data = getData();
if (data != null) {
try {
data.setStatsState(STATE_COMPLETED);
data.setStatsResult(statisticsAsString(event));
updateData(data);
LOGGER.log(Level.INFO, "Data " + dataId + " " + data.getName() + " coverage statistics completed.");
} catch (JsonProcessingException e) {
data.setStatsState(STATE_ERROR);
data.setStatsResult("Error during statistic serializing.");
updateData(data);
}
}
}
@Override
public void failed(ProcessEvent event) {
final Data data = getData();
if (data != null) {
data.setStatsState(STATE_ERROR);
//data.setStatsResult(Exceptions.formatStackTrace(event.getException()));
updateData(data);
Exception exception = event.getException();
LOGGER.log(Level.WARNING, "Error during coverage statistic update for data " + dataId +
" " + data.getName() + " : " + exception.getMessage(), exception);
}
}
private Data getData() {
return dataRepository.findById(dataId);
}
/**
* Serialize Statistic in JSON
* @param event
* @return JSON String or null if event output is null.
* @throws JsonProcessingException
*/
private String statisticsAsString(ProcessEvent event) throws JsonProcessingException {
final ParameterValueGroup out = event.getOutput();
if (out != null) {
final ImageStatistics statistics = value(OUTCOVERAGE, out);
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addSerializer(ImageStatistics.class, new ImageStatisticSerializer()); //custom serializer
mapper.registerModule(module);
//mapper.enable(SerializationFeature.INDENT_OUTPUT); //json pretty print
return mapper.writeValueAsString(statistics);
}
return null;
}
}
}