<?php
/**
* Pimcore
*
* This source file is available under following license:
* - Pimcore Commercial License (PCL)
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license PCL
*/
namespace Pimcore\Bundle\DataHubSimpleRestBundle\Service;
use Pimcore\Bundle\DataHubSimpleRestBundle\MappingAndDataExtractor\AbstractMappingAndDataExtractor;
use Pimcore\Bundle\DataHubSimpleRestBundle\MappingAndDataExtractor\DataExtractorFactory;
class IndexService extends AbstractService
{
const LOCK_NAME_PREFIX = 'datahub-simplerest-indexservice-';
/**
* @param string $configName
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
protected function checkAndCreateIndexAlias(string $configName)
{
foreach ($this->getDataExtractorsForConfig($configName) as $type => $mappingAndDataExtractor) {
$this->indexHandler->checkAndCreateIndexAlias($mappingAndDataExtractor);
}
}
/**
* @param string|null $configName
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function createOrUpdateMapping(string $configName = null)
{
foreach ($this->getConfigsToProcess($configName) as $configName) {
$lockname = self::LOCK_NAME_PREFIX . $configName;
$lock = $this->lockFactory->createLock($lockname);
if (!$lock->acquire()) {
throw new \Exception("IndexService for config '$configName' already locked.");
}
try {
$usedIndices = [];
$this->checkAndCreateIndexAlias($configName);
foreach ($this->getDataExtractorsForConfig($configName) as $type => $mappingAndDataExtractor) {
$this->indexHandler->createOrUpdateMapping($mappingAndDataExtractor);
$usedIndices[] = $mappingAndDataExtractor->getIndexName();
}
$this->indexHandler->cleanupUnusedEsIndices($this->indexNamePrefix . '_' . $configName, $usedIndices);
} finally {
$lock->release();
}
}
}
/**
* @param string $configName
*
* @return array
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function getLabelKeysFromIndex(string $configName): array
{
$allAttributes = [];
foreach ($this->getDataExtractorsForConfig($configName) as $type => $mappingAndDataExtractor) {
$allAttributes = array_merge(
$allAttributes,
$this->indexHandler->getLabelKeysFromIndex($mappingAndDataExtractor->getIndexName(), $mappingAndDataExtractor->getLabelBlackList())
);
}
return array_unique($allAttributes);
}
/**
* @param string $configName
*/
public function cleanupIndicesForConfig(string $configName)
{
$this->indexHandler->cleanupUnusedEsIndices($this->indexNamePrefix . '_' . $configName, []);
}
/**
* @param $elementId
* @param string $entityType
* @param string|null $configName
*
* @return bool
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function doIndexData($elementId, string $entityType, string $configName = null): bool
{
$originalConfigName = $configName;
foreach ($this->getConfigsToProcess($configName) as $configName) {
$mappingAndDataExtractor = $this->getDataExtractorForConfigAndEntityType($configName, $entityType);
if ($mappingAndDataExtractor) {
$data = $mappingAndDataExtractor->extractData($elementId);
$dataExtractorCollection = $this->getDataExtractorCollectionForConfigAndType($configName, $entityType);
$possibleElementIndices = [];
foreach ($dataExtractorCollection as $dataExtractor) {
$possibleElementIndices[] = $dataExtractor->getIndexName();
}
//get existing item from index - theoretical max count is number of indices
$indexElements = $this->indexHandler->queryIndexById($possibleElementIndices, $elementId, count($possibleElementIndices));
//add old parent element to queue - to update or delete it
if ($indexElements['total_count'] > 0) {
foreach ($indexElements['items'] as $indexElement) {
$parentIndexElement = $this->indexHandler->queryIndexById($possibleElementIndices, $indexElement['system']['parentId']);
if ($parentIndexElement) {
$this->addItemToQueue($parentIndexElement['system']['id'], $parentIndexElement['system'][AbstractMappingAndDataExtractor::INTERNAL_ENTITY_TYPE], $originalConfigName == null ? null : $configName);
}
}
}
if (empty($data)) {
if ($indexElements['total_count'] > 0) {
// if there is no child element, delete
// if there is one, it might be a virtual parent folder that must not be deleted - except if there is more than one element with same ID in index (might be left over of virtual parents)
if (!$this->indexHandler->hasChildElementInIndex($possibleElementIndices, $elementId) || $indexElements['total_count'] > 1) {
$this->indexHandler->deleteItem($mappingAndDataExtractor->getIndexName(), $elementId);
}
}
} else {
if ($entityType === DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET || $entityType == DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET_FOLDER) {
//parents are in asset folder
$folderMappingExtractor = $this->getDataExtractorForConfigAndEntityType($configName, DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET_FOLDER);
} else {
//parents are in object folder
$folderMappingExtractor = $this->getDataExtractorForConfigAndEntityType($configName, DataExtractorFactory::DATA_EXTRACTOR_TYPE_OBJECT_FOLDER);
}
//check if element is in folder index and if entity type is not folder -> delete from index to avoid duplicate entries
foreach ($indexElements['items'] as $indexElement) {
if (
!in_array($entityType, [DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET_FOLDER, DataExtractorFactory::DATA_EXTRACTOR_TYPE_OBJECT_FOLDER]) &&
$indexElement && in_array($indexElement['system'][AbstractMappingAndDataExtractor::INTERNAL_ENTITY_TYPE], [DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET_FOLDER, DataExtractorFactory::DATA_EXTRACTOR_TYPE_OBJECT_FOLDER])
) {
$this->indexHandler->deleteItem($folderMappingExtractor->getIndexName(), $elementId);
}
}
//add new element
$this->indexHandler->indexItem($mappingAndDataExtractor->getIndexName(), $elementId, $data);
//check if parent element is in any index of config ... if not add a virutal folder to folder index
$virtualParentArray = $mappingAndDataExtractor->getVirtualParentArray($elementId);
if ($virtualParentArray) {
//check if new parent(s) are already in index - add or update them
foreach ($virtualParentArray as $virtualParent) {
if ($newParentIndexElement = $this->indexHandler->queryIndexById($possibleElementIndices, $virtualParent['id'])) {
$this->addItemToQueue($newParentIndexElement['system']['id'], $newParentIndexElement['system'][AbstractMappingAndDataExtractor::INTERNAL_ENTITY_TYPE], $originalConfigName == null ? null : $configName);
break;
} else {
$this->indexHandler->indexItem($folderMappingExtractor->getIndexName(), $virtualParent['id'], $virtualParent['data']);
}
}
}
}
}
}
return true;
}
/**
* @param $elementId
* @param string $entityType
* @param string|null $configName
*/
public function addItemToQueue($elementId, string $entityType, string $configName = null)
{
$this->queueService->addItemToQueue($elementId, $entityType, $configName);
}
/**
* @param int $limit
*
* @return array
*/
public function getAllQueueEntries($limit = 100000): array
{
return $this->queueService->getAllQueueEntries($limit);
}
/**
* @param $elementId
* @param $entityType
* @param $configName
*
* @return bool
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function processQueueEntry($elementId, $entityType, $configName): bool
{
$success = $this->doIndexData($elementId, $entityType, $configName);
if ($success) {
$this->queueService->markQueueEntryAsProcessed($elementId, $entityType, $configName);
}
return $success;
}
/**
* @param string|null $configName
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function initIndex(string $configName = null)
{
$originalConfigName = $configName;
$this->invalidateIndexAll($configName);
foreach ($this->getConfigsToProcess($configName) as $configName) {
foreach ($this->getDataExtractorsForConfig($configName) as $type => $mappingAndDataExtractor) {
//do not init folders as they are initialized implicitly with parents that are added
//so we avoid empty folders in index
if ($type !== DataExtractorFactory::DATA_EXTRACTOR_TYPE_OBJECT_FOLDER && $type !== DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET_FOLDER) {
$ids = $mappingAndDataExtractor->calculateAllElementIds();
foreach ($ids as $id) {
$this->queueService->addItemToQueue($id, $type, $originalConfigName == null ? null : $configName);
}
}
}
}
}
public function invalidateDuplicateIndexEntries(string $configName = null)
{
$originalConfigName = $configName;
foreach ($this->getConfigsToProcess($configName) as $configName) {
//for assets
$dataExtractorCollection = $this->getDataExtractorCollectionForConfigAndType($configName, DataExtractorFactory::DATA_EXTRACTOR_TYPE_ASSET);
if ($dataExtractorCollection) {
$indexNames = [];
foreach ($dataExtractorCollection as $dataExtractor) {
$indexNames[] = $dataExtractor->getIndexName();
}
$duplicateEntries = $this->indexHandler->findDuplicateElements($indexNames);
foreach ($duplicateEntries as $entry) {
$this->queueService->addItemToQueue($entry['id'], $entry[AbstractMappingAndDataExtractor::INTERNAL_ENTITY_TYPE], $originalConfigName == null ? null : $configName);
}
}
//for objects
$dataExtractorCollection = $this->getDataExtractorCollectionForConfigAndType($configName, DataExtractorFactory::DATA_EXTRACTOR_TYPE_OBJECT_FOLDER);
if ($dataExtractorCollection) {
$indexNames = [];
foreach ($dataExtractorCollection as $dataExtractor) {
$indexNames[] = $dataExtractor->getIndexName();
}
$duplicateEntries = $this->indexHandler->findDuplicateElements($indexNames);
foreach ($duplicateEntries as $entry) {
$this->queueService->addItemToQueue($entry['id'], $entry[AbstractMappingAndDataExtractor::INTERNAL_ENTITY_TYPE], $originalConfigName == null ? null : $configName);
}
}
}
}
/**
* @param string|null $configName
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function invalidateIndexAll(string $configName = null)
{
$originalConfigName = $configName;
foreach ($this->getConfigsToProcess($configName) as $configName) {
foreach ($this->getDataExtractorsForConfig($configName) as $type => $mappingAndDataExtractor) {
$allIds = $this->indexHandler->getAllIdsFromIndex($mappingAndDataExtractor->getIndexName());
foreach ($allIds as $id) {
$this->addItemToQueue($id, $type, $originalConfigName == null ? null : $configName);
}
}
}
}
/**
* @param string $path
* @param string $entityType
* @param string|null $configName
*
* @throws \Pimcore\Bundle\DataHubSimpleRestBundle\Exception\InvalidRequestException
*/
public function invalidateIndexByOriginalPath(string $path, string $entityType, string $configName = null)
{
$originalConfigName = $configName;
foreach ($this->getConfigsToProcess($configName) as $configName) {
foreach ($this->getDataExtractorCollectionForConfigAndType($configName, $entityType) as $type => $mappingAndDataExtractor) {
$allIds = $this->indexHandler->getAllIdsFromIndexByOriginalPath($mappingAndDataExtractor->getIndexName(), $path);
foreach ($allIds as $id) {
$this->addItemToQueue($id, $type, $originalConfigName == null ? null : $configName);
}
}
}
}
}