Vytvoření nového rozšíření
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:
com.fg.cps.eshop.order.OrderFeaturecom.fg.cps.eshop.product.ProductFeatureAnotace 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:
@FeatureSpringConfig( javaConfigs = {ProductFeatureContextConfiguration.class}, featureEnabled = RestApiFeature.class, condition = RestApiFeatureConfiguration.BACKEND_API_ENABLED)@FeatureSpringConfigs
- Popis: Kontejner pro více konfigurací.
- Příklad:
@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:
@MessageSource({"classpath:/META-INF/lib_eshop_edee/product/i18n/messagesAdmin"})- 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:
@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:
@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:
@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:
@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:
@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:
@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:
@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:
@CacheConfig( name = "orderRounding", maxElementsInMemory = 30)@CacheConfigs
- Popis: Kontejner pro více @CacheConfig.
- Příklad:
@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:
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:
@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.
- rights – pole @Right pro definici nadstandardních práv, která nejsou definována v
- Příklad:
@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'}) } ) })@Right
- 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:
@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:
@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:
@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:
@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):
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:
@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.
@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:
<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.
