package nl.ipo.cds.etl; import static java.lang.Integer.MAX_VALUE; import static nl.ipo.cds.domain.RefreshPolicy.*; import static nl.ipo.cds.utils.UrlUtils.getLastModifiedHeader; import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; import java.io.IOException; import java.net.URL; import java.sql.Timestamp; import java.util.Collection; import java.util.Properties; import javax.inject.Inject; import javax.xml.stream.XMLStreamException; import nl.idgis.commons.jobexecutor.Job; import nl.idgis.commons.jobexecutor.JobLogger; import nl.idgis.commons.jobexecutor.JobLogger.LogLevel; import nl.idgis.commons.jobexecutor.JobTypeIntrospector; import nl.idgis.commons.jobexecutor.Process; import nl.ipo.cds.dao.ManagerDao; import nl.ipo.cds.domain.Dataset; import nl.ipo.cds.domain.DatasetType; import nl.ipo.cds.domain.EtlJob; import nl.ipo.cds.etl.FeatureProcessor.ValidationException; import nl.ipo.cds.etl.featurecollection.FeatureCollection; import nl.ipo.cds.etl.featurecollection.NumberLimitedFeatureCollection; import nl.ipo.cds.etl.log.LogStringBuilder; import nl.ipo.cds.etl.process.DatasetMetadata; import nl.ipo.cds.etl.process.Harvester; import nl.ipo.cds.etl.process.HarvesterException; import nl.ipo.cds.etl.process.HarvesterFactory; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.transaction.annotation.Transactional; /** * Base class of all processes. * * Exceptionhandling is done by catching all exception. Checked Exceptions are caught (and swallowed) and * written as warning to the log. RuntimeExceptions are caught and written as error to the log, * but also propagated to make sure transaction are rolled back. * */ public abstract class AbstractProcess<T extends EtlJob> implements Process<T>, ApplicationContextAware { public static enum MessageKey { NO_FEATURES, XML_FEATURES_HTTP_ERROR, XML_FEATURES_EXCEPTION, NO_DATASET_HANDLERS, WFS_EXCEPTIONREPORT, XML_BLOCKING_FEATURE_ERROR, WFS_UNPARSEBLE_RESPONSE } private static final Log technicalLog = LogFactory.getLog(AbstractProcess.class); // developer log private final static LogLevel LOG_LEVEL = LogLevel.ERROR; private ApplicationContext applicationContext; private final Class<? extends Job> jobType; private final ManagerDao managerDao; private final Properties logProperties; private final FeatureProcessor featureProcessor; //private EventLogger<AbstractProcess.MessageKey> userLog; // user log @Inject private HarvesterFactory harvesterFactory; public AbstractProcess ( final Class<? extends Job> jobType, final ManagerDao managerDao, final FeatureProcessor featureProcessor, final Properties logProperties) { this.jobType = jobType; this.managerDao = managerDao; this.logProperties = logProperties; this.featureProcessor = featureProcessor; } @Override public Class<? extends Job> getJobType () { return jobType; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @SuppressWarnings({ "unchecked", "rawtypes" }) protected DatasetHandlers<PersistableFeature> createDatasetHandlers(EtlJob job) throws XMLStreamException, IOException { Collection<DatasetHandlersFactory> factories = applicationContext.getBeansOfType(DatasetHandlersFactory.class).values(); for(DatasetHandlersFactory<PersistableFeature> factory : factories) { if (!factory.isJobSupported (job)) { continue; } DatasetHandlers<PersistableFeature> datasetHandlers = factory.createDatasetHandlers(job, managerDao); if(datasetHandlers != null) { return datasetHandlers; } } return null; } /* (non-Javadoc) * @see nl.ipo.cds.etl.Process#executeJob(nl.ipo.cds.domain.Job) */ @Transactional @Override public boolean process (final T job, final JobLogger jobLogger) throws Exception { return process (job, featureProcessor, jobLogger); } public boolean process(final T job, final FeatureProcessor featureProcessor, final JobLogger jobLogger) throws Exception { return process(job, featureProcessor, jobLogger, MAX_VALUE); } public boolean process(final T job, final FeatureProcessor featureProcessor, final JobLogger jobLogger, int featureLimit) throws Exception { boolean error = true; technicalLog.debug("START " + JobTypeIntrospector.getJobTypeName(job) + " Job"); final LogStringBuilder<AbstractProcess.MessageKey> userLog = new LogStringBuilder<AbstractProcess.MessageKey>(); userLog.setProperties(logProperties); userLog.setJobLogger(jobLogger); DatasetHandlers<PersistableFeature> datasetHandlers = createDatasetHandlers(job); if (datasetHandlers == null) { String message = userLog.logEvent(job, AbstractProcess.MessageKey.NO_DATASET_HANDLERS, LOG_LEVEL, job.getUuid(), job.getBronhouder().getNaam(), job.getBronhouder().getCode()); job.setResult(message); technicalLog.warn(message); return error; } DatasetMetadata md = fetchDatasetMetadataAndAugmentJob(job); if (md != null) { updateVerversen (job); if (job.getVerversen()) { if (featureProcessor.requiresFeatureProcessing(job)) { FeatureCollection fc = datasetHandlers.retrieveFeaturesFromBronhouder(job, featureProcessor, userLog, md); if (fc != null) { fc = new NumberLimitedFeatureCollection(fc, featureLimit); error = processFeatures(featureProcessor, job, datasetHandlers, fc, jobLogger, userLog); } else{ job.setFeatureCount(0); } } else { technicalLog.debug("feature processing phase skipped"); } } } else { technicalLog.debug("fetching of metadata failed for " + JobTypeIntrospector.getJobTypeName(job) + " Job"); } technicalLog.debug("END " + JobTypeIntrospector.getJobTypeName(job) + " Job"); return error; } private DatasetMetadata fetchDatasetMetadataAndAugmentJob(T job) throws HarvesterException { Harvester mdHarvester = harvesterFactory.createHarvester(job); if (!mdHarvester.updateJobWithDatasetUrl()) { technicalLog.error("Unable to fetch dataset metadata for job " + job); } return mdHarvester.getMetadata(); } private void updateVerversen(T job) { //w1502 019 refreshpolicy from dataset entity class technicalLog.debug("abstract process managerDao +++++++++++++++++++++++ " + this.managerDao); technicalLog.debug("abstract process managerDao: " + managerDao); Dataset dataset = managerDao.getDatasetBy(job.getBronhouder(), job.getDatasetType(), job.getUuid()); //DatasetType datasetType = job.getDatasetType(); if ((dataset.getRefreshPolicy() == IF_MODIFIED_HTTP_HEADER)) { setMetadataUpdateDatumFromLastModifiedHeader (job); } RefreshPolicyGuard guard = new RefreshPolicyGuard(this.managerDao); EtlJob lastSuccess = managerDao.getLastSuccessfullImportJob(job); boolean needsRefresh = guard.isRefreshAllowed(job, lastSuccess); job.setVerversen(needsRefresh); } private void setMetadataUpdateDatumFromLastModifiedHeader(T job) { long lastModified = 0; try { final URL datasetUrl = new URL (job.getDatasetUrl()); lastModified = getLastModifiedHeader(datasetUrl); } catch (Exception e ) { throw new RuntimeException("Unable to retrieve last-modified header from " + job.getDatasetUrl()); } technicalLog.debug("lastModifiedHeader: " + lastModified); job.setMetadataUpdateDatum(new Timestamp(lastModified)); } private boolean processFeatures(FeatureProcessor processor, final T job, final DatasetHandlers<PersistableFeature> datasetHandlers, final FeatureCollection fc, final JobLogger jobLogger, final LogStringBuilder<AbstractProcess.MessageKey> userLog ) { boolean error = false; try { int num = processFeatures(processor, job, datasetHandlers, fc, jobLogger); job.setFeatureCount(num); if (num == 0) { String message = userLog.logEvent(job, AbstractProcess.MessageKey.NO_FEATURES, LOG_LEVEL); technicalLog.warn(message); job.setResult(message); error = true; } } catch (ValidationException e) { String message = userLog.logEvent(job, AbstractProcess.MessageKey.XML_BLOCKING_FEATURE_ERROR, LogLevel.ERROR); technicalLog.warn(message); job.setResult(message); job.setFeatureCount(0); error = true; } catch (Exception e) { String message = userLog.logEvent(job, AbstractProcess.MessageKey.XML_FEATURES_EXCEPTION, LOG_LEVEL, ExceptionUtils.getRootCauseMessage(e)); job.setResult(message); job.setFeatureCount(0); error = true; technicalLog.error("Error while parsing/processing features", e); } technicalLog.debug("processing features finished"); return error; } @Transactional(propagation=REQUIRES_NEW, rollbackFor=Throwable.class) private int processFeatures(FeatureProcessor processor, final T job, final DatasetHandlers<PersistableFeature> datasetHandlers, final FeatureCollection fc, final JobLogger jobLogger ) throws ValidationException { return processor.processFeatures(job, datasetHandlers, fc, jobLogger); } }