Class SandboxTrackableSolrQueryContributor

All Implemented Interfaces:
SolrQueryContributor, org.springframework.core.Ordered

public class SandboxTrackableSolrQueryContributor extends AbstractCatalogTrackableSolrQueryContributor
Add query filters to narrow query results down to the most specific Sandbox context provided.
  • Constructor Details

    • SandboxTrackableSolrQueryContributor

      public SandboxTrackableSolrQueryContributor()
  • Method Details

    • contributeInternal

      protected void contributeInternal(org.apache.solr.client.solrj.SolrQuery solrQuery, com.broadleafcommerce.search.api.domain.SearchRequest searchRequest, List<com.broadleafcommerce.search.api.domain.FieldDefinition> fieldDefinitions, org.springframework.data.domain.Pageable page, com.broadleafcommerce.search.core.service.SearchRequestProperties properties, @Nullable String locale, @Nullable com.broadleafcommerce.data.tracking.core.context.ContextInfo context)
      Description copied from class: AbstractSolrQueryContributor
      Customize the provided solrQuery with some additional behavior.
      Specified by:
      contributeInternal in class AbstractSolrQueryContributor
      Parameters:
      solrQuery - The query to customize
      searchRequest - The search query request
      fieldDefinitions - The fields to perform the search on
      page - Page information for page to retrieve for query
      properties - Additional properties for this query
      locale - The locale of this search
      context - The context information surrounding sandboxing/multitenant state
    • filterOnUserSandbox

      protected String filterOnUserSandbox(com.broadleafcommerce.data.tracking.core.context.ContextInfo context)
      Create a filter for the current user's sandbox.
      Parameters:
      context - The context information surrounding sandboxing/multitenant state
      Returns:
      Filter for the current user's sandbox
    • filterOnOtherSandboxes

      protected String filterOnOtherSandboxes(com.broadleafcommerce.data.tracking.core.context.ContextInfo context)
      Create a filter for sandboxes other than the current user's sandbox.
      Parameters:
      context - The context information surrounding sandboxing/multitenant state
      Returns:
      Filter for sandboxes other than the current user's sandbox
    • filterOnProductionSandbox

      protected String filterOnProductionSandbox()
      Create a filter for the production sandbox.
      Returns:
      Filter for the production sandbox
    • filterOverriddenSandboxes

      protected void filterOverriddenSandboxes(org.apache.solr.client.solrj.SolrQuery solrQuery, com.broadleafcommerce.data.tracking.core.context.ContextInfo context)

      The other filters applied via filterOnUserSandbox(ContextInfo), filterOnOtherSandboxes(ContextInfo), and filterOnProductionSandbox() will return a list of multiple candidate records that may be relevant for the current sandbox request.

      However, the current user should only see the 'most-derived' representation of a record, so the filter applied in this method eliminates all but the most relevant record.

      Here is what the current user should be seeing, from highest to lowest precedence:

      • If there is a user-level record by the current user, return that.
      • If there is a higher-than-user-level record (ex: promoted) by any user, return that.
      • If there is a production-level record by any user, return that.

      During indexing, if we know a record has a more-derived change somewhere, we track it on SolrNarrowFields.SANDBOX_OVERRIDES. If there is a user-level record that is applying changes to the base record, the base record's SolrNarrowFields.SANDBOX_OVERRIDES will contain an element that looks like sandboxId,sandboxOwner (where sandboxOwner is the author of the user-level change). If there is a higher-than-user-level record that is applying changes to the base record, the base record's SolrNarrowFields.SANDBOX_OVERRIDES will contain an element that just looks like sandboxId.

      Thus, the filtering logic here should exclude any records where SolrNarrowFields.SANDBOX_OVERRIDES contains sandboxId, since that means there is a potentially visible higher-than-user-level record that should be returned instead. Furthermore, the filtering logic should also exclude any records where SolrNarrowFields.SANDBOX_OVERRIDES contains sandboxId,sandboxOwner where sandboxOwner is the current user, since that means there is a potentially visible user-level record by the current user that should be returned instead.

      Parameters:
      solrQuery - Query to execute in Solr
      context - The context information surrounding sandboxing/multitenant state
      See Also:
    • determinePlainSandboxOverrideFilterValues

      protected Set<String> determinePlainSandboxOverrideFilterValues(com.broadleafcommerce.data.tracking.core.context.ContextInfo context)
    • buildPlainSandboxOverridesFilter

      protected String buildPlainSandboxOverridesFilter(Set<String> plainSandboxOverrideValuesToFilter)
    • filterOverriddenSandboxesWithCatalogAwareness

      protected void filterOverriddenSandboxesWithCatalogAwareness(org.apache.solr.client.solrj.SolrQuery solrQuery, com.broadleafcommerce.data.tracking.core.context.ContextInfo context)

      Similar to filterOverriddenSandboxes(SolrQuery, ContextInfo), but for catalog-discriminated entities.

      When an entity is not catalog discriminated, it is a safe assumption that we can filter out a record purely based on SolrNarrowFields.SANDBOX_OVERRIDES, because we have confidence that the override itself will be visible within the current context filtration constraints and therefore will appear in the results.

      However, when an entity is catalog discriminated, it's possible that the sandbox override of this record is not visible from the current catalog context. In Broadleaf, we only allow child catalogs to inherit production records: sandbox changes in parent catalogs are excluded. Thus, it is possible that within a particular context, the 'sandbox override' is not actually visible and the base record should be the one that is returned.

      For example, assume there is a production recordA in parentCatalog. Now assume there is also a promoted sandbox change to recordA in parentCatalog and sandboxX. If you submit a search in childCatalog and sandboxX, you expect to see production recordA from the parentCatalog.

      The only catalogs from which we should allow sandbox changes are the 'most derived' catalogs in our current context. In other words, for tenant-level requests, or application requests with ContextRequest.isForceFilterByCatalogIncludeInheritance(), we only expect to see sandbox results from ContextRequest.getCatalogId() and none of its ancestors. For application requests that don't have any forced-catalog settings, we only expect to see sandbox results from all the direct Application.getIsolatedCatalogs() (the implicit ones only), and none of their ancestors.

      This is why we must leverage SolrNarrowFields.CATALOG_AWARE_SANDBOX_OVERRIDES instead, which will allow us to only consider sandbox overrides visible to the current catalog context.

      A special consideration is still required for marketplace catalogs, since our narrowing process for marketplace is different from our normal catalog narrowing. See buildMarketplaceAndCatalogAwareSandboxOverridesFilter(Set, Set, Set) for more details.

      Parameters:
      solrQuery - The query to customize
      context - context information around sandboxing and multitenant state
    • buildCatalogAwareSandboxOverridesFilter

      @Nullable protected String buildCatalogAwareSandboxOverridesFilter(Set<String> catalogIdsToFilterBy, Set<String> plainSandboxOverrideValuesToFilter)
    • generateCatalogAwareSandboxOverrides

      protected Stream<String> generateCatalogAwareSandboxOverrides(Set<String> plainSandboxOverrideValues, String catalogId)
    • buildMarketplaceAndCatalogAwareSandboxOverridesFilter

      protected String buildMarketplaceAndCatalogAwareSandboxOverridesFilter(Set<String> directImplicitIsolatedCatalogIdsForApp, Set<String> allIsolatedCatalogIdsForApp, Set<String> plainSandboxOverrideValuesToFilter)

      Should only be used if the request is in the context of a marketplace application. Narrowing against marketplace catalogs is done differently than normal catalogs due to the fact that a single marketplace may contain thousands of marketplace application catalogs.

      For the purposes of this filter, it's infeasible to enumerate all of those catalogs. However, we can get by with a few assumptions about the structure of marketplace catalogs.

      Most importantly, we don't expect any 'inheritance' for marketplace catalogs. For each assigned catalog, there is just the base tenant-level vendor catalog + the implicit application-level vendor-marketplace catalog. That's the only two catalogs we expect to get data from in a particular hierarchy. There can be no further children.

      1. Notably, because the vendor catalog is at the tenant level and the implicit catalog is at the application level, they will never have changes in the same sandbox. The vendor catalog will have changes in tenant-level sandboxes and the implicit catalog will only have changes in application-level sandboxes.

      2. At the application level, the caller will be in an application-level sandbox. Here, we will only inherit tenant-level records that are in production - this is actually handled implicitly be the fact that they are in the application-level sandbox instead of in the tenant-level sandbox (and therefore can never see tenant level sandbox changes). Next, for application-level overrides, the production tenant record will have an entry in SolrNarrowFields.SANDBOX_OVERRIDES for the current app-specific sandbox. We do not expect this app-level override to itself be overridden anywhere deeper due to the limited inheritance structure of marketplace catalogs. So we can reliably assume that this override will be visible in the caller's context.

      Putting this all together, we can conclude that for marketplace catalog items, we support an alternate filtration based on the original SolrNarrowFields.SANDBOX_OVERRIDES field.

      Parameters:
      directImplicitIsolatedCatalogIdsForApp - the directly assigned isolated implicit catalog ids for the application from Application.getIsolatedCatalogs()
      allIsolatedCatalogIdsForApp - all visible assigned isolated catalog IDs for the application, including inherited catalogs
      plainSandboxOverrideValuesToFilter - the vanilla sandbox override values to consider during filtration
      Returns:
      a solr filter that can be used to filter sandbox overrides with marketplace and catalog awareness
    • shouldContribute

      protected boolean shouldContribute(org.apache.solr.client.solrj.SolrQuery solrQuery, com.broadleafcommerce.search.api.domain.SearchRequest searchRequest, org.springframework.data.domain.Pageable page, com.broadleafcommerce.search.core.service.SearchRequestProperties properties, @Nullable com.broadleafcommerce.data.tracking.core.context.ContextInfo context)
      Description copied from class: AbstractSolrQueryContributor
      Determines if this contributor should run.
      Specified by:
      shouldContribute in class AbstractSolrQueryContributor
      Parameters:
      solrQuery - The query to customize
      searchRequest - The search query request
      page - Page information for page to retrieve for query
      properties - Additional properties for this query.
      context - The context information surrounding sandboxing/multitenant state
      Returns:
      whether this contributor should run
    • contributeOnTypeAhead

      public boolean contributeOnTypeAhead()
      Returns:
      Should this contributor be executed on TypeAhead requests?