package nl.ipo.cds.etl.process;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import javax.xml.stream.XMLStreamReader;
import nl.idgis.commons.jobexecutor.JobLogger;
import nl.ipo.cds.domain.EtlJob;
import nl.ipo.cds.etl.DatasetHandlers;
import nl.ipo.cds.etl.Feature;
import nl.ipo.cds.etl.FeatureFilter;
import nl.ipo.cds.etl.FeatureOutputStream;
import nl.ipo.cds.etl.FileCache;
import nl.ipo.cds.etl.PersistableFeature;
import nl.ipo.cds.etl.attributemapping.AttributeMappingFactory;
import nl.ipo.cds.etl.db.DBWriter;
import nl.ipo.cds.etl.db.DBWriterFactory;
import nl.ipo.cds.etl.filtering.DatasetFiltererFactory;
import nl.ipo.cds.etl.process.helpers.HttpGetUtil;
import nl.ipo.cds.etl.util.CopyInOutputStream;
import org.apache.axiom.om.OMElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.deegree.commons.xml.stax.XMLStreamUtils;
import org.postgresql.copy.CopyIn;
import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractorAdapter;
public class ImportFeatureProcessor extends ValidateFeatureProcessor {
private static final Log technicalLog = LogFactory.getLog(ImportFeatureProcessor.class); // developer log
private final DataSource dataSource;
private final NativeJdbcExtractorAdapter nativeJdbcExtractorAdapter;
private final FileCache fileCache;
public ImportFeatureProcessor (
final AttributeMappingFactory attributeMappingFactory,
final DatasetFiltererFactory datasetFiltererFactory,
final DataSource dataSource,
final NativeJdbcExtractorAdapter nativeJdbcExtractorAdapter,
final FileCache fileCache) {
super (attributeMappingFactory, datasetFiltererFactory);
this.dataSource = dataSource;
this.nativeJdbcExtractorAdapter = nativeJdbcExtractorAdapter;
this.fileCache = fileCache;
}
/**
* Adds a filter to the list of standard filters. A filter is appended to the list that writes features to the dbWriter.
*/
@Override
protected List<FeatureFilter<PersistableFeature, PersistableFeature>> createFilters (final EtlJob job, final DatasetHandlers<PersistableFeature> datasetHandlers, final JobLogger logger) {
final List<FeatureFilter<PersistableFeature, PersistableFeature>> filters = new ArrayList<FeatureFilter<PersistableFeature, PersistableFeature>> (super.createFilters (job, datasetHandlers, logger));
// Add a DBWriter filter at the end of the pipeline:
filters.add (createDBWriterFilter (job, datasetHandlers, logger));
return filters;
}
private FeatureFilter<PersistableFeature, PersistableFeature> createDBWriterFilter (final EtlJob job, final DatasetHandlers<PersistableFeature> datasetHandlers, final JobLogger logger) {
technicalLog.debug("initializing");
final Connection connection = DataSourceUtils.getConnection(dataSource);
technicalLog.debug("database connection established");
DBWriterFactory<PersistableFeature> dbWriterFactory = datasetHandlers.getDBWriterFactory("job_id", "" + job.getId());
try {
String query =
"delete from " + dbWriterFactory.getTableName() + " " +
"where job_id in (" +
"select j0.id from manager.etljob j0 where j0.uuid = ? )";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, job.getUuid());
technicalLog.debug("delete query: " + query + ", uuid = " + job.getUuid());
int count = stmt.executeUpdate();
technicalLog.debug("# of deleted features: " + count);
stmt.close();
} catch(SQLException e) {
throw new RuntimeException("Couldn't remove existing data", e);
}
technicalLog.debug("existing data removed");
CopyManager copyManager;
try {
copyManager = new CopyManager((BaseConnection)
nativeJdbcExtractorAdapter.getNativeConnection(connection));
} catch(SQLException e) {
throw new RuntimeException("Couldn't construct CopyManager");
}
technicalLog.debug("CopyManager constructed");
final java.io.OutputStream outputStream;
try {
CopyIn copyIn = copyManager.copyIn(dbWriterFactory.getQuery());
outputStream = new CopyInOutputStream(copyIn);
} catch(SQLException e) {
throw new RuntimeException("Couldn't start copy", e);
}
technicalLog.debug("copy started");
final DBWriter<PersistableFeature> dbWriter;
try {
dbWriter = dbWriterFactory.getDBWriter(outputStream, "utf-8");
} catch(UnsupportedEncodingException e) {
throw new RuntimeException("Couldn't construct DBWriter", e);
}
technicalLog.debug("dbwriter constructed");
return new FeatureFilter<PersistableFeature, PersistableFeature> () {
@Override
public void processFeature (final PersistableFeature feature, final FeatureOutputStream<PersistableFeature> outputStream, final FeatureOutputStream<Feature> errorOutputStream) {
technicalLog.debug("writing feature: " + feature.getId());
dbWriter.writeObject(feature);
}
@Override
public void finish () {
dbWriter.close();
technicalLog.debug("dbWriter closed");
DataSourceUtils.releaseConnection(connection, dataSource);
}
@Override
public boolean postProcess() {
return true;
}
};
}
@Override
public URL processUrl(EtlJob job) {
URL processedUrl = null;
HttpGetUtil wfsHttpGetUtil = null;
try {
processedUrl = new URL(job.getDatasetUrl());
technicalLog.debug("trying to download " + processedUrl);
wfsHttpGetUtil = new HttpGetUtil(processedUrl.toExternalForm());
if(!wfsHttpGetUtil.isValidResponse()) {
// invalid dataset urls are to be properly handled elsewhere,
// in this method we only try to persist the data in the cache
return processedUrl;
}
technicalLog.debug("writing to file cache");
XMLStreamReader xmlStream = wfsHttpGetUtil.getEntityOMElement().getXMLStreamReaderWithoutCaching();
XMLStreamUtils.skipStartDocument(xmlStream);
processedUrl = fileCache.storeToCache(job, xmlStream);
xmlStream.close();
} catch (Exception e) {
throw new RuntimeException("Not be able to write to fileCache" ,e);
} finally{
if(wfsHttpGetUtil != null) {
wfsHttpGetUtil.close();
}
}
return processedUrl;
}
public FileCache getFileCache() {
return fileCache;
}
@Override
public boolean requiresFeatureProcessing(EtlJob job) {
return job.getVerversen();
}
}