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