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

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

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

Zkopírovat odkaz na sekciVytvoř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í).

Zkopírovat odkaz na sekciRegistrace 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:

1 com.fg.cps.eshop.order.OrderFeature2com.fg.cps.eshop.product.ProductFeature

Zkopírovat odkaz na sekciAnotace 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í.

Zkopírovat odkaz na sekci@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
1 @FeatureSpringConfig(2    javaConfigs = {ProductFeatureContextConfiguration.class},3    featureEnabled = RestApiFeature.class,4    condition = RestApiFeatureConfiguration.BACKEND_API_ENABLED5)

Zkopírovat odkaz na sekci@FeatureSpringConfigs

  • Popis: Kontejner pro více konfigurací.
  • Příklad:
java
1 @FeatureSpringConfigs({2    @FeatureSpringConfig(javaConfigs = {ProductFeatureContextConfiguration.class}),3    @FeatureSpringConfig(4        featureEnabled = RestApiFeature.class,5        javaConfigs = {ProductApiConfig.class},6        condition = RestApiFeatureConfiguration.BACKEND_API_ENABLED7    )8})

Zkopírovat odkaz na sekci@MessageSource

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

Zkopírovat odkaz na sekci@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
1 @Mail("classpath:/META-INF/lib_eshop_order/mail/module-mail-listeners-order.xml")

Zkopírovat odkaz na sekci@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
1 @DataModel(2    modelConfiguration = "classpath:/META-INF/lib_eshop_edee/product/model/productModel.xml",3    modelVersion = @ModelVersion(4        name = "lib_eshop_product",5        version = "2025.09.15.13.30",6        scriptDir = "classpath:/META-INF/lib_eshop_edee/product/sql/"7    )8)

Zkopírovat odkaz na sekci@DataModels

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

Zkopírovat odkaz na sekci@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
1 @ModelVersion(name = "lib_eshop_product", version = "2025.09.15.13.30", scriptDir = "classpath:/META-INF/lib_eshop_edee/product/sql/")

Zkopírovat odkaz na sekci@FeatureTreeNodeConfig

  • Popis: Registrace uzlů do navigačního stromu admin UI (obal pro více @TreeNode).
  • Příklad:
java
1 @FeatureTreeNodeConfig({2    @TreeNode(3        localizedIdentifierId = "edeeShopCatalog",4        localizedIdentifierCode = "edeeShop.edeeShopCatalog.title",5        localizedIdentifierDefault = "Produkty",6        onClickPageRelativeId = "productListingPage",7        securityIdentifierId = "product",8        securityIdentifierName = "product",9        edeeShopRootTreeNode = true,10        icon = "icon-edee-product icon-blue",11        priority = "500"12    ),13    @TreeNode(14        localizedIdentifierId = "edeeShopObsoleteProducts",15        localizedIdentifierCode = "edeeShop.edeeShopObsoleteProducts.title",16        localizedIdentifierDefault = "Ukončené produkty",17        onClickPageRelativeId = "obsoleteProductListingPage",18        securityIdentifierId = "product",19        securityIdentifierName = "product",20        edeeShopParentTreeNodeId = "edeeShopCatalog",21        icon = "icon-edee-product-obsolete icon-blue",22        priority = "1000"23    )24})

Zkopírovat odkaz na sekci@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 stromě.
    • icon, priority: vzhled a pořadí.
    • stateCompact / stateSelectable / stateHidden, skipContextMenu: stav uzlu.
    • featureEnabled, condition: podmínky vytvoření uzlu.
  • Příklad:
java
1 @TreeNode(2    localizedIdentifierId = "eshopOrder/orderList",3    localizedIdentifierCode = "eshopOrderModule.orderListingPage.title",4    localizedIdentifierDefault = "Objednávky",5    onClickPageRelativeId = "orderListingPage",6    securityIdentifierId = "order",7    securityIdentifierName = "order",8    edeeShopRootTreeNode = true,9    icon = "icon-edee-orders icon-blue",10    priority = "1000"11)

Zkopírovat odkaz na sekci@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
1 @RefreshEventUi({Order.ENTITY_NAME, OrderDispatch.ENTITY_NAME, OrderForPickUp.ENTITY_NAME})

Zkopírovat odkaz na sekci@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
1 @CacheConfig(2    name = "orderRounding",3    maxElementsInMemory = 304)

Zkopírovat odkaz na sekci@CacheConfigs

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

Zkopírovat odkaz na sekci@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
1 public class OrderFeature extends AbstractBaseEdeeShopFeature<EdeeShopModuleConfig> {2    @FeatureSpringBean("orderModelDescriptor")3    private OrderModelDescriptor orderModelDescriptor = new OrderModelDescriptor();4}

Zkopírovat odkaz na sekci@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
1 @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.

Zkopírovat odkaz na sekci@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
1 @Security(2    rights = {3        @Right(value = 'R', name = "Čtení", description = "Čtení objednávek"),4        @Right(value = 'W', name = "Zápis", description = "Upravit objednávky")5    },6    resources = {7        @SecuredResource(8            name = "order",9            description = "Objednávky",10            rights = {11                @SecuredResourceRightDescription(right = 'R', description = "Čtení objednávky"),12                @SecuredResourceRightDescription(right = 'W', description = "Upravit objednávku")13            },14            defaultResourceRightAssignments = {15                @DefaultResourceRightAssignment(role = ROLE_ALL_USERS, rights = {'R'}),16                @DefaultResourceRightAssignment(role = ROLE_LOGGED_IN_USER, rights = {'R','W'})17            }18        )19    }20)
  • 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
1 @Right(value = 'D', name = "Mazání", description = "Smazat objednávky")

Zkopírovat odkaz na sekci@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
1 @SecuredResource(name = "order", description = "Objednávky")

Zkopírovat odkaz na sekci@SecuredResourceRightDescription

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

Zkopírovat odkaz na sekci@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
1 @DefaultResourceRightAssignment(role = ROLE_ALL_USERS, rights = {'R'})

Zkopírovat odkaz na sekciRozš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
1 import com.fg.metadata.business.MetadataManager;2import com.fg.metadata.business.conversionService.MetadataConversionService;3import static com.fg.cps.eshop.feature.config.spring.SharedBeanNames.COMMON_EDEE_SHOP_METADATA_MANAGER;4
5@Override6public void beforeSpringInitialization() {7    super.beforeSpringInitialization();8    if (!isFeatureEnabled()) return;9
10    // 1) Rozšíření modelu pomocí traitů (příklad)11    myEntityModelDescriptor.addTrait(MyEntityWithSomething.class);12    myEntityModelDescriptor.addTraitForSubModel(MySubEntity.class, MySubEntityExtraTrait.class);13}14
15@Override16public void afterSpringInitialization(AbstractRefreshableApplicationContext moduleContext) {17    super.afterSpringInitialization(moduleContext);18
19    // 2) Přístup k jedinému MetadataManageru a registrace konvertorů20    MetadataManager metadataManager = moduleContext.getBean(21        COMMON_EDEE_SHOP_METADATA_MANAGER, MetadataManager.class22    );23
24    MetadataConversionService conversionService = metadataManager25        .getMetadataConfiguration()26        .getConversionService();27
28    conversionService.addConverter(new MyDomainToStringConverter());29    conversionService.addConverter(new StringToMyDomainConverter());30
31    final MetadataConfiguration metadataConfiguration = metadataManager.getMetadataConfiguration();32    QueryInterceptor interceptor = new MyQueryInterceptor(...);33    metadataConfiguration.addQueryInterceptor(interceptor);34    // a další addSqlQueryInterceptor, addEntityBeforePersistenceInterceptor...35}36
37@Override38public void prepareStart(AbstractRefreshableApplicationContext moduleContext) {39    super.prepareStart(moduleContext);40
41    // 3) Promítnutí deskriptorů do ADaM Pojo kontraktů42    MetadataConfiguration cfg = moduleContext43        .getBean(COMMON_EDEE_SHOP_METADATA_MANAGER, MetadataManager.class)44        .getMetadataConfiguration();45
46    cfg.addOrUpdatePojoContract(47        myEntityModelDescriptor.getEntityName(),48        myEntityModelDescriptor.toProxyInterfaceDescriptor()49    );50}

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ů.

Zkopírovat odkaz na sekciDefinice GUI

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

Zkopírovat odkaz na sekciKonfigurace 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
1 @Override2public boolean isFeatureEnabled() {3    return someConfigElement != null;4}

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
1 @ConfigElement(path = "someConfigElement")2private 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
1 <module customName="edeeShop" classname="com.fg.cps.eshop.cps.EdeeShopModule">2    ...3    <config>4        <someConfigElement>5            <!-- konfigurace pro SomeConfigElement -->6        </someConfigElement>7    </config>8    ...9</module>