EdeeShop · Implementace

Vytvoření nového rozšíření

Upraveno

Následující dokument popisuje, jak vytvořit vlastní rozšíření.

Vytvoření nového rozšíření

Nové rozšíření musí implementovat rozhraní com.fg.cps.eshop.feature.ModuleFeature. Význam jednotlivých metod je popsán v samotném rozhraní. Pro zjednodušení implementace je připraven abstraktní předek com.fg.cps.eshop.feature.AbstractBaseEdeeShopFeature, ze kterého lze vyjít a případně přetížit potřebné metody.

Rozšíření lze dále definovat pomocí anotací (viz Anotace pro rozšíření).

Nakonec je potřeba provést registraci rozšíření (viz Registrace rozšíření).

Registrace rozšíření

Registrace rozšíření probíhá pomocí souboru /META-INF/edeeModuleFeature.init, ve kterém je uvedena plně kvalifikovaná třída rozšíření na samostatném řádku. Nové rozšíření musí tedy obsahovat tento soubor v rámci svého JAR balíčku.

Příklad:

kód
com.fg.cps.eshop.order.OrderFeaturecom.fg.cps.eshop.product.ProductFeature

Anotace pro rozšíření

Rozšíření dále specifikují anotace z následujících balíčků:

Níže je přehled všech klíčových anotací, jejich význam, hlavní parametry a ukázky použití.

@FeatureSpringConfig

  • Popis: Registrace Java Spring konfigurací v rámci rozšíření; podporuje podmíněnou aktivaci podle zapnutých rozšíření a/nebo SpEL podmínky.
  • Parametry:
    • javaConfigs: povinné, seznam konfiguračních tříd (označených @Configuration).
    • featureEnabled: seznam tříd rozšíření, které musí být aktivní.
    • condition: SpEL výraz vyhodnocovaný nad instancí rozšíření (root-context).
  • Příklad:
java
@FeatureSpringConfig(    javaConfigs = {ProductFeatureContextConfiguration.class},    featureEnabled = RestApiFeature.class,    condition = RestApiFeatureConfiguration.BACKEND_API_ENABLED)

@FeatureSpringConfigs

  • Popis: Kontejner pro více konfigurací.
  • Příklad:
java
@FeatureSpringConfigs({    @FeatureSpringConfig(javaConfigs = {ProductFeatureContextConfiguration.class}),    @FeatureSpringConfig(        featureEnabled = RestApiFeature.class,        javaConfigs = {ProductApiConfig.class},        condition = RestApiFeatureConfiguration.BACKEND_API_ENABLED    )})

@MessageSource

  • Popis: Registrace zdrojů zpráv (i18n) pro danou feature.
  • Parametry: value – seznam classpath umístění se soubory lokalizací.
  • Příklad:
java
@MessageSource({"classpath:/META-INF/lib_eshop_edee/product/i18n/messagesAdmin"})

@Mail

  • Popis: Registrace e-mailových šablon a listenerů pomocí konfiguračních XML na classpath; lze podmínit SpEL výrazem.
  • Parametry:
    • value – seznam umístění;
    • condition – SpEL podmínka.
  • Příklad:
java
@Mail("classpath:/META-INF/lib_eshop_order/mail/module-mail-listeners-order.xml")

@DataModel

  • Popis: Rozšíření datového a ADaM modelu (XML konfigurace).
  • Parametry:
    • modelConfiguration: Resource cesta na ADaM model (XML).
    • modelVersion: @ModelVersion s údaji pro verzování DB (name, version, scriptDir).
    • featureEnabled: další rozšíření, která musí být aktivní.
    • priority: priorita zpracování.
    • condition: SpEL podmínka.
  • Příklad:
java
@DataModel(    modelConfiguration = "classpath:/META-INF/lib_eshop_edee/product/model/productModel.xml",    modelVersion = @ModelVersion(        name = "lib_eshop_product",        version = "2025.09.15.13.30",        scriptDir = "classpath:/META-INF/lib_eshop_edee/product/sql/"    ))

@DataModels

  • Popis: Umožňuje definovat více položek (@DataModel) s možností podmínění.
  • Příklad:
java
@DataModels(models = {    @DataModel(modelConfiguration = "classpath:/path/to/modelA.xml"),    @DataModel(modelConfiguration = "classpath:/path/to/modelB.xml", priority = "10")})

@ModelVersion

  • Popis: Popis verzování datového modelu pro SQL autoupdate.
  • Parametry: name (unikátní jméno modelu), version (verze), scriptDir (Resource cesta ke skriptům).
  • Příklad:
java
@ModelVersion(name = "lib_eshop_product", version = "2025.09.15.13.30", scriptDir = "classpath:/META-INF/lib_eshop_edee/product/sql/")

@FeatureTreeNodeConfig

  • Popis: Registrace uzlů do navigačního stromu admin UI (obal pro více @TreeNode).
  • Příklad:
java
@FeatureTreeNodeConfig({    @TreeNode(        localizedIdentifierId = "edeeShopCatalog",        localizedIdentifierCode = "edeeShop.edeeShopCatalog.title",        localizedIdentifierDefault = "Produkty",        onClickPageRelativeId = "productListingPage",        securityIdentifierId = "product",        securityIdentifierName = "product",        edeeShopRootTreeNode = true,        icon = "icon-edee-product icon-blue",        priority = "500"    ),    @TreeNode(        localizedIdentifierId = "edeeShopObsoleteProducts",        localizedIdentifierCode = "edeeShop.edeeShopObsoleteProducts.title",        localizedIdentifierDefault = "Ukončené produkty",        onClickPageRelativeId = "obsoleteProductListingPage",        securityIdentifierId = "product",        securityIdentifierName = "product",        edeeShopParentTreeNodeId = "edeeShopCatalog",        icon = "icon-edee-product-obsolete icon-blue",        priority = "1000"    )})

@TreeNode

  • Popis: Registrace jednoho uzlu do navigačního stromu admin UI.
  • Parametry (výběr):
    • localizedIdentifierId - identifikátor uzlu (unikátní v rámci stromu).
    • localizedIdentifierCode - kód pro i18n (messages.properties).
    • localizedIdentifierDefault - výchozí text (pokud chybí překlad).
    • onClickPageRelativeId nebo onClickPageUrl - cílová stránka (relativní ID v rámci modulu nebo absolutní URL).
    • securityIdentifierId / securityIdentifierName - zabezpečení uzlu.
    • edeeShopParentTreeNodeId, edeeShopRootTreeNode: umístění ve stromu.
    • icon, priority: vzhled a pořadí.
    • stateCompact / stateSelectable / stateHidden, skipContextMenu: stav uzlu.
    • featureEnabled, condition: podmínky vytvoření uzlu.
  • Příklad:
java
@TreeNode(    localizedIdentifierId = "eshopOrder/orderList",    localizedIdentifierCode = "eshopOrderModule.orderListingPage.title",    localizedIdentifierDefault = "Objednávky",    onClickPageRelativeId = "orderListingPage",    securityIdentifierId = "order",    securityIdentifierName = "order",    edeeShopRootTreeNode = true,    icon = "icon-edee-orders icon-blue",    priority = "1000")

@RefreshEventUi

  • Popis: Uvede entity (entityView), pro které se má do UI vypalovat refreshEvent pro aktualizace AR widgetů.
  • Parametry: value – seznam názvů entit.
  • Příklad:
java
@RefreshEventUi({Order.ENTITY_NAME, OrderDispatch.ENTITY_NAME, OrderForPickUp.ENTITY_NAME})

@CacheConfig

  • Popis: Definice cache pro feature; registruje GenericCacheConfiguration.
  • Parametry (výběr): name, scope, copyOnRead/copyOnWrite, diskPersistent, statistics, maxElementsInMemory/OnDisk, memoryStoreEvictionPolicy, TTL/TTI, catalogSpecific, featureEnabled, condition.
  • Příklad:
java
@CacheConfig(    name = "orderRounding",    maxElementsInMemory = 30)

@CacheConfigs

  • Popis: Kontejner pro více @CacheConfig.
  • Příklad:
java
@CacheConfigs(cacheConfigs = {    @CacheConfig(name = SHOPPING_CART_CACHE, maxElementsInMemory = 500, timeToLiveSeconds = 3600, scope = CacheScope.DISTRIBUTED),    @CacheConfig(name = CACHE_BILLING_COUNTRIES, maxElementsInMemory = 15),    @CacheConfig(name = CACHE_DELIVERY_COUNTRIES, maxElementsInMemory = 15)})

@FeatureSpringBean

  • Popis: Označí pole v rozšíření jako Spring bean, který má být k dispozici v kontextu pod daným názvem.
  • Parametry: value – název beanu.
  • Příklad:
java
public class OrderFeature extends AbstractBaseEdeeShopFeature<EdeeShopModuleConfig> {    @FeatureSpringBean("orderModelDescriptor")    private OrderModelDescriptor orderModelDescriptor = new OrderModelDescriptor();}

@FulltextResource

  • Popis: Umožňuje definovat novou fulltextovou sekci, která bude využita v rámci publikovaných dat pro koncové uživatele. Pokud je anotace přítomna, systém se pokusí pro každý katalog (případně i region) vytvořit a zaregistrovat fulltextovou sekci (FulltextSection). Pokud již stejné sekce existují (např. byly registrovány fulltext modulem nebo konfigurací implementující FulltextConfig), nedojde k duplicitní registraci.
  • Parametry:
    • value – název/fragment sekce; musí být unikátní. Používá se jako část identifikátoru sekce (viz SectionConfig#getName(), FulltextSection.Key#fragment()).
    • withoutCatalog – pokud true, vytvoří se jediná sekce bez vazby na katalog (default false = sekce po katalozích).
  • Příklad:
java
@FulltextResource(OrderFeature.FULLTEXT_SECTION_FRAGMENT)
  • Anotace MessageSource, Documentation, RefreshEventUi a další výše zmíněné jsou zpracovány v předkovi AbstractBaseEdeeShopFeature – viz zdrojový kód pro detaily načítání a aplikace.

@Security

  • Popis: Agreguje veškeré bezpečnostní nastavení rozšíření – přidaná práva a definice chráněných zdrojů.
  • Parametry:
    • rights – pole @Right pro definici nadstandardních práv, která nejsou definována v RegistrationIntegrationFeature. Obvykle není potřeba.
    • resources – pole @SecuredResource s definicemi chráněných zdrojů a jejich práv; pokud rozšíření poskytuje chráněné zdroje, je nutné je zde uvést.
  • Příklad:
java
@Security(    rights = {        @Right(value = 'R', name = "Čtení", description = "Čtení objednávek"),        @Right(value = 'W', name = "Zápis", description = "Upravit objednávky")    },    resources = {        @SecuredResource(            name = "order",            description = "Objednávky",            rights = {                @SecuredResourceRightDescription(right = 'R', description = "Čtení objednávky"),                @SecuredResourceRightDescription(right = 'W', description = "Upravit objednávku")            },            defaultResourceRightAssignments = {                @DefaultResourceRightAssignment(role = ROLE_ALL_USERS, rights = {'R'}),                @DefaultResourceRightAssignment(role = ROLE_LOGGED_IN_USER, rights = {'R','W'})            }        )    })
  • Popis: Definuje nové oprávnění (právo). Každé právo je v systému unikátní (jednoznakový kód).
  • Parametry: value (char kód, např. R/W/D), name (název), description (popis).
  • Příklad:
java
@Right(value = 'D', name = "Mazání", description = "Smazat objednávky")

@SecuredResource

  • Popis: Definuje chráněný zdroj v systému (unikátní název), ke kterému se vážou práva.
  • Parametry: name (unikátní identifikátor zdroje), description (popis), featureEnabled (podmíněné povolení), rights (pole @SecuredResourceRightDescription), defaultResourceRightAssignments (výchozí přiřazení práv rolím při absenci předdefinovaných rolí).
  • Příklad:
java
@SecuredResource(name = "order", description = "Objednávky")

@SecuredResourceRightDescription

  • Popis: Popis konkrétního práva vztahujícího se k danému zdroji.
  • Parametry: right (char), description (popis).
  • Příklad:
java
@SecuredResourceRightDescription(right = 'R', description = "Číst zdroj")

@DefaultResourceRightAssignment

  • Popis: Výchozí přiřazení práv ke zdroji pro danou roli.
  • Parametry: role (kód role), rights (pole char s právy).
  • Příklad:
java
@DefaultResourceRightAssignment(role = ROLE_ALL_USERS, rights = {'R'})

Rozšiřování datového modelu a registrace konvertorů (ADaM / MetadataManager)

V rámci rozšíření je často potřeba:

  • doplnit/rozšířit datový model (proxy rozhraní, "traits" pro entity a sub‑entity),
  • zaregistrovat konvertory do ADaMu (MetadataConversionService),
  • registrovat nebo konfigurovat ADaM interceptory (query, SQL query, before-persistence),

Toto se obvykle provádí v lifecycle metodách rozšíření:

  • beforeSpringInitialization() – přidání traitů na modelové deskriptory,
  • afterSpringInitialization(ctx) – práce s MetadataManagerem a registrace konvertorů/interceptorů,
  • prepareStart(ctx) – finální promítnutí modelových deskriptorů do ADaM Pojo kontraktů.

Důležité: EdeeShopModule má pouze JEDEN MetadataManager. K instanci se vždy přistupuje jako ke Spring beanu pod konstantním jménem COMMON_EDEE_SHOP_METADATA_MANAGER (viz com.fg.cps.eshop.feature.config.spring.SharedBeanNames). Vždy jej proto získávejte přes modulový Spring kontext.

Ukázka (zjednodušeně inspirováno com.fg.cps.eshop.order.OrderFeature#afterSpringInitialization):

java
import com.fg.metadata.business.MetadataManager;import com.fg.metadata.business.conversionService.MetadataConversionService;import static com.fg.cps.eshop.feature.config.spring.SharedBeanNames.COMMON_EDEE_SHOP_METADATA_MANAGER;
@Overridepublic void beforeSpringInitialization() {    super.beforeSpringInitialization();    if (!isFeatureEnabled()) return;
    // 1) Rozšíření modelu pomocí traitů (příklad)    myEntityModelDescriptor.addTrait(MyEntityWithSomething.class);    myEntityModelDescriptor.addTraitForSubModel(MySubEntity.class, MySubEntityExtraTrait.class);}
@Overridepublic void afterSpringInitialization(AbstractRefreshableApplicationContext moduleContext) {    super.afterSpringInitialization(moduleContext);
    // 2) Přístup k jedinému MetadataManageru a registrace konvertorů    MetadataManager metadataManager = moduleContext.getBean(        COMMON_EDEE_SHOP_METADATA_MANAGER, MetadataManager.class    );
    MetadataConversionService conversionService = metadataManager        .getMetadataConfiguration()        .getConversionService();
    conversionService.addConverter(new MyDomainToStringConverter());    conversionService.addConverter(new StringToMyDomainConverter());
    final MetadataConfiguration metadataConfiguration = metadataManager.getMetadataConfiguration();    QueryInterceptor interceptor = new MyQueryInterceptor(...);    metadataConfiguration.addQueryInterceptor(interceptor);    // a další addSqlQueryInterceptor, addEntityBeforePersistenceInterceptor...}
@Overridepublic void prepareStart(AbstractRefreshableApplicationContext moduleContext) {    super.prepareStart(moduleContext);
    // 3) Promítnutí deskriptorů do ADaM Pojo kontraktů    MetadataConfiguration cfg = moduleContext        .getBean(COMMON_EDEE_SHOP_METADATA_MANAGER, MetadataManager.class)        .getMetadataConfiguration();
    cfg.addOrUpdatePojoContract(        myEntityModelDescriptor.getEntityName(),        myEntityModelDescriptor.toProxyInterfaceDescriptor()    );}

Poznámky a doporučení:

  • MetadataManager nikdy neinicializujte vlastní instancí – vždy používejte bean COMMON_EDEE_SHOP_METADATA_MANAGER.
  • Konvertory registrujte v afterSpringInitialization, kdy je již k dispozici plně sestavený MetadataManager a jeho ConversionService.
  • Traity přidávejte v beforeSpringInitialization, aby byly modelové deskriptory kompletní ještě před přípravou startu.
  • V prepareStart nezapomeňte zavolat addOrUpdatePojoContract pro každý modelový deskriptor, který vaše rozšíření modifikuje (entity i sub‑entity).
  • Interceptory v ADaMu lze rovněž registrovat i konfigurovat:
    • registrace nového interceptoru: metadataManager.getMetadataConfiguration().addQueryInterceptor(new MyQueryInterceptor(...));
    • registrace SQL query interceptoru: metadataManager.getMetadataConfiguration().addSqlQueryInterceptor(new MySqlQueryInterceptor(...));
    • registrace persistence interceptoru: metadataManager.getMetadataConfiguration().addEntityBeforePersistenceInterceptor(new MyBeforePersistenceInterceptor(...));
    • úprava již existujícího interceptoru: použijte AdamUtils.findQueryInterceptorByType(metadataManager, SomeInterceptor.class) a poté jej nakonfigurujte.
  • Reálné příklady:
    • com.fg.cps.eshop.productAlias.ProductAliasFeature#afterSpringInitialization – registrace query, SQL query i persistence interceptorů a úprava LocalizedNameQueryInterceptor.
    • com.fg.eshop.rest.RestApiFeature#afterSpringInitialization – registrace CatalogEntityIdTranslateInterceptor přes addQueryInterceptor.
    • com.fg.cps.eshop.order.OrderFeature – ukázka práce s query interceptorem přes AdamUtils a registrace konvertorů.

Definice GUI

Každé rozšíření umožňuje definovat vlastní GUI. Stačí implementovat rozhraní com.fg.cps.eshop.feature.type.EdeeModuleFeatureWithGui. Více zde.

Konfigurace rozšíření

Každé rozšíření může být aktivováno vždy nebo aktivace může být podmíněna například konfigurací.

V případě, že má být rozšíření zapnuté vždy, stačí implementovat metodu isFeatureEnabled() a vrátit true.

V ostatních případech musí tato metoda implementovat vlastní logiku, která rozhodne, zda je rozšíření aktivní či nikoliv. Např. pokud bude rozšíření závislé na nějaké konfiguraci, bude implementace vypadat nějak takto:

java
@Overridepublic boolean isFeatureEnabled() {    return someConfigElement != null;}

Kde someConfigElement představuje konkrétní konfiguraci a může se jednat např. o pole, které je označeno anotací com.fg.config.annotation.ConfigElement.

java
@ConfigElement(path = "someConfigElement")private SomeConfigElement someConfigElement;

Tato anotace zajistí, že pokud je konfigurace dostupná (existuje v konfiguraci EdeeShopModulu), bude do pole someConfigElement inicializována instance SomeConfigElement. Pokud konfigurace neexistuje, zůstane pole null.

V tomto případě bude konfigurace modulu vypadat nějak takto:

xml
<module customName="edeeShop" classname="com.fg.cps.eshop.cps.EdeeShopModule">    ...    <config>        <someConfigElement>            <!-- konfigurace pro SomeConfigElement -->        </someConfigElement>    </config>    ...</module>

Publikace dat do evitaDB

Detailní popis publikace dat do evitaDB, včetně anotací schématu, vytváření nových publikovaných entit a projektového rozšiřování existujících entit, najdete v samostatné kapitole Publikace dat do evitaDB.