Traity a jejich inicializace
Traity a jejich inicializace
Zkopírovat odkaz na sekciTraity
Traity jsou funkcionalita, která umožňuje dynamické rozšiřování vlastností objektů.
V EdeeShopu lze standardně rozšiřovat o traity všechny entity implementující TraitSupport. Přesněji řečeno, traity využívají možnosti Proxycian. Z toho plyne, že traity lze aplikovat pouze na "Proxycian" proxy, což zahrnuje všechny ADaM a EVITA entity, ale mohou to být i další objekty.
V EdeeShopu jsou podporovány dva způsoby, jak registrovat traity: pomocí ModelClassDescriptor nebo "Na vyžádání" (OnDemand).
Zkopírovat odkaz na sekciModelClassDescriptor
Každá entita, kterou lze v EdeeShopu rozšiřovat o traity, má svou implementaci ModelClassDescriptor. Zaregistrováním traitů do odpovídající implementace se trait začne aplikovat. K registraci traitů by mělo docházet ve fázi com.fg.cps.eshop.feature.AbstractBaseEdeeShopFeature#beforeSpringInitialization.
Příklady rozšíření datového modelu
1 OrderModelDescriptor orderModelDescriptor = orderFeature.getOrderModelDescriptor();2orderModelDescriptor.addTrait(OrderWithVoucher.class); // Trait rozšiřující Order.class3orderModelDescriptor.addTraitForSubModel(OrderItem.class, OrderItemWithEhub.class); // Trait rozšiřující bundle `items`4orderModelDescriptor.addAdvice(CustomAdvice.INSTANCE); // Případné upravené vyhodnocení metod pomocí projektové `one.edee.oss.proxycian.recipe.Advice`, kde `CustomAdvice.INSTANCE` je nějaký staticky singleton
Poznámka: ModelClassDescriptor se pak použije při vytváření proxy, případně se předá do konfigurace ADaMa pro vytváření ADaM proxy objektů (viz.: com.fg.metadata.model.config.MetadataConfiguration.addOrUpdatePojoContract).
Zkopírovat odkaz na sekciRegistrace traitu na vyžádání (OnDemand)
Některé traity jsou potřebné pouze ve specifických případech a není žádoucí je registrovat do ModelClassDescriptor. Například proto, že s nimi nechceme "znečistit" servisní API, tak aby byly tyto traity dostupné pro danou entitu/objekt úplně všude a vždy. Jedná se například o traity, jejichž výstup se použije pouze pro zobrazení dat v AR. V EdeeShopu jsou to typicky traity končící suffixem "WithAdminViewExtensions".
Registrace těchto traitů se provádí pomocí metody com.fg.cps.eshop.catalogEntity.service.wrapping.WrappingService#wrapInto v místě konkrétního použití, například ve výstupu administračních dataproviderů. Metoda vytváří pouze novou instanci proxy, která obaluje původní instanci a na ni deleguje všechna volání metod, s výjimkou těch, které jsou definovány přímo na rozšiřujícím "extension" traitu.
1 @Override2public Category getRecord(Query query) {3 final Category category = categoryService.getCategoryByQuery(query);4 if (category == null) {5 return null;6 }7 return wrappingService.wrapInto(category, CategoryWithAdminViewExtensions.class);8}
Administrační providery umožňují přidávat "onDemand" traity z konfigurace GUI pomocí metadata onDemandTrait (viz com.fg.cps.eshop.common.web.dataProvider.AbstractAdminDataProvider.getOnDemandTrait). Aktuálně je tato podpora implementována pro com.fg.cps.eshop.order.web.backend.dataprovider.OrderDataProvider, com.fg.cps.eshop.order.web.backend.dataprovider.OrderDispatchAdminDataProvider, a pro další je v případě potřeby možné ji doplnit.
1 <setMetadataWidget id="orderDispatchListGridListing">2 <onDemandTrait>com.fg.cps.eshop.balikobot.model.OrderDispatchWithBalikobotAdmin</onDemandTrait>3</setMetadataWidget>
Zkopírovat odkaz na sekciTraitInitCallback - inicializace traitů
Některé traity vyžadují složitější inicializaci dat, která poskytují. Složitější inicializace může být například dodatečné donačtení dat (z databáze, cache nebo jiného zdroje) nebo parsování JSON dat do DTO. Jednoduchým příkladem je EdeeShopEntityWithCatalogCallback, který entitám (např. produktům), u kterých je evidováno pouze idCatalog, nastaví celý objekt katalogu. U entity je pak objekt katalogu k dispozici a není nutné ho na x místech načítat.
Zkopírovat odkaz na sekciZpůsoby Inicializace
Inicializovat data je možné dvěma způsoby: při načítání entit nebo pomocí LazyInitCallback.
!!! První způsob, tedy inicializace při načítání entit, je obecně efektivnější než přes inicializace přes LazyInitCallback. Důvod je, že jsou k dispozici všechny entity, které mají být inicializovány, a je pro ně možné hromadně donačíst doplňující data. "LazyInitCallback" inicializuje každou entitu samostatně a tedy případné donačítání dat se děje pro každou entitu zvlášť.
Zkopírovat odkaz na sekciPři Načítání Entit
TraitInitCallback, který se má vykonat při načítání dat, se definuje v com.fg.cps.eshop.catalogEntity.model.trait.Trait#onInit.
1 @Trait(onInit = EdeeShopEntityWithCatalogCallback.class)2public interface EdeeShopEntity extends IdAccessor<Integer>
Pokud chceme, aby TraitInitCallback byl vykonán pouze někdy (např. chceme inicializovat štítky, pouze když je v rámci dotazu požadováno je načíst), je nutné tuto skutečnost ošetřit v daném initCallbacku. V případě, že se má inicializace považovat za zpracovanou, tak volání TraitInitCallback#init musí vracet true. To zajistí, že se inicializace nespustí vícekrát, a to ani přes LazyInitCallback. Tento příznak se nastaví pro všechny entity, které jsou na vstupu tohoto callbacku.
Příklady podmíněné inicializace:
1 public boolean init(List<GenericEntityWithTags> traitInstance, Query originalQuery, CatalogEntityServiceBridge serviceBridge) {2 if (traitInstance.isEmpty()) {3 return true;4 }5 if (!isRequiredToLoadTags(traitInstance)) {6 return false;7 }8 initEntitiesWithTags(traitInstance, serviceBridge);9 return true;10}11 12private boolean isRequiredToLoadTags(List<GenericEntityWithTags> traitInstance) {13 return ((MetadataContainerProvider)traitInstance.get(0)).getMetadataContainer().wasPropertyBundleFetchedFromStorage(GenericEntityWithTags.BUNDLE_TAG);14}
1 public boolean init(List<VoucherStats> vouchers, Query originalQuery, VoucherServiceBridge serviceBridge) {2 final LoadVoucherStatsHint loadVoucherStatsHint = QueryUtils.findSpecificConstraint(originalQuery.getHint(), LoadVoucherStatsHint.class);3 if (loadVoucherStatsHint == null) {4 return false;5 }6 if (!vouchers.isEmpty()) {7 // .... 8 }9 return true;10}
Inicializace traitů pomocí TraitInitCallback je na EdeeShopu standardně implementována pro všechny entity implementující rozhraní TraitSupport, nicméně samotnou inicializaci zajišťuje TraitUtils#initializeLoadedEntities.
Zkopírovat odkaz na sekciLazyInitCallback
V případě, že TraitInitCallback implementuje LazyInitCallback, data mohou být inicializována až v okamžiku, kdy se na ně dotazujeme, tedy voláním metody, která musí být na sobě anotaci RequiresInit. K lazy inicializaci dojde pouze v případě, že se data neinicializovala již při načtení entit.
1 @RequiresInit(EntityWithTagsInitCallback.class)2List<EntityTag> getTags();
V EdeeShopu je podpora pro lazyInitCallback standardně zapnutá pro:
- všechny EdeeShopEntity (produkty, kategory atd.) a pro CatalogEntity.
- pro objednávky, nákupní košík (včetně CartData).
V případě, že v rámci TraitInitCallback je potřeba provolat metodu s RequiresInit, je nutné tento případ ošetřit tak, že pro danou instanci označíme TraitInitCallback jako zpracovaný.
1 instances.stream().forEach(instance -> instance.setInitializedBy(EntityWithTagsInitCallback.class));Funkcionalitu LazyInitCallback je možné případně nasadit i na jiné entity, musí být ale splněny tyto podmínky:
- Entita musí extendovat TraitSupport.
- Instance entity je vytvořena jako proxy pomocí proxycian.
- Proxy musí mít na sobě zaregistrovanou LazyInitCallbackAdvice.