package com.constellio.app.modules.es.services.crawler; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.app.modules.es.connectors.spi.ConnectorEventObserver; import com.constellio.app.modules.es.connectors.spi.ConnectorLogger; import com.constellio.app.modules.es.model.connectors.ConnectorDocument; import com.constellio.app.modules.es.model.connectors.ConnectorInstance; import com.constellio.app.modules.es.services.ConnectorsUtils; import com.constellio.app.modules.es.services.ESSchemasRecordsServices; import com.constellio.app.modules.es.services.mapping.ConnectorField; import com.constellio.app.modules.es.services.mapping.ConnectorMappingService; import com.constellio.data.dao.dto.records.RecordsFlushing; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.Transaction; import com.constellio.model.entities.records.wrappers.User; import com.constellio.model.services.records.BulkRecordTransactionHandler; import com.constellio.model.services.records.BulkRecordTransactionHandlerOptions; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.records.RecordServicesRuntimeException; import com.constellio.model.services.records.RecordUtils; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.users.UserServices; public class DefaultConnectorEventObserver implements ConnectorEventObserver { private static Logger LOGGER = LoggerFactory.getLogger(DefaultConnectorEventObserver.class); UserServices userServices; ESSchemasRecordsServices es; ConnectorLogger connectorLogger; String resourceName; BulkRecordTransactionHandler handler; ConnectorMappingService mappingService; Map<String, Map<String, ConnectorField>> fieldsDeclarationPerConnectorId = new HashMap<>(); public DefaultConnectorEventObserver(ESSchemasRecordsServices es, ConnectorLogger connectorLogger, String resourceName) { this.es = es; this.connectorLogger = connectorLogger; this.resourceName = resourceName; this.userServices = es.getModelLayerFactory().newUserServices(); BulkRecordTransactionHandlerOptions options = new BulkRecordTransactionHandlerOptions().withRecordsPerBatch(50); this.handler = new BulkRecordTransactionHandler(es.getRecordServices(), resourceName, options); this.mappingService = new ConnectorMappingService(es); } @Override public void addUpdateEvents(ConnectorDocument... documents) { addUpdateEvents(asList(documents)); } @Override public void addUpdateEvents(List<ConnectorDocument> documents) { List<Record> documentRecords = new ArrayList<>(); for (ConnectorDocument document : documents) { // if (document.isFetched()) { // LOGGER.info("**** Received fetched document '" + document.getWrappedRecord().getIdTitle() + "'"); // } else { // LOGGER.info("**** Received document to fetch : '" + document.getId() + "'"); // } Map<String, ConnectorField> fieldDeclarations = applyMappedPropertiesToMetadata(document); addFieldDeclarations(document.getConnector(), fieldDeclarations); documentRecords.add(document.getWrappedRecord()); } Transaction transaction = new Transaction(documentRecords); boolean flushNow = false; for (Record record : documentRecords) { if (flushNow || es.getModelLayerFactory().getRecordsCaches().getCache(record.getCollection()) .getCacheConfigOf(record.getSchemaCode()) != null) flushNow = true; } transaction.setRecordFlushing(flushNow ? RecordsFlushing.NOW : RecordsFlushing.LATER()); try { es.getRecordServices().execute(transaction); } catch (RecordServicesException e) { throw new RuntimeException(e); } } private synchronized void addFieldDeclarations(String connectorId, Map<String, ConnectorField> fieldDeclarations) { Map<String, ConnectorField> connectorFields = fieldsDeclarationPerConnectorId.get(connectorId); if (connectorFields == null) { connectorFields = new HashMap<>(); this.fieldsDeclarationPerConnectorId.put(connectorId, connectorFields); } for (ConnectorField connectorField : fieldDeclarations.values()) { if (!connectorFields.containsKey(connectorField.getId())) { connectorFields.put(connectorField.getId(), connectorField); } } } @Override public void push(List<ConnectorDocument> documents) { List<Record> documentRecords = new ArrayList<>(); for (ConnectorDocument document : documents) { Map<String, ConnectorField> fieldDeclarations = applyMappedPropertiesToMetadata(document); addFieldDeclarations(document.getConnector(), fieldDeclarations); documentRecords.add(document.getWrappedRecord()); } boolean flushNow = false; for (Record record : documentRecords) { if (flushNow || es.getModelLayerFactory().getRecordsCaches().getCache(record.getCollection()) .getCacheConfigOf(record.getSchemaCode()) != null) flushNow = true; } List<Record> records = new RecordUtils().unwrap(documents); handler.append(records); } Map<String, ConnectorField> applyMappedPropertiesToMetadata(ConnectorDocument<?> document) { ConnectorInstance instance = es.getConnectorInstance(document.getConnector()); String connectorDocumentSchemaType = new SchemaUtils().getSchemaTypeCode(document.getSchemaCode()); Map<String, List<String>> mapping = mappingService.getMapping(instance, connectorDocumentSchemaType); for (Map.Entry<String, List<String>> entry : mapping.entrySet()) { List<Object> values = new ArrayList<>(); for (String field : entry.getValue()) { String id = field.split(":")[1]; Object fieldValues = document.getProperties().get(id); if (fieldValues != null) { if (fieldValues instanceof List) { values.addAll((List) fieldValues); } else { values.add(fieldValues); } } } document.set(entry.getKey(), values); } Map<String, ConnectorField> fields = new HashMap<>(document.getFieldsDeclarations()); document.clearProperties(); return fields; } @Override public void deleteEvents(ConnectorDocument... documents) { deleteEvents(new DeleteEventOptions(), asList(documents)); } @Override public void deleteEvents(DeleteEventOptions options, ConnectorDocument... documents) { deleteEvents(options, asList(documents)); } @Override public void close() { handler.closeAndJoin(); saveNewDeclaredFields(); } private void saveNewDeclaredFields() { Transaction modifiedConnectorInstancesTransaction = new Transaction(); for (Map.Entry<String, Map<String, ConnectorField>> entry : fieldsDeclarationPerConnectorId.entrySet()) { ConnectorInstance<?> instance = es.getConnectorInstance(entry.getKey()); Map<String, ConnectorField> declaredFields = entry.getValue(); List<ConnectorField> connectorFields = new ArrayList<>(instance.getAvailableFields()); boolean newField = false; for (ConnectorField connectorField : declaredFields.values()) { if (!hasDeclaredFieldsWithCode(connectorFields, connectorField.getId())) { connectorFields.add(connectorField); newField = true; } } if (newField) { instance.setAvailableFields(connectorFields); modifiedConnectorInstancesTransaction.add(instance); } } try { es.getModelLayerFactory().newRecordServices().execute(modifiedConnectorInstancesTransaction); } catch (RecordServicesException e) { throw new RuntimeException(e); } fieldsDeclarationPerConnectorId.clear(); } @Override public void deleteEvents(List<ConnectorDocument> documents) { this.deleteEvents(new DeleteEventOptions(), documents); } @Override public void deleteEvents(DeleteEventOptions options, List<ConnectorDocument> documents) { for (ConnectorDocument document : documents) { try { es.getRecordServices().logicallyDelete(document.getWrappedRecord(), User.GOD, options.logicalDeleteOptions); es.getRecordServices().physicallyDelete(document.getWrappedRecord(), User.GOD, options.physicalDeleteOptions); } catch (RecordServicesRuntimeException e) { String title = "Cannot delete document '" + document.getWrappedRecord().getIdTitle() + "'"; String description = ConnectorsUtils.getStackTrace(e); connectorLogger.error(title, description, new HashMap<String, String>()); } } } private boolean hasDeclaredFieldsWithCode(List<ConnectorField> availableFields, String id) { for (ConnectorField field : availableFields) { if (id != null && id.equals(field.getId())) { return true; } } return false; } @Override public void flush() { handler.pushCurrent(); handler.barrier(); es.getRecordServices().flush(); saveNewDeclaredFields(); } @Override public void cleanup() { handler.resetException(); } }