package nl.ipo.cds.etl; import static nl.ipo.cds.etl.process.HarvesterMessageKey.METADATA_FEATURETYPE_INVALID; import static nl.ipo.cds.etl.process.HarvesterMessageKey.METADATA_FEATURETYPE_NOT_FOUND; import java.io.File; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.sql.DataSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import nl.idgis.commons.jobexecutor.Job; import nl.idgis.commons.jobexecutor.JobLogger.LogLevel; import nl.ipo.cds.attributemapping.operations.OperationType; import nl.ipo.cds.attributemapping.operations.discover.OperationDiscoverer; import nl.ipo.cds.dao.ManagerDao; import nl.ipo.cds.dao.attributemapping.AttributeMappingDao; import nl.ipo.cds.dao.attributemapping.InputOperationDTO; import nl.ipo.cds.dao.attributemapping.OperationDTO; import nl.ipo.cds.dao.attributemapping.OperationInputDTO; import nl.ipo.cds.dao.attributemapping.TransformOperationDTO; import nl.ipo.cds.dao.impl.ManagerDaoImpl; import nl.ipo.cds.domain.AttributeType; import nl.ipo.cds.domain.Dataset; import nl.ipo.cds.domain.FeatureType; import nl.ipo.cds.domain.FeatureTypeAttribute; import nl.ipo.cds.domain.ValidateJob; import nl.ipo.cds.etl.attributemapping.AttributeMappingValidator; import nl.ipo.cds.etl.attributemapping.AttributeMappingValidator.MessageKey; import nl.ipo.cds.etl.featuretype.FeatureTypeNotFoundException; import nl.ipo.cds.etl.featuretype.GMLFeatureTypeParser; import nl.ipo.cds.etl.log.EventLogger; import nl.ipo.cds.etl.operations.transform.ConditionalTransform; import nl.ipo.cds.etl.operations.transform.SplitStringTransform; import nl.ipo.cds.etl.process.DatasetMetadata; import nl.ipo.cds.etl.process.HarvesterException; import nl.ipo.cds.etl.process.HarvesterFactory; import nl.ipo.cds.etl.process.MetadataHarvester; import nl.ipo.cds.etl.theme.AttributeDescriptor; import nl.ipo.cds.etl.theme.ThemeConfig; import nl.ipo.cds.etl.theme.ThemeDiscoverer; import nl.ipo.cds.etl.util.BlockingExecutor; import nl.ipo.cds.executor.ConfigDir; import org.deegree.commons.xml.XMLProcessingException; import org.deegree.feature.types.AppSchema; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class GenerateAttributeMappings implements Runnable { private static final int ADVISORY_LOCK_KEY = 0; @Configuration @ComponentScan (basePackageClasses = { nl.ipo.cds.etl.config.Package.class, nl.ipo.cds.etl.theme.Package.class }) @ImportResource ({ "/nl/ipo/cds/dao/dataSource-applicationContext.xml", "/nl/ipo/cds/dao/dao-applicationContext.xml" }) public static class Config { @Bean public ConfigDir configDir (final @Value("file:${CONFIGDIR}") String configDirPath) { return new ConfigDir (configDirPath); } @Bean public GenerateAttributeMappings verifyDatasetSchemas () { return new GenerateAttributeMappings (); } @Bean public Executor executor () { return new BlockingExecutor (1); } } private @Inject ManagerDao managerDao; private @Inject HarvesterFactory harvesterFactory; private @Inject DataSource dataSource; private @Inject ThemeDiscoverer themeDiscoverer; private @Inject OperationDiscoverer operationDiscoverer; private @Inject ConfigDir configDir; private Collection<OperationType> operationTypes; private AttributeMappingDao attributeMappingDao; @PostConstruct public void init () { this.operationTypes = operationDiscoverer.getOperationTypes (); this.attributeMappingDao = new AttributeMappingDao (managerDao); } public static void main (final String ... args) { try { final AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (Config.class); applicationContext.getBean (GenerateAttributeMappings.class).run (); applicationContext.close (); } catch(Exception e) { e.printStackTrace (); System.exit(1); } System.exit(0); } @Override public void run () { try { Connection connection = DataSourceUtils.getConnection(dataSource); PreparedStatement stmt = connection.prepareStatement("select pg_try_advisory_lock(?)"); stmt.setLong(1, ADVISORY_LOCK_KEY); ResultSet rs = stmt.executeQuery(); boolean result = false; while(rs.next()) { result = rs.getBoolean(1); } rs.close(); stmt.close(); if(!result) { System.err.println("Couldn't acquire lock (JobExecutor already running?)"); return; } for (final Dataset ds: managerDao.getAllDatasets ()) { processDataset (ds); } DataSourceUtils.releaseConnection(connection, dataSource); } catch (Exception e) { e.printStackTrace (); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void processDataset (final Dataset dataset) { try { final FeatureType featureType = getFeatureType (dataset); final Set<AttributeDescriptor<?>> attributeDescriptors = getAttributeDescriptors (dataset); for (final AttributeDescriptor<?> attributeDescriptor: attributeDescriptors) { processAttributeDescriptor (dataset, attributeDescriptor, featureType); } } catch (Exception e) { e.printStackTrace (); } } @Transactional (propagation = Propagation.REQUIRES_NEW) public void processAttributeDescriptor (final Dataset dataset, final AttributeDescriptor<?> attributeDescriptor, final FeatureType featureType) throws Exception { final List<OperationInputDTO> transformInputs = new ArrayList<OperationInputDTO> (); // Locate a feature type attribute: final FeatureTypeAttribute featureTypeAttribute = getFeatureTypeAttribute (dataset, attributeDescriptor, featureType); if (featureTypeAttribute == null) { return; } final OperationDTO operation = createAttributeOperation (dataset, attributeDescriptor, featureTypeAttribute); transformInputs.add (new OperationInputDTO (operation)); // Create a conditional transform operation: final TransformOperationDTO transformOperation = new TransformOperationDTO (getConditionalTransformOperationType (), transformInputs, new ConditionalTransform.Settings ()); // Validate the mapping: final AttributeMappingValidator validator = new AttributeMappingValidator (attributeDescriptor, featureType, new EventLogger<AttributeMappingValidator.MessageKey> () { @Override public String logEvent(Job job, MessageKey messageKey, LogLevel logLevel, Map<String, Object> context, String... messageValues) { return null; } @Override public String logEvent(Job job, MessageKey messageKey, LogLevel logLevel, double x, double y, String gmlId, String... messageValues) { return null; } @Override public String logEvent(Job job, MessageKey messageKey, LogLevel logLevel, String... messageValues) { return null; } }); final boolean isValid = validator.isValid (new ValidateJob (), transformOperation); // Store the mapping: try { attributeMappingDao.putAttributeMapping (dataset, attributeDescriptor, transformOperation, isValid); ((ManagerDaoImpl)managerDao).getEntityManager ().flush (); } catch (Exception e) { e.printStackTrace (); System.exit (1); } } private OperationDTO createAttributeOperation (final Dataset dataset, final AttributeDescriptor<?> attributeDescriptor, final FeatureTypeAttribute attribute) { final InputOperationDTO inputOperation = new InputOperationDTO (attribute, attribute.getName ().getLocalPart (), attribute.getType ()); // Convert string to list: if (attributeDescriptor.getAttributeType ().equals (String[].class)) { final SplitStringTransform.Settings settings = new SplitStringTransform.Settings (); settings.setBoundary ("|"); return new TransformOperationDTO ( getTransformOperationType ("splitStringTransform"), Arrays.asList (new OperationInputDTO[] { new OperationInputDTO (inputOperation) }), settings ); } // Convert string to date: if (attributeDescriptor.getAttributeType ().equals (Date.class) && attribute.getType ().equals (AttributeType.STRING)) { return new TransformOperationDTO ( getTransformOperationType ("toDateTransform"), Arrays.asList (new OperationInputDTO[] { new OperationInputDTO (inputOperation) }), null ); } // Convert datetime to date: if (attributeDescriptor.getAttributeType ().equals (Date.class) && attribute.getType ().equals (AttributeType.DATE_TIME)) { return new TransformOperationDTO ( getTransformOperationType ("timestampToDateTransform"), Arrays.asList (new OperationInputDTO[] { new OperationInputDTO (inputOperation) }), null ); } return inputOperation; } private FeatureTypeAttribute getFeatureTypeAttribute (final Dataset dataset, final AttributeDescriptor<?> attributeDescriptor, final FeatureType featureType) throws Exception { final String attributeName = attributeDescriptor.getName (); // Try to locate an exact match: final FeatureTypeAttribute exactMatch = getFeatureTypeAttribute (featureType, attributeName); if (exactMatch != null) { return exactMatch; } // Locate a mapped match: final File[] files = new File[] { new File (new File (configDir.getPath ().substring (5)), "nl/ipo/cds/etl/protectedSite/config/generic.xml"), new File (new File (configDir.getPath ().substring (5)), "nl/ipo/cds/etl/protectedSite/config/" + dataset.getBronhouder ().getCode () + ".xml"), new File (new File (configDir.getPath ().substring (5)), "nl/ipo/cds/etl/protectedSite/config/" + dataset.getBronhouder ().getCode () + "." + dataset.getDatasetType ().getNaam () + ".xml") }; final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder (); final XPathFactory xpathFactory = XPathFactory.newInstance (); final XPath xpath = xpathFactory.newXPath (); final XPathExpression expr = xpath.compile ("/config/replace/*"); for (final File file: files) { if (!file.exists ()) { continue; } final Document document = builder.parse (file); final NodeList nodeList = (NodeList)expr.evaluate (document, XPathConstants.NODESET); for (int i = 0; i < nodeList.getLength (); ++ i) { final Node node = nodeList.item (i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } final Element element = (Element)node; final String textContent = element.getTextContent (); if (textContent == null) { continue; } if (!textContent.substring (5).equals (attributeName)) { continue; } final String replacedFullName = element.getTagName (); final String replacedName = replacedFullName.substring (replacedFullName.indexOf (':') + 1); return getFeatureTypeAttribute (featureType, replacedName); } } return null; } private FeatureTypeAttribute getFeatureTypeAttribute (final FeatureType featureType, final String name) { for (final FeatureTypeAttribute a: featureType.getAttributes ()) { if (a.getName ().getLocalPart ().equals (name)) { return a; } } return null; } private FeatureType getFeatureType (final Dataset dataset) throws HarvesterException { final String uuid = dataset.getUuid (); final MetadataHarvester harvester = harvesterFactory.createMetadataHarvester (); final DatasetMetadata metadata = harvester.parseMetadata (uuid); String ftNameWithColon = metadata.getFeatureTypeName(); String featureTypeName = ftNameWithColon.substring (ftNameWithColon.indexOf (':') + 1); ThemeConfig<?> themeConfig = themeDiscoverer.getThemeConfiguration(dataset.getDatasetType().getThema().getNaam()); try { AppSchema appSchema = themeConfig.getSchemaHarvester().parseApplicationSchema(metadata); return new GMLFeatureTypeParser().parseSchema(appSchema, featureTypeName); } catch (FeatureTypeNotFoundException e) { throw new HarvesterException (e, METADATA_FEATURETYPE_NOT_FOUND, metadata.getSchemaUrl(), featureTypeName); } catch (XMLProcessingException e) { throw new HarvesterException (e, METADATA_FEATURETYPE_INVALID, metadata.getSchemaUrl(), featureTypeName, e.getMessage()); } } private Set<AttributeDescriptor<?>> getAttributeDescriptors (final Dataset dataset) { final ThemeConfig<?> themeConfig = themeDiscoverer.getThemeConfiguration ( dataset .getDatasetType () .getThema () .getNaam () ); if (themeConfig == null) { throw new IllegalArgumentException (String.format ("Theme not found: %s", dataset.getDatasetType ().getThema ().getNaam ())); } return themeConfig.getAttributeDescriptors (); } private OperationType getTransformOperationType (final String operationName) { for (final OperationType operationType: operationTypes) { if (operationType.getName ().equals (operationName)) { return operationType; } } return null; } private OperationType getConditionalTransformOperationType () { for (final OperationType operationType: operationTypes) { if (operationType.getPropertyBeanClass () != null && operationType.getPropertyBeanClass ().equals (ConditionalTransform.Settings.class)) { return operationType; } } throw new IllegalStateException ("No conditional transform operation type found"); } }