Skip to main content

BEYABLE Product Ranking API - SAP Commerce Cloud Integration Guide

Overview​

This guide provides step-by-step instructions for integrating the BEYABLE Product Ranking API with SAP Commerce Cloud (formerly Hybris). The integration enables AI-powered product ranking to optimize your product listings across category pages, search results, and recommendation slots.

Table of Contents​

  1. Prerequisites
  2. Architecture Overview
  3. Extension Structure
  4. Installation
  5. Configuration
  6. Implementation
  7. CronJob Configuration
  8. Frontend Integration
  9. Testing
  10. Deployment
  11. Monitoring & Troubleshooting
  12. Performance Optimization

Prerequisites​

System Requirements​

  • SAP Commerce Cloud 1905 or higher (Hybris 6.x also supported with modifications)
  • Administrator access to Backoffice and HAC (Hybris Administration Console)
  • Java 11+ (Java 8 for older versions)
  • Apache Maven 3.6+
  • Knowledge of Spring Framework and Hybris platform

Required Information from BEYABLE​

Obtain the following from your BEYABLE account manager:

  • Account ID: Your 32-character UUID (without hyphens)
  • Subscription Key: Your API authentication key
  • Ranking Rule ID(s): UUID for each ranking rule
  • Segmented Ranking ID(s): UUID for segmented rankings (if applicable)
  • Site/Locale Mapping: Tenant configuration for your SAP Commerce sites

SAP Commerce Knowledge Required​

  • Understanding of SAP Commerce extensions and build process
  • Experience with Backoffice configuration
  • Familiarity with Flexible Search and models
  • Knowledge of CronJobs and service layer
  • Understanding of Solr search configuration

Architecture Overview​

┌─────────────────────────────────────────────────────────────────┐
│ BEYABLE Platform │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Ranking Rules & │ │ Product Ranking API │ │
│ │ Segment Config │───────>│ api.eu1.beyable.com │ │
│ └──────────────────┘ └──────────────────────────────┘ │
└───────────────────────────────────────┬─────────────────────────┘
│ CSV Response
│ (ProductId, Position)
▼
┌─────────────────────────────────────────────────────────────────┐
│ SAP Commerce Cloud (Hybris) │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ CronJob: Sync │ │ Model: │ │
│ │ Rankings │───────>│ BeyableRankingModel │ │
│ │ (Every 2hrs) │ │ (Store rankings) │ │
│ └──────────────────┘ └──────────────────────────────┘ │
│ │ │
│ ┌──────────────────┐ │ │
│ │ Shopper visits │ │ │
│ │ Category/Search │ ▼ │
│ └────────┬─────────┘ ┌──────────────────────────────┐ │
│ │ │ Service/DAO: │ │
│ │ │ - Read segment cookie │ │
│ └─────────────────>│ - Apply rankings │ │
│ │ - Sort products │ │
│ └──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Optimized Product Listing │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Integration Flow:

  1. BEYABLE Cookie: BEYABLE Platform sets visitor segment cookie
  2. Scheduled CronJob: SAP Commerce job fetches rankings from BEYABLE API (every 1-4 hours)
  3. Data Storage: Rankings stored in SAP Commerce database via FlexibleSearch
  4. Runtime Application: Services read segment, apply rankings to product searches
  5. Frontend Display: Optimized product listings displayed to shoppers

Extension Structure​

beyableranking/
├── acceleratoraddon/
│ └── web/
│ ├── src/
│ │ └── com/beyable/ranking/addon/
│ │ └── controllers/
│ │ └── BeyableRankingPageController.java
│ └── webroot/
│ ├── WEB-INF/
│ │ ├── tags/
│ │ │ └── beyable/
│ │ │ └── tracking.tag
│ │ └── views/
│ │ └── responsive/
│ │ └── pages/
│ └── _ui/
│ └── responsive/
│ └── common/
│ └── js/
│ └── beyable-tracking.js
├── resources/
│ ├── beyableranking-items.xml
│ ├── beyableranking-spring.xml
│ ├── beyableranking-beans.xml
│ └── localization/
│ └── beyableranking-locales_en.properties
├── src/
│ └── com/beyable/ranking/
│ ├── constants/
│ │ └── BeyablerankingConstants.java
│ ├── dao/
│ │ ├── BeyableRankingDao.java
│ │ └── impl/
│ │ └── DefaultBeyableRankingDao.java
│ ├── service/
│ │ ├── BeyableRankingService.java
│ │ ├── BeyableApiService.java
│ │ └── impl/
│ │ ├── DefaultBeyableRankingService.java
│ │ └── DefaultBeyableApiService.java
│ ├── cronjob/
│ │ └── BeyableRankingSyncJob.java
│ ├── populators/
│ │ └── BeyableProductSearchPagePopulator.java
│ ├── strategies/
│ │ └── BeyableProductSortStrategy.java
│ └── facade/
│ └── impl/
│ └── BeyableProductSearchFacade.java
├── testsrc/
│ └── com/beyable/ranking/
│ └── service/
│ └── BeyableRankingServiceTest.java
├── extensioninfo.xml
├── external-dependencies.xml
├── project.properties
└── buildcallbacks.xml

Installation​

Step 1: Create Extension​

Create the extension directory structure:

cd hybris/bin/custom
mkdir -p beyableranking
cd beyableranking

Step 2: Extension Info​

Create extensioninfo.xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<extensioninfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="extensioninfo.xsd">
<extension abstractclassprefix="Generated"
classprefix="Beyableranking"
name="beyableranking"
usemaven="true">
<requires-extension name="commerceservices"/>
<requires-extension name="commercefacades"/>
<requires-extension name="acceleratorstorefrontcommons"/>
<requires-extension name="basecommerce"/>
<requires-extension name="processing"/>

<coremodule generated="true"
manager="com.beyable.ranking.jalo.BeyablerankingManager"
packageroot="com.beyable.ranking"/>

<webmodule jspcompile="false" webroot="/beyableranking"/>

<meta key="backoffice-module" value="true"/>
</extension>
</extensioninfo>

Step 3: Project Properties​

Create project.properties:

# Extension Information
beyableranking.key=beyableranking

# Build Configuration
beyableranking.global.dir=${ext.beyableranking.path}
beyableranking.build.dir=${ext.beyableranking.path}/classes
beyableranking.jar.file=${ext.beyableranking.path}/bin/beyablerankingserver.jar

# Dependencies
beyableranking.dependencies=commerceservices,commercefacades,acceleratorstorefrontcommons

Step 4: External Dependencies​

Create external-dependencies.xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<external-dependencies>
<extensions>
<!-- Apache HTTP Client for API calls -->
<extension name="httpcore" version="4.4.14"/>
<extension name="httpclient" version="4.5.13"/>

<!-- CSV Parsing -->
<extension name="commons-csv" version="1.9.0"/>

<!-- JSON Parsing (optional) -->
<extension name="gson" version="2.8.9"/>
</extensions>
</external-dependencies>

Step 5: Add to Local Extensions​

Edit hybris/config/localextensions.xml:

<hybrisconfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:noNamespaceSchemaLocation="resources/schemas/extensions.xsd">
<extensions>
<!-- Other extensions... -->
<extension name="beyableranking" />
</extensions>
</hybrisconfig>

Configuration​

Step 1: Items Definition​

Create resources/beyableranking-items.xml:

<?xml version="1.0" encoding="UTF-8"?>
<items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="items.xsd">

<atomictypes>
<atomictype class="java.lang.String" autocreate="true" generate="false"/>
<atomictype class="java.lang.Integer" autocreate="true" generate="false"/>
<atomictype class="java.util.Date" autocreate="true" generate="false"/>
</atomictypes>

<itemtypes>
<!-- BEYABLE Ranking Item Type -->
<itemtype code="BeyableRanking"
extends="GenericItem"
autocreate="true"
generate="true"
jaloclass="com.beyable.ranking.jalo.BeyableRanking">

<deployment table="beyableranking" typecode="10500"/>

<attributes>
<!-- Product Reference -->
<attribute qualifier="product" type="Product">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="false"/>
</attribute>

<!-- Ranking Position -->
<attribute qualifier="position" type="java.lang.Integer">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="false"/>
</attribute>

<!-- A/B Test Group -->
<attribute qualifier="abTestGroup" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="true"/>
</attribute>

<!-- Visitor Segment -->
<attribute qualifier="segmentId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="true"/>
</attribute>

<!-- Rule ID -->
<attribute qualifier="ruleId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="false"/>
</attribute>

<!-- Base Site -->
<attribute qualifier="baseSite" type="BaseSite">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="false"/>
</attribute>

<!-- Last Sync Date -->
<attribute qualifier="lastSyncDate" type="java.util.Date">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="true"/>
</attribute>
</attributes>

<indexes>
<index name="productRankingIdx">
<key attribute="product"/>
<key attribute="baseSite"/>
<key attribute="segmentId"/>
</index>
<index name="ruleSegmentIdx">
<key attribute="ruleId"/>
<key attribute="segmentId"/>
<key attribute="baseSite"/>
</index>
<index name="positionIdx">
<key attribute="position"/>
</index>
</indexes>
</itemtype>

<!-- BEYABLE Configuration Item Type -->
<itemtype code="BeyableConfig"
extends="GenericItem"
autocreate="true"
generate="true"
jaloclass="com.beyable.ranking.jalo.BeyableConfig">

<deployment table="beyableconfig" typecode="10501"/>

<attributes>
<!-- Base Site Reference -->
<attribute qualifier="baseSite" type="BaseSite">
<persistence type="property"/>
<modifiers read="true" write="true" search="true" optional="false" unique="true"/>
</attribute>

<!-- API Configuration -->
<attribute qualifier="accountId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
</attribute>

<attribute qualifier="subscriptionKey" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false" encrypted="true"/>
</attribute>

<attribute qualifier="apiBaseUrl" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="true"/>
<defaultvalue>"https://api.eu1.beyable.com"</defaultvalue>
</attribute>

<!-- Ranking Rules -->
<attribute qualifier="defaultRankingId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
</attribute>

<attribute qualifier="segmentedRankingId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="true"/>
</attribute>

<attribute qualifier="tenantId" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="true"/>
</attribute>

<!-- Feature Flags -->
<attribute qualifier="enabled" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.FALSE</defaultvalue>
</attribute>

<attribute qualifier="useSegmentation" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.FALSE</defaultvalue>
</attribute>

<attribute qualifier="applyToCategories" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.TRUE</defaultvalue>
</attribute>

<attribute qualifier="applyToSearch" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.TRUE</defaultvalue>
</attribute>

<!-- Cookie Configuration -->
<attribute qualifier="segmentCookieName" type="java.lang.String">
<persistence type="property"/>
<modifiers read="true" write="true" optional="true"/>
<defaultvalue>"b_segment"</defaultvalue>
</attribute>

<!-- Sync Configuration -->
<attribute qualifier="syncEnabled" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.TRUE</defaultvalue>
</attribute>

<attribute qualifier="apiTimeout" type="java.lang.Integer">
<persistence type="property"/>
<modifiers read="true" write="true" optional="true"/>
<defaultvalue>java.lang.Integer.valueOf(30000)</defaultvalue>
</attribute>

<!-- Debug -->
<attribute qualifier="debugLogging" type="java.lang.Boolean">
<persistence type="property"/>
<modifiers read="true" write="true" optional="false"/>
<defaultvalue>java.lang.Boolean.FALSE</defaultvalue>
</attribute>
</attributes>
</itemtype>
</itemtypes>
</items>

Step 2: Spring Configuration​

Create resources/beyableranking-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

<!-- Constants -->
<bean id="beyablerankingConstants"
class="com.beyable.ranking.constants.BeyablerankingConstants"/>

<!-- DAO Layer -->
<bean id="beyableRankingDao"
class="com.beyable.ranking.dao.impl.DefaultBeyableRankingDao">
<property name="flexibleSearchService" ref="flexibleSearchService"/>
<property name="modelService" ref="modelService"/>
</bean>

<!-- Service Layer -->
<bean id="beyableApiService"
class="com.beyable.ranking.service.impl.DefaultBeyableApiService">
<property name="configurationService" ref="configurationService"/>
</bean>

<bean id="beyableRankingService"
class="com.beyable.ranking.service.impl.DefaultBeyableRankingService">
<property name="beyableRankingDao" ref="beyableRankingDao"/>
<property name="beyableApiService" ref="beyableApiService"/>
<property name="modelService" ref="modelService"/>
<property name="baseSiteService" ref="baseSiteService"/>
<property name="productService" ref="productService"/>
</bean>

<!-- CronJob -->
<bean id="beyableRankingSyncJob"
class="com.beyable.ranking.cronjob.BeyableRankingSyncJob"
parent="abstractJobPerformable">
<property name="beyableRankingService" ref="beyableRankingService"/>
<property name="baseSiteService" ref="baseSiteService"/>
</bean>

<!-- Product Search Strategy -->
<bean id="beyableProductSortStrategy"
class="com.beyable.ranking.strategies.BeyableProductSortStrategy">
<property name="beyableRankingService" ref="beyableRankingService"/>
</bean>

<!-- HTTP Client Configuration -->
<bean id="beyableHttpClient"
class="org.apache.http.impl.client.HttpClients"
factory-method="createDefault"/>
</beans>

Step 3: Build the Extension​

cd hybris/bin/platform
. ./setantenv.sh
ant clean all

Step 4: Update System​

ant updatesystem

Or via HAC:

  1. Navigate to: Platform > Update
  2. Click Update button
  3. Wait for completion

Implementation​

Step 1: Constants​

Create src/com/beyable/ranking/constants/BeyablerankingConstants.java:

package com.beyable.ranking.constants;

public final class BeyablerankingConstants extends GeneratedBeyablerankingConstants
{
public static final String EXTENSIONNAME = "beyableranking";

// Cookie Names
public static final String DEFAULT_SEGMENT_COOKIE = "b_segment";

// API Endpoints
public static final String API_ENDPOINT_PRODUCT_RANKING = "/productranking/{accountId}/{rankingId}";
public static final String API_ENDPOINT_PRODUCT_RANKING_TENANT = "/productranking/{accountId}/{rankingId}/{tenant}";
public static final String API_ENDPOINT_SEGMENTED_RANKING = "/segmented-productranking/{accountId}/{rankingId}/{segmentId}";
public static final String API_ENDPOINT_SEGMENTED_RANKING_TENANT = "/segmented-productranking/{accountId}/{rankingId}/{segmentId}/{tenant}";

// HTTP Headers
public static final String HEADER_SUBSCRIPTION_KEY = "Subscription-Key";
public static final String HEADER_ACCEPT = "Accept";
public static final String HEADER_ACCEPT_CSV = "text/csv";

// Default Values
public static final String DEFAULT_API_BASE_URL = "https://api.eu1.beyable.com";
public static final int DEFAULT_API_TIMEOUT = 30000;

private BeyablerankingConstants()
{
// Private constructor to prevent instantiation
}
}

Step 2: DAO Layer​

Create src/com/beyable/ranking/dao/BeyableRankingDao.java:

package com.beyable.ranking.dao;

import com.beyable.ranking.model.BeyableRankingModel;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.model.product.ProductModel;

import java.util.List;

public interface BeyableRankingDao
{
/**
* Find ranking for a specific product
*/
BeyableRankingModel findRanking(ProductModel product, String ruleId, String segmentId, BaseSiteModel baseSite);

/**
* Find all rankings for a rule and segment
*/
List<BeyableRankingModel> findAllRankings(String ruleId, String segmentId, BaseSiteModel baseSite);

/**
* Clear all rankings for a rule and segment
*/
void clearRankings(String ruleId, String segmentId, BaseSiteModel baseSite);

/**
* Save ranking
*/
void saveRanking(BeyableRankingModel ranking);

/**
* Save rankings in batch
*/
void saveRankings(List<BeyableRankingModel> rankings);
}

Create src/com/beyable/ranking/dao/impl/DefaultBeyableRankingDao.java:

package com.beyable.ranking.dao.impl;

import com.beyable.ranking.dao.BeyableRankingDao;
import com.beyable.ranking.model.BeyableRankingModel;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import de.hybris.platform.servicelayer.search.SearchResult;
import org.apache.log4j.Logger;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DefaultBeyableRankingDao implements BeyableRankingDao
{
private static final Logger LOG = Logger.getLogger(DefaultBeyableRankingDao.class);

private FlexibleSearchService flexibleSearchService;
private ModelService modelService;

@Override
public BeyableRankingModel findRanking(ProductModel product, String ruleId, String segmentId, BaseSiteModel baseSite)
{
final StringBuilder query = new StringBuilder();
query.append("SELECT {pk} FROM {BeyableRanking} WHERE ");
query.append("{product} = ?product AND ");
query.append("{ruleId} = ?ruleId AND ");
query.append("{baseSite} = ?baseSite");

final Map<String, Object> params = new HashMap<>();
params.put("product", product);
params.put("ruleId", ruleId);
params.put("baseSite", baseSite);

if (segmentId != null)
{
query.append(" AND {segmentId} = ?segmentId");
params.put("segmentId", segmentId);
}
else
{
query.append(" AND {segmentId} IS NULL");
}

final FlexibleSearchQuery fsQuery = new FlexibleSearchQuery(query.toString());
fsQuery.addQueryParameters(params);

try
{
final SearchResult<BeyableRankingModel> result = flexibleSearchService.search(fsQuery);
return result.getResult().isEmpty() ? null : result.getResult().get(0);
}
catch (Exception e)
{
LOG.error("Error finding ranking: " + e.getMessage(), e);
return null;
}
}

@Override
public List<BeyableRankingModel> findAllRankings(String ruleId, String segmentId, BaseSiteModel baseSite)
{
final StringBuilder query = new StringBuilder();
query.append("SELECT {pk} FROM {BeyableRanking} WHERE ");
query.append("{ruleId} = ?ruleId AND ");
query.append("{baseSite} = ?baseSite");

final Map<String, Object> params = new HashMap<>();
params.put("ruleId", ruleId);
params.put("baseSite", baseSite);

if (segmentId != null)
{
query.append(" AND {segmentId} = ?segmentId");
params.put("segmentId", segmentId);
}
else
{
query.append(" AND {segmentId} IS NULL");
}

query.append(" ORDER BY {position} ASC");

final FlexibleSearchQuery fsQuery = new FlexibleSearchQuery(query.toString());
fsQuery.addQueryParameters(params);

try
{
final SearchResult<BeyableRankingModel> result = flexibleSearchService.search(fsQuery);
return result.getResult();
}
catch (Exception e)
{
LOG.error("Error finding rankings: " + e.getMessage(), e);
return List.of();
}
}

@Override
public void clearRankings(String ruleId, String segmentId, BaseSiteModel baseSite)
{
final List<BeyableRankingModel> rankings = findAllRankings(ruleId, segmentId, baseSite);

if (!rankings.isEmpty())
{
modelService.removeAll(rankings);
LOG.info(String.format("Cleared %d rankings for rule %s, segment %s",
rankings.size(), ruleId, segmentId));
}
}

@Override
public void saveRanking(BeyableRankingModel ranking)
{
modelService.save(ranking);
}

@Override
public void saveRankings(List<BeyableRankingModel> rankings)
{
modelService.saveAll(rankings);
}

// Getters and Setters
public void setFlexibleSearchService(FlexibleSearchService flexibleSearchService)
{
this.flexibleSearchService = flexibleSearchService;
}

public void setModelService(ModelService modelService)
{
this.modelService = modelService;
}
}

Step 3: API Service​

Create src/com/beyable/ranking/service/BeyableApiService.java:

package com.beyable.ranking.service;

import com.beyable.ranking.dto.BeyableRankingData;
import com.beyable.ranking.model.BeyableConfigModel;

import java.util.List;

public interface BeyableApiService
{
/**
* Fetch rankings from BEYABLE API
*/
List<BeyableRankingData> fetchRankings(BeyableConfigModel config, String ruleId, String segmentId);
}

Create src/com/beyable/ranking/service/impl/DefaultBeyableApiService.java:

package com.beyable.ranking.service.impl;

import com.beyable.ranking.constants.BeyablerankingConstants;
import com.beyable.ranking.dto.BeyableRankingData;
import com.beyable.ranking.model.BeyableConfigModel;
import com.beyable.ranking.service.BeyableApiService;
import de.hybris.platform.servicelayer.config.ConfigurationService;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

public class DefaultBeyableApiService implements BeyableApiService
{
private static final Logger LOG = Logger.getLogger(DefaultBeyableApiService.class);

private ConfigurationService configurationService;

@Override
public List<BeyableRankingData> fetchRankings(BeyableConfigModel config, String ruleId, String segmentId)
{
final String endpoint = buildEndpoint(config, ruleId, segmentId);
final String url = config.getApiBaseUrl() + endpoint;

if (config.getDebugLogging())
{
LOG.debug("BEYABLE API Request: " + url);
}

try (CloseableHttpClient httpClient = HttpClients.createDefault())
{
final HttpGet request = new HttpGet(url);
request.setHeader(BeyablerankingConstants.HEADER_SUBSCRIPTION_KEY, config.getSubscriptionKey());
request.setHeader(BeyablerankingConstants.HEADER_ACCEPT, BeyablerankingConstants.HEADER_ACCEPT_CSV);

final HttpResponse response = httpClient.execute(request);
final int statusCode = response.getStatusLine().getStatusCode();

if (statusCode != 200)
{
LOG.error("BEYABLE API Error: Status " + statusCode);
throw new IOException("API returned status " + statusCode);
}

final String csvContent = EntityUtils.toString(response.getEntity());
final List<BeyableRankingData> rankings = parseCSV(csvContent);

if (config.getDebugLogging())
{
LOG.debug("BEYABLE API Response: " + rankings.size() + " rankings retrieved");
}

return rankings;
}
catch (Exception e)
{
LOG.error("Error fetching rankings from BEYABLE API: " + e.getMessage(), e);
throw new RuntimeException("Failed to fetch rankings", e);
}
}

private String buildEndpoint(BeyableConfigModel config, String ruleId, String segmentId)
{
final String accountId = config.getAccountId();
final String tenantId = config.getTenantId();

String endpoint;

if (segmentId != null && tenantId != null)
{
endpoint = BeyablerankingConstants.API_ENDPOINT_SEGMENTED_RANKING_TENANT
.replace("{accountId}", accountId)
.replace("{rankingId}", ruleId)
.replace("{segmentId}", segmentId)
.replace("{tenant}", tenantId);
}
else if (segmentId != null)
{
endpoint = BeyablerankingConstants.API_ENDPOINT_SEGMENTED_RANKING
.replace("{accountId}", accountId)
.replace("{rankingId}", ruleId)
.replace("{segmentId}", segmentId);
}
else if (tenantId != null)
{
endpoint = BeyablerankingConstants.API_ENDPOINT_PRODUCT_RANKING_TENANT
.replace("{accountId}", accountId)
.replace("{rankingId}", ruleId)
.replace("{tenant}", tenantId);
}
else
{
endpoint = BeyablerankingConstants.API_ENDPOINT_PRODUCT_RANKING
.replace("{accountId}", accountId)
.replace("{rankingId}", ruleId);
}

return endpoint;
}

private List<BeyableRankingData> parseCSV(String csvContent) throws IOException
{
final List<BeyableRankingData> rankings = new ArrayList<>();

try (CSVParser parser = CSVParser.parse(new StringReader(csvContent),
CSVFormat.DEFAULT.withFirstRecordAsHeader()))
{
for (CSVRecord record : parser)
{
final BeyableRankingData data = new BeyableRankingData();
data.setProductId(record.get("ProductId"));
data.setAbTestGroup(record.get("abTestGroup"));
data.setPosition(Integer.parseInt(record.get("Position")));

rankings.add(data);
}
}

return rankings;
}

// Getters and Setters
public void setConfigurationService(ConfigurationService configurationService)
{
this.configurationService = configurationService;
}
}

Step 4: Ranking Service​

Create src/com/beyable/ranking/service/BeyableRankingService.java:

package com.beyable.ranking.service;

import com.beyable.ranking.model.BeyableConfigModel;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.model.product.ProductModel;

import java.util.List;

public interface BeyableRankingService
{
/**
* Sync rankings from BEYABLE API
*/
int syncRankings(BaseSiteModel baseSite);

/**
* Get configuration for base site
*/
BeyableConfigModel getConfiguration(BaseSiteModel baseSite);

/**
* Apply rankings to product list
*/
List<ProductModel> applyRankings(List<ProductModel> products, String segmentId, BaseSiteModel baseSite);

/**
* Get visitor segment from cookies
*/
String getVisitorSegment();

/**
* Check if BEYABLE is enabled for site
*/
boolean isEnabled(BaseSiteModel baseSite);
}

Create src/com/beyable/ranking/service/impl/DefaultBeyableRankingService.java:

package com.beyable.ranking.service.impl;

import com.beyable.ranking.constants.BeyablerankingConstants;
import com.beyable.ranking.dao.BeyableRankingDao;
import com.beyable.ranking.dto.BeyableRankingData;
import com.beyable.ranking.model.BeyableConfigModel;
import com.beyable.ranking.model.BeyableRankingModel;
import com.beyable.ranking.service.BeyableApiService;
import com.beyable.ranking.service.BeyableRankingService;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.product.ProductService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.site.BaseSiteService;
import org.apache.log4j.Logger;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

public class DefaultBeyableRankingService implements BeyableRankingService
{
private static final Logger LOG = Logger.getLogger(DefaultBeyableRankingService.class);

private BeyableRankingDao beyableRankingDao;
private BeyableApiService beyableApiService;
private ModelService modelService;
private BaseSiteService baseSiteService;
private ProductService productService;

@Override
public int syncRankings(BaseSiteModel baseSite)
{
final BeyableConfigModel config = getConfiguration(baseSite);

if (config == null || !config.getEnabled() || !config.getSyncEnabled())
{
LOG.warn("BEYABLE sync is disabled for site: " + baseSite.getUid());
return 0;
}

LOG.info("Starting BEYABLE ranking sync for site: " + baseSite.getUid());

int totalSynced = 0;

try
{
// Sync default rankings
final String defaultRankingId = config.getDefaultRankingId();
if (defaultRankingId != null)
{
totalSynced += syncRankingsForRule(config, defaultRankingId, null, baseSite);
}

// Sync segmented rankings if enabled
if (config.getUseSegmentation() && config.getSegmentedRankingId() != null)
{
// Note: You might want to sync multiple segments
// For now, we'll just sync the primary segmented rule
final List<String> segments = getSegmentsToSync();
for (String segmentId : segments)
{
totalSynced += syncRankingsForRule(config, config.getSegmentedRankingId(), segmentId, baseSite);
}
}

LOG.info("BEYABLE sync completed. Total rankings synced: " + totalSynced);
}
catch (Exception e)
{
LOG.error("Error during BEYABLE sync: " + e.getMessage(), e);
throw new RuntimeException("Sync failed", e);
}

return totalSynced;
}

private int syncRankingsForRule(BeyableConfigModel config, String ruleId, String segmentId, BaseSiteModel baseSite)
{
LOG.info(String.format("Syncing rankings for rule: %s, segment: %s", ruleId, segmentId != null ? segmentId : "default"));

try
{
// Fetch rankings from API
final List<BeyableRankingData> rankingData = beyableApiService.fetchRankings(config, ruleId, segmentId);

LOG.info("Fetched " + rankingData.size() + " rankings from BEYABLE API");

// Clear old rankings
beyableRankingDao.clearRankings(ruleId, segmentId, baseSite);

// Store new rankings
final List<BeyableRankingModel> rankings = new ArrayList<>();
final Date now = new Date();

for (BeyableRankingData data : rankingData)
{
try
{
final ProductModel product = productService.getProductForCode(data.getProductId());

final BeyableRankingModel ranking = modelService.create(BeyableRankingModel.class);
ranking.setProduct(product);
ranking.setPosition(data.getPosition());
ranking.setAbTestGroup(data.getAbTestGroup());
ranking.setSegmentId(segmentId);
ranking.setRuleId(ruleId);
ranking.setBaseSite(baseSite);
ranking.setLastSyncDate(now);

rankings.add(ranking);
}
catch (Exception e)
{
LOG.warn("Product not found: " + data.getProductId());
}
}

// Batch save
beyableRankingDao.saveRankings(rankings);

LOG.info("Stored " + rankings.size() + " rankings");

return rankings.size();
}
catch (Exception e)
{
LOG.error("Error syncing rankings for rule " + ruleId + ": " + e.getMessage(), e);
return 0;
}
}

private List<String> getSegmentsToSync()
{
// Customize based on your segmentation strategy
return Arrays.asList("premium", "standard", "vip");
}

@Override
public BeyableConfigModel getConfiguration(BaseSiteModel baseSite)
{
final String query = "SELECT {pk} FROM {BeyableConfig} WHERE {baseSite} = ?baseSite";
final Map<String, Object> params = new HashMap<>();
params.put("baseSite", baseSite);

try
{
return flexibleSearchService.searchUnique(new FlexibleSearchQuery(query, params));
}
catch (Exception e)
{
LOG.warn("No BEYABLE configuration found for site: " + baseSite.getUid());
return null;
}
}

@Override
public List<ProductModel> applyRankings(List<ProductModel> products, String segmentId, BaseSiteModel baseSite)
{
final BeyableConfigModel config = getConfiguration(baseSite);

if (config == null || !config.getEnabled())
{
return products;
}

// Get rankings
final String ruleId = (segmentId != null && config.getSegmentedRankingId() != null)
? config.getSegmentedRankingId()
: config.getDefaultRankingId();

final List<BeyableRankingModel> rankings = beyableRankingDao.findAllRankings(ruleId, segmentId, baseSite);

if (rankings.isEmpty())
{
return products;
}

// Create ranking map
final Map<String, Integer> rankingMap = new HashMap<>();
for (BeyableRankingModel ranking : rankings)
{
rankingMap.put(ranking.getProduct().getCode(), ranking.getPosition());
}

// Split products into ranked and unranked
final List<ProductModel> rankedProducts = new ArrayList<>();
final List<ProductModel> unrankedProducts = new ArrayList<>();

for (ProductModel product : products)
{
if (rankingMap.containsKey(product.getCode()))
{
rankedProducts.add(product);
}
else
{
unrankedProducts.add(product);
}
}

// Sort ranked products by position
rankedProducts.sort(Comparator.comparingInt(p -> rankingMap.get(p.getCode())));

// Combine: ranked first, then unranked
final List<ProductModel> result = new ArrayList<>(rankedProducts);
result.addAll(unrankedProducts);

if (config.getDebugLogging())
{
LOG.debug(String.format("Applied rankings: %d ranked, %d unranked",
rankedProducts.size(), unrankedProducts.size()));
}

return result;
}

@Override
public String getVisitorSegment()
{
final BaseSiteModel currentSite = baseSiteService.getCurrentBaseSite();
final BeyableConfigModel config = getConfiguration(currentSite);

if (config == null || !config.getUseSegmentation())
{
return null;
}

final ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

if (attributes != null)
{
final HttpServletRequest request = attributes.getRequest();
final Cookie[] cookies = request.getCookies();

if (cookies != null)
{
final String cookieName = config.getSegmentCookieName() != null
? config.getSegmentCookieName()
: BeyablerankingConstants.DEFAULT_SEGMENT_COOKIE;

for (Cookie cookie : cookies)
{
if (cookieName.equals(cookie.getName()))
{
return cookie.getValue();
}
}
}
}

return null;
}

@Override
public boolean isEnabled(BaseSiteModel baseSite)
{
final BeyableConfigModel config = getConfiguration(baseSite);
return config != null && config.getEnabled();
}

// Getters and Setters
public void setBeyableRankingDao(BeyableRankingDao beyableRankingDao)
{
this.beyableRankingDao = beyableRankingDao;
}

public void setBeyableApiService(BeyableApiService beyableApiService)
{
this.beyableApiService = beyableApiService;
}

public void setModelService(ModelService modelService)
{
this.modelService = modelService;
}

public void setBaseSiteService(BaseSiteService baseSiteService)
{
this.baseSiteService = baseSiteService;
}

public void setProductService(ProductService productService)
{
this.productService = productService;
}
}

Step 5: DTO Class​

Create src/com/beyable/ranking/dto/BeyableRankingData.java:

package com.beyable.ranking.dto;

public class BeyableRankingData
{
private String productId;
private String abTestGroup;
private Integer position;

// Getters and Setters
public String getProductId()
{
return productId;
}

public void setProductId(String productId)
{
this.productId = productId;
}

public String getAbTestGroup()
{
return abTestGroup;
}

public void setAbTestGroup(String abTestGroup)
{
this.abTestGroup = abTestGroup;
}

public Integer getPosition()
{
return position;
}

public void setPosition(Integer position)
{
this.position = position;
}
}

CronJob Configuration​

Step 1: CronJob Implementation​

Create src/com/beyable/ranking/cronjob/BeyableRankingSyncJob.java:

package com.beyable.ranking.cronjob.

;

import com.beyable.ranking.service.BeyableRankingService;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.cronjob.enums.CronJobResult;
import de.hybris.platform.cronjob.enums.CronJobStatus;
import de.hybris.platform.cronjob.model.CronJobModel;
import de.hybris.platform.servicelayer.cronjob.AbstractJobPerformable;
import de.hybris.platform.servicelayer.cronjob.PerformResult;
import de.hybris.platform.site.BaseSiteService;
import org.apache.log4j.Logger;

import java.util.Collection;

public class BeyableRankingSyncJob extends AbstractJobPerformable<CronJobModel>
{
private static final Logger LOG = Logger.getLogger(BeyableRankingSyncJob.class);

private BeyableRankingService beyableRankingService;
private BaseSiteService baseSiteService;

@Override
public PerformResult perform(CronJobModel cronJob)
{
LOG.info("Starting BEYABLE ranking synchronization");

try
{
final Collection<BaseSiteModel> baseSites = baseSiteService.getAllBaseSites();
int totalSynced = 0;

for (BaseSiteModel baseSite : baseSites)
{
if (beyableRankingService.isEnabled(baseSite))
{
LOG.info("Syncing rankings for site: " + baseSite.getUid());
totalSynced += beyableRankingService.syncRankings(baseSite);
}
}

LOG.info("BEYABLE sync completed successfully. Total rankings synced: " + totalSynced);
return new PerformResult(CronJobResult.SUCCESS, CronJobStatus.FINISHED);
}
catch (Exception e)
{
LOG.error("Error during BEYABLE sync: " + e.getMessage(), e);
return new PerformResult(CronJobResult.ERROR, CronJobStatus.ABORTED);
}
}

// Getters and Setters
public void setBeyableRankingService(BeyableRankingService beyableRankingService)
{
this.beyableRankingService = beyableRankingService;
}

public void setBaseSiteService(BaseSiteService baseSiteService)
{
this.baseSiteService = baseSiteService;
}
}

Step 2: Create CronJob via Backoffice​

  1. Navigate to System > Background Processes > Cronjobs
  2. Click "+" to create new cronjob
  3. Configure:
    • Code: beyableRankingSyncJob
    • Job: Select beyableRankingSyncJob
    • Session Language: en
  4. Click Save

Step 3: Create Trigger​

  1. In the cronjob, go to Time Schedule tab
  2. Click "+" to add trigger
  3. Configure:
    • Active: Yes
    • Cron Expression: 0 0 */2 * * ? (every 2 hours)
  4. Click Save

Step 4: Alternative: ImpEx Script​

Create resources/impex/essentialdata-beyable-cronjobs.impex:

# BEYABLE Ranking Sync CronJob
INSERT_UPDATE ServicelayerJob; code[unique=true]; springId
; beyableRankingSyncJob ; beyableRankingSyncJob

INSERT_UPDATE CronJob; code[unique=true]; job(code); sessionLanguage(isocode)
; beyableRankingSyncCronJob ; beyableRankingSyncJob ; en

INSERT_UPDATE Trigger; cronjob(code)[unique=true]; cronExpression
; beyableRankingSyncCronJob ; 0 0 */2 * * ?

Load via HAC:

  1. Navigate to Console > Impex Import
  2. Paste the ImpEx
  3. Click Import content

Frontend Integration​

Step 1: Product Search Populator​

Create src/com/beyable/ranking/populators/BeyableProductSearchPagePopulator.java:

package com.beyable.ranking.populators;

import com.beyable.ranking.service.BeyableRankingService;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.search.data.SearchStateData;
import de.hybris.platform.commerceservices.search.facetdata.ProductSearchPageData;
import de.hybris.platform.converters.Populator;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.product.ProductService;
import de.hybris.platform.servicelayer.dto.converter.ConversionException;
import de.hybris.platform.site.BaseSiteService;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

public class BeyableProductSearchPagePopulator implements Populator<ProductSearchPageData<SearchStateData, ProductData>, ProductSearchPageData<SearchStateData, ProductData>>
{
private static final Logger LOG = Logger.getLogger(BeyableProductSearchPagePopulator.class);

private BeyableRankingService beyableRankingService;
private ProductService productService;
private BaseSiteService baseSiteService;

@Override
public void populate(ProductSearchPageData<SearchStateData, ProductData> source,
ProductSearchPageData<SearchStateData, ProductData> target) throws ConversionException
{
if (!beyableRankingService.isEnabled(baseSiteService.getCurrentBaseSite()))
{
return;
}

final String segmentId = beyableRankingService.getVisitorSegment();
final List<ProductData> products = source.getResults();

if (products == null || products.isEmpty())
{
return;
}

try
{
// Convert ProductData to ProductModel
final List<ProductModel> productModels = new ArrayList<>();
for (ProductData productData : products)
{
final ProductModel product = productService.getProductForCode(productData.getCode());
productModels.add(product);
}

// Apply rankings
final List<ProductModel> rankedProducts = beyableRankingService.applyRankings(
productModels,
segmentId,
baseSiteService.getCurrentBaseSite()
);

// Convert back to ProductData and update target
// Note: This is a simplified approach. In production, you'd need proper conversion
final List<ProductData> rankedProductData = new ArrayList<>();
for (ProductModel productModel : rankedProducts)
{
for (ProductData productData : products)
{
if (productData.getCode().equals(productModel.getCode()))
{
rankedProductData.add(productData);
break;
}
}
}

target.setResults(rankedProductData);

if (LOG.isDebugEnabled())
{
LOG.debug("BEYABLE rankings applied to " + rankedProductData.size() + " products");
}
}
catch (Exception e)
{
LOG.error("Error applying BEYABLE rankings: " + e.getMessage(), e);
// Don't fail the search, just log the error
}
}

// Getters and Setters
public void setBeyableRankingService(BeyableRankingService beyableRankingService)
{
this.beyableRankingService = beyableRankingService;
}

public void setProductService(ProductService productService)
{
this.productService = productService;
}

public void setBaseSiteService(BaseSiteService baseSiteService)
{
this.baseSiteService = baseSiteService;
}
}

Register the populator in beyableranking-spring.xml:

<!-- Add to beyableranking-spring.xml -->
<bean id="beyableProductSearchPagePopulator"
class="com.beyable.ranking.populators.BeyableProductSearchPagePopulator">
<property name="beyableRankingService" ref="beyableRankingService"/>
<property name="productService" ref="productService"/>
<property name="baseSiteService" ref="baseSiteService"/>
</bean>

<!-- Modify commerceSearchPageDataConverter to include our populator -->
<bean parent="modifyPopulatorList">
<property name="list" ref="commerceSearchPageDataConverter"/>
<property name="add" ref="beyableProductSearchPagePopulator"/>
</bean>

Step 2: Tracking Tag​

Create acceleratoraddon/web/webroot/WEB-INF/tags/beyable/tracking.tag:

<%@ tag body-content="empty" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<%-- BEYABLE Tracking Script --%>
<c:if test="${beyableEnabled}">
<script type="text/javascript">
// BEYABLE integration - segment detection
(function() {
var segmentCookieName = '${beyableSegmentCookieName}';

// Helper to read cookie
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
}

// Log segment for debugging (if debug mode is enabled)
var segment = getCookie(segmentCookieName);
if (segment && console && console.log) {
console.log('BEYABLE Segment:', segment);
}
})();
</script>
</c:if>

Include in your footer:

<%@ taglib prefix="beyable" tagdir="/WEB-INF/tags/beyable" %>

<!-- Add to footer template -->
<beyable:tracking/>

Testing​

Step 1: Configuration Test​

Create Configuration via ImpEx:

# Create BEYABLE Configuration
INSERT_UPDATE BeyableConfig; baseSite(uid)[unique=true]; accountId; subscriptionKey; apiBaseUrl; defaultRankingId; enabled
; electronics ; YOUR_ACCOUNT_ID ; YOUR_SUBSCRIPTION_KEY ; https://api.eu1.beyable.com ; YOUR_RANKING_ID ; true

# Update with additional settings
UPDATE BeyableConfig; baseSite(uid)[unique=true]; useSegmentation; applyToCategories; applyToSearch; segmentCookieName
; electronics ; true ; true ; true ; b_segment

Load via HAC:

  1. Console > Impex Import
  2. Paste the ImpEx
  3. Click Import content

Step 2: Manual CronJob Test​

Via Backoffice:

  1. Navigate to System > Background Processes > Cronjobs
  2. Find beyableRankingSyncCronJob
  3. Click Start
  4. Monitor logs in hybris/log/tomcat/console.log

Via HAC:

  1. Navigate to Monitoring > Cronjobs
  2. Find beyableRankingSyncCronJob
  3. Click Run

Step 3: Verify Data Storage​

Via Flexible Search (HAC):

SELECT {pk}, {product}, {position}, {ruleId}, {segmentId} 
FROM {BeyableRanking}

Via Backoffice:

  1. Navigate to System > Types
  2. Search for BeyableRanking
  3. View instances

Step 4: Frontend Test​

  1. Clear Cache:

    • HAC > Platform > Cache > Clear All
  2. Test Category Page:

    • Visit a category page
    • Verify products are ordered by BEYABLE rankings
    • Use browser dev tools to check segment cookie
  3. Test Search:

    • Perform a product search
    • Verify ranked results

Step 5: Segment Test​

Set Test Cookie (Browser Console):

document.cookie = "b_segment=premium; path=/";
location.reload();

Clear Cookie:

document.cookie = "b_segment=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/";

Deployment​

Production Deployment Checklist​

Pre-Deployment​

  1. Code Review:

    • Review all custom code
    • Ensure error handling is robust
    • Verify logging is appropriate
  2. Backup:

    • Export type system (HAC > Platform > Type System)
    • Backup database
    • Document current configuration
  3. Build:

    cd hybris/bin/platform
    . ./setantenv.sh
    ant clean all

Deployment Steps​

  1. Stop Server:

    ./hybrisserver.sh stop
  2. Deploy Code:

    • Copy extension to hybris/bin/custom/
    • Update localextensions.xml
  3. Update System:

    ant updatesystem
  4. Start Server:

    ./hybrisserver.sh start
  5. Initialize Configuration:

    • Load initial ImpEx (configuration, cronjob)
    • Via HAC or Backoffice
  6. Schedule CronJob:

    • Configure trigger
    • Run initial sync
  7. Clear Caches:

    • HAC > Platform > Cache > Clear All
    • HAC > Monitoring > JVM > Clear Caches

Post-Deployment​

  1. Verify:

    • Check storefront functionality
    • Verify rankings are applied
    • Test segment detection
    • Review logs for errors
  2. Monitor:

    • Monitor cronjob execution
    • Check database record counts
    • Monitor page performance
    • Track error rates
  3. Documentation:

    • Document configuration
    • Create runbook
    • Train support team

Monitoring & Troubleshooting​

Log Files​

Monitor these log files:

  1. Application Logs:

    • hybris/log/tomcat/console.log
    • Contains all application logs
  2. Cronjob Logs:

    • View in Backoffice: System > Background Processes > Cronjobs > Logs
    • Or HAC: Monitoring > Cronjobs

Common Issues​

1. API Authentication Failures​

Symptom: HTTP 401 errors in logs

Solution:

  • Verify Subscription-Key in BeyableConfig
  • Test API manually:
    curl -H "Subscription-Key: YOUR_KEY" \
    "https://api.eu1.beyable.com/productranking/ACCOUNT_ID/RANKING_ID"
  • Check configuration in Backoffice

2. Rankings Not Applied​

Symptom: Products not sorted by BEYABLE ranking

Solution:

  1. Verify BEYABLE is enabled in configuration
  2. Check database for ranking records:
    SELECT COUNT(*) FROM {BeyableRanking}
  3. Verify cronjob execution in Backoffice
  4. Enable debug logging in configuration
  5. Clear all caches (HAC > Platform > Cache)

3. CronJob Failures​

Symptom: Cronjob shows error status

Solution:

  1. Review cronjob logs in Backoffice
  2. Check application logs for stack traces
  3. Verify API credentials
  4. Test API connection manually
  5. Check for network/firewall issues

4. Performance Issues​

Symptom: Slow category/search pages

Solution:

  • Enable caching for search results
  • Index database properly (rankings table)
  • Reduce ranking object count
  • Consider Solr integration for large catalogs
  • Monitor query performance

5. Segment Not Detected​

Symptom: Segmented rankings not working

Solution:

  1. Verify cookie name matches configuration
  2. Check browser cookies in dev tools
  3. Test with manual cookie
  4. Verify tracking tag is loaded
  5. Check cookie domain settings

Debug Commands​

Test Service via Groovy Script (HAC):

import com.beyable.ranking.service.BeyableRankingService
import de.hybris.platform.site.BaseSiteService

def beyableRankingService = spring.getBean("beyableRankingService", BeyableRankingService.class)
def baseSiteService = spring.getBean("baseSiteService", BaseSiteService.class)

def baseSite = baseSiteService.getBaseSiteForUID("electronics")
def config = beyableRankingService.getConfiguration(baseSite)

println "Config found: " + (config != null)
println "Enabled: " + (config != null ? config.enabled : "N/A")
println "Account ID: " + (config != null ? config.accountId : "N/A")

// Test sync
def result = beyableRankingService.syncRankings(baseSite)
println "Synced: " + result + " rankings"

Performance Optimization​

1. Database Indexing​

Ensure proper indexes (defined in items.xml):

<indexes>
<index name="productRankingIdx">
<key attribute="product"/>
<key attribute="baseSite"/>
<key attribute="segmentId"/>
</index>
</indexes>

2. Caching Strategy​

Implement caching in service layer:

@Cacheable(value = "beyableRankingsCache", key = "#ruleId + '_' + #segmentId + '_' + #baseSite.uid")
public List<BeyableRankingModel> findAllRankings(String ruleId, String segmentId, BaseSiteModel baseSite)
{
// Implementation
}

Configure in Spring:

<cache:annotation-driven/>

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="beyableRankingsCache"/>
</bean>
</set>
</property>
</bean>

3. Reduce Object Count​

Only sync rankings for active products:

private List<BeyableRankingData> filterActiveProducts(List<BeyableRankingData> rankings)
{
return rankings.stream()
.filter(data -> {
try {
ProductModel product = productService.getProductForCode(data.getProductId());
return product != null && !product.getVariantType().equals(VariantType.valueOf("VARIANT"));
} catch (Exception e) {
return false;
}
})
.collect(Collectors.toList());
}

4. Async Processing​

For large catalogs, consider async processing:

@Async
public CompletableFuture<Integer> syncRankingsAsync(BaseSiteModel baseSite)
{
return CompletableFuture.completedFuture(syncRankings(baseSite));
}

5. Solr Integration​

For optimal performance with large catalogs, integrate with Solr:

  1. Add ranking position to Solr schema
  2. Index rankings during sync
  3. Use Solr sorting instead of post-processing

Appendix​

A. API Endpoint Reference​

EndpointUse Case
/productranking/{accountId}/{rankingId}Default tenant, no segmentation
/productranking/{accountId}/{rankingId}/{tenant}Specific tenant, no segmentation
/segmented-productranking/{accountId}/{rankingId}/{segmentId}Default tenant, with segmentation
/segmented-productranking/{accountId}/{rankingId}/{segmentId}/{tenant}Full configuration

B. Backoffice Navigation​

TaskNavigation Path
Configure BEYABLESystem > Types > BeyableConfig
View RankingsSystem > Types > BeyableRanking
Manage CronJobsSystem > Background Processes > Cronjobs
View LogsSystem > Background Processes > Logs

C. Troubleshooting Checklist​

  • BEYABLE enabled in configuration
  • Account ID and Subscription Key configured
  • Ranking rule IDs configured
  • Extension added to localextensions.xml
  • System updated (ant updatesystem)
  • CronJob created and scheduled
  • Database records created
  • Rankings applied to product listings
  • Logs free of errors
  • Segment cookie detected (if using segmentation)

Support​

For additional assistance:

  • Extension Issues: Check application logs and HAC
  • BEYABLE API Issues: Contact your BEYABLE account manager
  • SAP Commerce Issues: Refer to SAP Commerce documentation or SAP support