Skip to content

Commit

Permalink
WIP [HTM-1076][HTM-1077] TMFeatureType event handler to propagate cha…
Browse files Browse the repository at this point in the history
…nges to solr indexes

fix PMD style

fixup after API changes in #876

move testcases and remove now useless test, use solr service inject

fix rebase issues
  • Loading branch information
mprins committed Oct 22, 2024
1 parent 71b0c74 commit b1f0be3
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public class SolrAdminController {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final FeatureSourceFactoryHelper featureSourceFactoryHelper;

private final FeatureTypeRepository featureTypeRepository;
private final SearchIndexRepository searchIndexRepository;
private final SolrService solrService;
Expand All @@ -65,7 +64,7 @@ public SolrAdminController(
/**
* Ping solr.
*
* @return the response entity (ok or an error response)
* @return the response entity (OK or an error response)
*/
@Operation(summary = "Ping Solr", description = "Ping Solr to check if it is available")
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
*/
package org.tailormap.api.repository;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.NonNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.tailormap.api.persistence.Application;
Expand All @@ -21,4 +24,17 @@ public interface ApplicationRepository extends JpaRepository<Application, Long>
@Override
@NonNull
Optional<Application> findById(@NonNull Long aLong);

/**
* Find all applications that have a layer that is linked to a specific (Solr) index.
*
* @param indexId The index id to search for
*/
@NonNull
@PreAuthorize("permitAll()")
@Query(
value =
"select * from application app, lateral jsonb_path_query(app.settings, ('$.layerSettings.**{1}.searchIndexId ? (@ == '||:indexId||')')::jsonpath)",
nativeQuery = true)
List<Application> findByIndexId(@Param("indexId") @NonNull Long indexId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2024 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.repository.events;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.rest.core.annotation.HandleAfterDelete;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.tailormap.api.geotools.featuresources.FeatureSourceFactoryHelper;
import org.tailormap.api.persistence.SearchIndex;
import org.tailormap.api.persistence.TMFeatureType;
import org.tailormap.api.persistence.json.AppLayerSettings;
import org.tailormap.api.repository.ApplicationRepository;
import org.tailormap.api.repository.SearchIndexRepository;
import org.tailormap.api.solr.SolrHelper;
import org.tailormap.api.solr.SolrService;

/** Event handler for Solr indexes when a {@code TMFeatureType} is updated or deleted. */
@RepositoryEventHandler
public class SolrTMFeatureTypeEventHandler {

private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final SearchIndexRepository searchIndexRepository;
private final SolrService solrService;
private final FeatureSourceFactoryHelper featureSourceFactoryHelper;
private final ApplicationRepository applicationRepository;

public SolrTMFeatureTypeEventHandler(
SearchIndexRepository searchIndexRepository,
SolrService solrService,
FeatureSourceFactoryHelper featureSourceFactoryHelper,
ApplicationRepository applicationRepository) {
this.searchIndexRepository = searchIndexRepository;
this.solrService = solrService;
this.featureSourceFactoryHelper = featureSourceFactoryHelper;
this.applicationRepository = applicationRepository;
}

/**
* Handle the update of a TMFeatureType.
*
* @param tmFeatureType the TMFeatureType to handle
*/
@HandleBeforeSave
public void handleTMFeatureTypeUpdate(TMFeatureType tmFeatureType) {
logger.debug("Handling TMFeatureType save event for: {}", tmFeatureType);
// determine if it is a new FT or an update
if (null == tmFeatureType.getId()) {
// do nothing as there is no index defined for a new feature type
logger.debug("New TMFeatureType: {}", tmFeatureType);
} else {
logger.debug("Updated TMFeatureType: {}", tmFeatureType);
searchIndexRepository.findByFeatureTypeId(tmFeatureType.getId()).stream()
.findAny()
.ifPresent(
searchIndex -> {
logger.debug(
"Updating search index {} for feature type: {}",
searchIndex.getName(),
searchIndex);

try (SolrHelper solrHelper =
new SolrHelper(solrService.getSolrClientForIndexing())) {
solrHelper.addFeatureTypeIndex(
searchIndex, tmFeatureType, featureSourceFactoryHelper);
} catch (UnsupportedOperationException
| IOException
| SolrServerException
| SolrException e) {
logger.error("Error re-indexing", e);
searchIndex.setStatus(SearchIndex.Status.ERROR);
searchIndexRepository.save(searchIndex);
}
});
}
}

/**
* Handle the deletion of a TMFeatureType.
*
* @param tmFeatureType the TMFeatureType to handle
*/
@HandleAfterDelete
public void handleTMFeatureTypeDeleteForSolr(TMFeatureType tmFeatureType) {
logger.debug("Handling TMFeatureType delete event for: {}", tmFeatureType);
searchIndexRepository.findByFeatureTypeId(tmFeatureType.getId()).stream()
.findAny()
.ifPresent(
searchIndex -> {
logger.info(
"Deleting search index {} for feature type: {}",
searchIndex.getName(),
searchIndex);

try (SolrHelper solrHelper = new SolrHelper(solrService.getSolrClientForIndexing())) {
solrHelper.clearIndexForLayer(searchIndex.getId());
searchIndexRepository.delete(searchIndex);
// find any application layers that use this index clear the index from them
applicationRepository
.findByIndexId(searchIndex.getId())
.forEach(
application -> {
application
.getAllAppTreeLayerNode()
.forEach(
appTreeLayerNode -> {
AppLayerSettings appLayerSettings =
application.getAppLayerSettings(appTreeLayerNode);
if (null != appLayerSettings.getSearchIndexId()
&& appLayerSettings
.getSearchIndexId()
.equals(searchIndex.getId())) {
appLayerSettings.setSearchIndexId(null);
}
});
applicationRepository.save(application);
});
} catch (UnsupportedOperationException
| IOException
| SolrServerException
| SolrException e) {
logger.error("Error deleting index for {}", searchIndex, e);
}
});
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/tailormap/api/solr/SolrHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class SolrHelper implements AutoCloseable, Constants {
/**
* Constructor
*
* @param solrClient the Solr client, this will be closed when this class is closed
* @param solrClient the Solr client, this client will be closed when this class is closed.
*/
public SolrHelper(@NotNull SolrClient solrClient) {
this.solrClient = solrClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import static org.hibernate.validator.internal.util.Contracts.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.tailormap.api.annotation.PostgresIntegrationTest;
Expand All @@ -30,4 +32,17 @@ void should_not_find_application_by_nonexistent_name() {
Application a = applicationRepository.findByName("does-not-exist");
assertNull(a);
}

@Test
void it_should_find_application_using_findByIndexId_with_valid_ID() {
final Application application = applicationRepository.findByIndexId(2L).get(0);
assertNotNull(application, "application should not be null");
assertEquals("default", application.getName(), "application name is incorrect");
}

@Test
void it_should_not_find_applications_findByIndexId_with_invalid_ID() {
final List<Application> applications = applicationRepository.findByIndexId(-2L);
assertTrue(applications.isEmpty(), "applications should be empty");
}
}

0 comments on commit b1f0be3

Please sign in to comment.