### Test LEP Configuration with GroovyLepEngineConfiguration Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD This example demonstrates how to configure your test context to extend `GroovyLepEngineConfiguration` and prioritize `TestLepConfiguration`. It also includes necessary beans like `LoggingConfigServiceStub` and `TenantAliasService`. ```java @SpringBootTest(classes = { TestLepConfiguration.class, // <- first MstemplateApp.class, IntegrationTestConfiguration.class }) ``` ```java @Override public LepUpdateMode lepUpdateMode() { return LepUpdateMode.SYNCHRONOUS; } @Bean public LoggingConfigService LoggingConfigService() { return new LoggingConfigServiceStub(); } @Bean public TenantAliasService tenantAliasService() { return new TenantAliasService(mock(CommonConfigRepository.class), mock(TenantListRepository.class)); } ``` -------------------------------- ### Implement LepContextFactory for Balance LEP Context (Java) Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD An example implementation of LepContextFactory for a balance LEP context. This factory class is responsible for building and returning a custom LepContext instance, injecting necessary services through its constructor. It demonstrates how to populate the services and templates within the LepContext. ```java @Component public class BalanceLepContextFactory implements LepContextFactory { private final BalanceService balanceService; private final PocketService pocketService; private final PocketQueryService pocketQueryService; private final BalanceHistoryService balanceHistoryService; private final MetricService metricService; private final RestTemplate restTemplate; private final TenantConfigService tenantConfigService; public BalanceLepContextFactory(BalanceService balanceService, PocketService pocketService, PocketQueryService pocketQueryService, BalanceHistoryService balanceHistoryService, MetricService metricService, @Qualifier("loadBalancedRestTemplate") RestTemplate restTemplate, TenantConfigService tenantConfigService) { this.balanceService = balanceService; this.pocketService = pocketService; this.pocketQueryService = pocketQueryService; this.balanceHistoryService = balanceHistoryService; this.metricService = metricService; this.restTemplate = restTemplate; this.tenantConfigService = tenantConfigService; } @Override public BaseLepContext buildLepContext(LepMethod lepMethod) { LepContext lepContext = new LepContext(); lepContext.services = new LepContext.LepServices(); lepContext.services.balanceService = balanceService; lepContext.services.pocketService = pocketService; lepContext.services.pocketQueryService = pocketQueryService; lepContext.services.balanceHistoryService = balanceHistoryService; lepContext.services.metricService = metricService; lepContext.services.tenantConfigService = tenantConfigService; lepContext.templates = new LepContext.LepTemplates(); lepContext.templates.rest = restTemplate; return lepContext; } } ``` -------------------------------- ### Use LepManagementService for Thread Contexts Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Demonstrates the modern approach to managing LEP thread contexts using `LepManagementService`. The `try-with-resources` statement ensures proper context management. ```java try (var context = lepManagementService.beginThreadContext()) { // run lep methods } ``` -------------------------------- ### Tenant Database Provisioning with Liquibase (Java) Source: https://context7.com/xm-online/xm-commons/llms.txt Automates database schema creation, migration, and deletion for multi-tenant applications using Liquibase. Each tenant gets an isolated database schema, with operations like creating, updating state, and deleting tenant databases. ```java import com.icthh.xm.commons.gen.model.Tenant; import com.icthh.xm.commons.tenantendpoint.provisioner.TenantProvisioner; import org.springframework.stereotype.Service; // Usage example - typically called by TenantManagementService @Service public class TenantLifecycleService { private final TenantProvisioner databaseProvisioner; public TenantLifecycleService(TenantProvisioner databaseProvisioner) { this.databaseProvisioner = databaseProvisioner; } public void onboardNewTenant(String tenantKey, String tenantName) { // Create tenant model Tenant tenant = new Tenant(); tenant.setTenantKey(tenantKey); tenant.setName(tenantName); // Creates database schema and runs Liquibase migrations // Schema name: ACME_CORP (uppercase) // Executes: CREATE SCHEMA IF NOT EXISTS ACME_CORP // Then runs: classpath:config/liquibase/master.xml databaseProvisioner.createTenant(tenant); } public void updateTenantState(String tenantKey, String state) { // Manage tenant state (e.g., ACTIVE, SUSPENDED) // Default implementation is no-op for database databaseProvisioner.manageTenant(tenantKey, state); } public void offboardTenant(String tenantKey) { // Drops tenant database schema // Executes: DROP SCHEMA ACME_CORP CASCADE databaseProvisioner.deleteTenant(tenantKey); } } // Application configuration example: /* application: db-schema-suffix: _db # Optional: Schema becomes ACME_CORP_DB spring: liquibase: change-log: classpath:config/liquibase/master.xml */ ``` -------------------------------- ### Migrate ChangeStateTransitionLepKeyResolver Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Demonstrates the migration of the `ChangeStateTransitionLepKeyResolver` from an old version extending `AppendLepKeyResolver` to a new version implementing `LepKeyResolver`. The new version simplifies segment extraction using `method.getParameter`. ```java @Component public class ChangeStateTransitionLepKeyResolver extends AppendLepKeyResolver { @Override protected String[] getAppendSegments(SeparatorSegmentedLepKey baseKey, LepMethod method, LepManagerService managerService) { String translatedXmEntityTypeKey = translateToLepConvention(getRequiredStrParam(method, "xmEntityTypeKey")); String translatedPrevStateKey = translateToLepConvention(getRequiredStrParam(method, "prevStateKey")); String translatedNextStateKey = translateToLepConvention(getRequiredStrParam(method, "nextStateKey")); return new String[] { translatedXmEntityTypeKey, translatedPrevStateKey, translatedNextStateKey }; } } ``` ```java @Component public class ChangeStateTransitionLepKeyResolver implements LepKeyResolver { @Override public List segments(LepMethod method) { return List.of( method.getParameter("xmEntityTypeKey", String.class), method.getParameter("prevStateKey", String.class), method.getParameter("nextStateKey", String.class) ); } } ``` -------------------------------- ### Migrate CommonsLepResolver Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Illustrates the migration of `CommonsLepResolver`. The old version extended `SeparatorSegmentedLepKeyResolver`, while the new version implements `LepKeyResolver`, separating group and segment logic. ```java @Component public class CommonsLepResolver extends SeparatorSegmentedLepKeyResolver { @Override protected LepKey resolveKey(SeparatorSegmentedLepKey inBaseKey, LepMethod method, LepManagerService managerService) { String group = getRequiredParam(method, "group", String.class) + ".Commons"; String name = getRequiredParam(method, "name", String.class); SeparatorSegmentedLepKey baseKey = new SeparatorSegmentedLepKey(group, XmLepConstants.EXTENSION_KEY_SEPARATOR, XmLepConstants.EXTENSION_KEY_GROUP_MODE); GroupMode groupMode = new GroupMode.Builder().prefixAndIdIncludeGroup(baseKey.getGroupSegmentsSize()).build(); return baseKey.append(name, groupMode); } } ``` ```java @Component public class CommonsLepResolver implements LepKeyResolver { @Override public String group(LepMethod method) { return method.getParameter("group", String.class); } @Override public List segments(LepMethod method) { return List.of(method.getParameter("name", String.class)); } } ``` -------------------------------- ### Run Task in Thread with LepThreadHelper Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD This snippet shows how to execute a task within a thread using `LepThreadHelper`. This is the recommended way to create new threads after the deprecation of `CoreContextsHolder`. ```groovy lepContext.thread.runInThread(executor) { // you task } ``` -------------------------------- ### Replace LEP Thread Context Initialization Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Shows the deprecated method of managing LEP thread contexts using `lepManager.beginThreadContext` and `lepManager.endThreadContext`. This approach is replaced by the more concise `LepManagementService`. ```java ... lepManager.beginThreadContext(threadContext -> { threadContext.setValue(THREAD_CONTEXT_KEY_TENANT_CONTEXT, tenantContextHolder.getContext()); threadContext.setValue(THREAD_CONTEXT_KEY_AUTH_CONTEXT, authContextHolder.getContext()); }); // run lep methods lepManager.endThreadContext(); ... ``` -------------------------------- ### Publish to Local Maven Repository with Gradle Source: https://github.com/xm-online/xm-commons/blob/master/README.md This snippet demonstrates how to publish the xm-commons library to your local Maven repository using Gradle. Ensure you have Gradle installed and configured. This command cleans the project and then publishes the artifacts locally. ```shell ./gradlew clean publishToMavenLocal ``` -------------------------------- ### Execute HTTP Callable Functions with FunctionServiceFacade (Java) Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to implement HTTP-callable functions using FunctionServiceFacade. It supports authenticated and anonymous access, handling POST and GET requests. The controller maps incoming requests to the FunctionServiceFacade for execution, returning the function's result data. ```java import com.icthh.xm.commons.domain.FunctionResult; import com.icthh.xm.commons.service.FunctionServiceFacade; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/functions") @RequiredArgsConstructor public class FunctionController { private final FunctionServiceFacade functionService; @PostMapping("/{functionKey}") public ResponseEntity executeFunction( @PathVariable String functionKey, @RequestBody Map input) { FunctionResult result = functionService.execute(functionKey, input, "POST"); return ResponseEntity.ok(result.getData()); } @GetMapping("/{functionKey}") public ResponseEntity executeFunctionGet( @PathVariable String functionKey, @RequestParam Map params) { FunctionResult result = functionService.execute(functionKey, params, "GET"); return ResponseEntity.ok(result.getData()); } @PostMapping("/anonymous/{functionKey}") public ResponseEntity executeAnonymousFunction( @PathVariable String functionKey, @RequestBody Map input) { // Execute without authentication FunctionResult result = functionService.executeAnonymous(functionKey, input, "POST"); return ResponseEntity.ok(result.getData()); } } ``` -------------------------------- ### Add Groovy Commons Dependency Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD This snippet shows how to add the xm-commons-lep-groovy dependency to your project. Ensure you use the correct version variable `${xm_commons_version}`. ```groovy implementation "com.icthh.xm.commons:xm-commons-lep-groovy:${xm_commons_version}" ``` -------------------------------- ### Get Localized Messages with LocalizationMessageService in Java Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to use LocalizationMessageService in Java to retrieve localized messages. It covers fetching messages for the current locale, messages with parameter substitution, and messages with fallback options. This service is crucial for internationalizing applications in a multi-tenant environment. ```java import com.icthh.xm.commons.i18n.spring.service.LocalizationMessageService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Map; @Service @RequiredArgsConstructor public class NotificationService { private final LocalizationMessageService messageService; public String getLocalizedMessage(String code) { // Get message for current locale (from authentication context or default) return messageService.getMessage(code); } public String getMessageWithSubstitution(String code, Map params) { // Get message and replace placeholders return messageService.getMessage(code, params); // Template: "Hello ${name}, your order ${orderId} is confirmed" // Result: "Hello John, your order ORD-123 is confirmed" } public String getMessageWithFallback(String code, String defaultMessage) { // Try config first, then message bundle, then default return messageService.getMessage(code, null, true, defaultMessage); } public String formatOrderConfirmation(String orderId, String customerName) { return messageService.getMessage( "notification.order.confirmed", Map.of( "orderId", orderId, "customerName", customerName, "supportEmail", "support@example.com" ) ); } public String formatError(String errorCode, String... params) { Map paramMap = new java.util.HashMap<>(); for (int i = 0; i < params.length; i++) { paramMap.put("param" + i, params[i]); } return messageService.getMessage(errorCode, paramMap, false, "An error occurred"); } } // Tenant i18n configuration: /config/tenants/ACME/i18n-message.yml /* notification.order.confirmed: en: "Dear ${customerName}, your order ${orderId} has been confirmed. Contact ${supportEmail} for questions." de: "Liebe(r) ${customerName}, Ihre Bestellung ${orderId} wurde bestätigt. Bei Fragen wenden Sie sich an ${supportEmail}." fr: "Cher(e) ${customerName}, votre commande ${orderId} a été confirmée. Contactez ${supportEmail} pour toute question." error.validation.required: en: "The field ${param0} is required" de: "Das Feld ${param0} ist erforderlich" notification.welcome: en: "Welcome to ACME Corporation!" de: "Willkommen bei ACME Corporation!" */ ``` -------------------------------- ### Configure Synchronous LEP Updates in Tests Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD This Groovy code snippet configures the LEP update mode to synchronous for testing purposes. It should be included in your test context configuration. ```groovy @Override public LepUpdateMode lepUpdateMode() { return LepUpdateMode.SYNCHRONOUS; } ``` -------------------------------- ### YAML Configuration for Function Export Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-function-export/README.md Example YAML configuration for defining functions and their specifications within the XM^online commons export system. This includes function keys, transaction types, input/output specifications, and definitions. ```yaml --- validateFunctionInput: true functions: - key: store/STORE-INFO anonymous: true txType: READ_ONLY inputSpec: ... outputSpec: ... - key: store/GET-EMPLOYEES-AGE httpMethods: - "GET" inputForm: ... outputForm: ... definitions: - key: userInfo value: ... forms: - key: datesForm ref: ... ``` -------------------------------- ### Set Tenant Context Before Thread Context Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD This Java snippet shows the correct way to set the tenant context using `TenantContextUtils.setTenant` before initializing the LEP thread context. This ensures the correct tenant is applied to operations within the thread. ```java TenantContextUtils.setTenant(tenantContextHolder, "TEST"); ``` -------------------------------- ### Create LepContext Class Extending BaseLepContext (Groovy) Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Defines a custom LepContext class that extends BaseLepContext and implements TraceServiceField. It includes nested static classes for services and templates, holding references to various service interfaces and a RestTemplate. This class serves as the context for LEP processing. ```groovy public class LepContext extends BaseLepContext implements TraceServiceField { public LepServices services; public LepTemplates templates; public static class LepServices { public BalanceService balanceService; public PocketService pocketService; public BalanceHistoryService balanceHistoryService; public MetricService metricService; public TenantConfigService tenantConfigService; public PocketQueryService pocketQueryService; } public static class LepTemplates{ public RestTemplate rest; } } ``` -------------------------------- ### Implement LepAdditionalContextField for Global LepContext Fields (Java) Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-lep/MIGRATION_GUIDE.MD Demonstrates how to implement LepAdditionalContextField to add global fields to LepContext. This involves defining an interface (e.g., TraceServiceField) with a default method to access the field type-safely. This pattern is intended for use in libraries or xm-commons to ensure consistent field availability across microservices. ```java @Override public String additionalContextKey() { return FIELD_NAME; // name of additional field } @Override public TraceService additionalContextValue() { return this; // new lepContext field value } @Override public Class fieldAccessorInterface() { return TraceServiceField.class; // interface that help to typesafe access field from lep } ``` ```java public interface TraceServiceField extends LepAdditionalContextField { String FIELD_NAME = "traceService"; default TraceService getTraceService() { // default it's important!!! and name get it's important return (TraceService)get(FIELD_NAME); } } ``` ```java public class LepContext extends BaseLepContext implements TraceServiceField { public MyCoolService myCoolService; // no need to implement a method from LepServiceFactoryField } ``` -------------------------------- ### Execute LEP Function via REST API Source: https://github.com/xm-online/xm-commons/blob/master/xm-commons-function/README.md Demonstrates how to invoke a configured LEP function using a REST API call. The example shows a GET request to a specific function endpoint, including necessary headers like Content-Type and Authorization. ```http GET https://.../api/functions/store/STORE-INFO?name=testStore Content-Type: application/json authorization: Bearer ... ``` -------------------------------- ### Java - Indexing and Searching Documents with ElasticsearchOperations Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to index single documents, perform bulk indexing, and execute various search queries using ElasticsearchOperations. It includes methods for basic search, searching by type, and excluding documents by ID. Dependencies include ElasticsearchOperations, QueryBuilders, and Spring Data classes. ```java import com.icthh.xm.commons.search.ElasticsearchOperations; import com.icthh.xm.commons.search.builder.QueryBuilders; import com.icthh.xm.commons.search.builder.NativeSearchQueryBuilder; import com.icthh.xm.commons.search.query.dto.DeleteQuery; import com.icthh.xm.commons.search.query.dto.IndexQuery; import com.icthh.xm.commons.search.dto.SearchDto; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Set; @Service @RequiredArgsConstructor public class SearchService { private final ElasticsearchOperations elasticsearchOperations; public void indexDocument(String id, Map document) { IndexQuery query = new IndexQuery(); query.setId(id); query.setObject(document); elasticsearchOperations.index(query); } public void bulkIndex(List> documents) { List queries = documents.stream() .map(doc -> { IndexQuery q = new IndexQuery(); q.setId((String) doc.get("id")); q.setObject(doc); return q; }) .toList(); elasticsearchOperations.bulkIndex(queries); } public Page search(String queryString, Class entityClass, String privilegeKey) { return elasticsearchOperations.search( null, // scrollTimeInMillis queryString, PageRequest.of(0, 20), entityClass, privilegeKey ); } public Page searchByType( String query, String typeKey, int page, int size, Class entityClass) { return elasticsearchOperations.searchByQueryAndTypeKey( query, typeKey, PageRequest.of(page, size), entityClass, "ENTITY.SEARCH" ); } public Page searchExcludingIds( String query, Set excludeIds, String typeKey, Class entityClass) { return elasticsearchOperations.searchWithIdNotIn( query, excludeIds, typeKey, PageRequest.of(0, 50), entityClass, "ENTITY.SEARCH" ); } public void deleteDocument(String id) { elasticsearchOperations.delete( elasticsearchOperations.getIndexName(), ElasticsearchOperations.INDEX_QUERY_TYPE, id ); } public void deleteByQuery(String field, String value) { DeleteQuery deleteQuery = new DeleteQuery(); deleteQuery.setQuery(QueryBuilders.termQuery(field, value)); elasticsearchOperations.delete(deleteQuery); } public void refreshIndex(String tenantCode) { elasticsearchOperations.refresh(tenantCode); } public long countDocuments() { var searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .build(); return elasticsearchOperations.count(searchQuery); } } ``` -------------------------------- ### Customize Business Logic with @LogicExtensionPoint in Java Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to use the @LogicExtensionPoint annotation in Java to enable runtime customization of business logic through external scripts (Groovy/JavaScript). It covers basic extension, dynamic key resolution using SpEL, and custom key resolvers for tenant-specific logic without code redeployment. ```java import com.icthh.xm.commons.lep.LogicExtensionPoint; import com.icthh.xm.commons.lep.spring.LepService; import com.icthh.xm.lep.api.LepKeyResolver; import com.icthh.xm.lep.api.LepMethod; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Map; @Service @LepService(group = "pricing") public class PricingService { // Basic LEP - can be customized via script at: // /config/tenants/{tenant}/lep/pricing/CalculateDiscount.groovy @LogicExtensionPoint(value = "CalculateDiscount") public BigDecimal calculateDiscount(BigDecimal originalPrice, String customerType) { // Default implementation if ("PREMIUM".equals(customerType)) { return originalPrice.multiply(new BigDecimal("0.10")); } return BigDecimal.ZERO; } // LEP with dynamic key resolution using SpEL expression @LogicExtensionPoint( value = "ProcessOrder", resolverExpression = "#orderType" // Creates key like: ProcessOrder$$STANDARD ) public void processOrder(String orderType, Map orderData) { // Default order processing System.out.println("Processing " + orderType + " order: " + orderData); } // LEP with custom key resolver for complex key generation @LogicExtensionPoint( value = "ValidateEntity", resolver = EntityTypeKeyResolver.class ) public boolean validateEntity(String entityType, Object entity) { // Default validation return entity != null; } // Custom key resolver implementation public static class EntityTypeKeyResolver implements LepKeyResolver { @Override public java.util.List segments(LepMethod method) { // Extracts entity type from method arguments to build key String entityType = (String) method.getParameter("entityType", String.class); return java.util.List.of(entityType.toUpperCase()); } } } ``` ```groovy // Example Groovy LEP script: /config/tenants/ACME/lep/pricing/CalculateDiscount.groovy /* def originalPrice = lepContext.inArgs.originalPrice def customerType = lepContext.inArgs.customerType if (customerType == "VIP") { return originalPrice * 0.25 // 25% discount for VIP } // Call default implementation return lepContext.lep.proceed(originalPrice, customerType) */ ``` -------------------------------- ### Manage LEP Engine Lifecycle with LepManagementService in Java Source: https://context7.com/xm-online/xm-commons/llms.txt Illustrates how LepManagementService in Java is used to manage the lifecycle of LEP engines, including initializing them, loading scripts, and managing thread contexts for tenant-aware script execution. It provides methods for executing LEP logic, running tasks within a LEP context, and checking engine status. ```java import com.icthh.xm.commons.lep.api.LepEngineSession; import com.icthh.xm.commons.lep.api.LepExecutor; import com.icthh.xm.commons.lep.api.LepKey; import com.icthh.xm.commons.lep.api.LepManagementService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class LepExecutionService { private final LepManagementService lepManagementService; public Object executeLepWithContext(String groupName, String keyName, Object... args) { // Begin LEP thread context for current tenant try (LepEngineSession session = lepManagementService.beginThreadContext()) { // Build LEP key LepKey lepKey = LepKey.builder() .group(groupName) .name(keyName) .build(); // Get executor and run LepExecutor executor = lepManagementService.getLepExecutor(lepKey); return executor .ifLepPresent(engine -> engine.execute(args)) .ifLepNotExists(() -> defaultBehavior(args)) .getMethodResult(); } } public void runTaskInLepContext(Runnable task) { // Utility method to run any task with LEP context lepManagementService.runInLepContext(task); } public void checkEngineStatus() { if (lepManagementService.isLepEnginesInited()) { System.out.println("LEP engines are initialized and ready"); } } private Object defaultBehavior(Object... args) { System.out.println("No LEP script found, using default behavior"); return null; } } ``` -------------------------------- ### Manage Tenant Context in Java with TenantContextHolder Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to manage multi-tenant context using TenantContextHolder in Java. It shows how to retrieve the current tenant key, validate tenant key formats, and switch tenant contexts. This module ensures thread-safe tenant isolation for multi-tenant applications. ```java import com.icthh.xm.commons.tenant.TenantContextHolder; import com.icthh.xm.commons.tenant.TenantContextUtils; import com.icthh.xm.commons.tenant.TenantKey; import org.springframework.stereotype.Service; @Service public class TenantAwareService { private final TenantContextHolder tenantContextHolder; public TenantAwareService(TenantContextHolder tenantContextHolder) { this.tenantContextHolder = tenantContextHolder; } public void processRequest() { // Get current tenant key (throws if not set) String tenantKey = TenantContextUtils.getRequiredTenantKeyValue(tenantContextHolder); System.out.println("Processing request for tenant: " + tenantKey); // Get tenant key as Optional TenantContextUtils.getTenantKey(tenantContextHolder) .ifPresent(key -> System.out.println("Tenant: " + key.getValue())); // Validate tenant key format (alphanumeric, max 48 chars) if (TenantContextUtils.isTenantKeyValid("ACME_CORP")) { System.out.println("Valid tenant key format"); } } public void switchTenant(String newTenantKey) { // Set tenant context (requires privileged access) TenantContextUtils.setTenant(tenantContextHolder, newTenantKey); // Or use TenantKey object TenantContextUtils.setTenant(tenantContextHolder, TenantKey.valueOf("NEW_TENANT")); } public String normalizeTenantKey(String tenantKey) { // Normalize tenant key (e.g., replace hyphens with underscores) return TenantContextUtils.normalizeTenant(tenantKey); // "my-tenant" becomes "my_tenant" } } ``` -------------------------------- ### Create Localized Business Exceptions with BusinessException (Java) Source: https://context7.com/xm-online/xm-commons/llms.txt Illustrates the usage of BusinessException for creating translatable, parameterized exceptions. This class allows for custom error codes and provides a mechanism to include parameters for client-side localization, enhancing user feedback for business rule violations. ```java import com.icthh.xm.commons.exceptions.BusinessException; import java.util.Map; public class OrderService { public void processOrder(Order order) { // Simple exception with default error code if (order == null) { throw new BusinessException("Order cannot be null"); } // Exception with custom error code if (order.getItems().isEmpty()) { throw new BusinessException( "error.order.empty", "Order must contain at least one item" ); } // Exception with parameters for translation if (order.getTotalAmount().compareTo(java.math.BigDecimal.valueOf(10000)) > 0) { throw new BusinessException( "error.order.limit.exceeded", "Order exceeds maximum limit", Map.of( "maxLimit", "10000", "currency", "USD", "actual", order.getTotalAmount().toString() ) ); } // Exception with positional parameters using withParams() if (!"PENDING".equals(order.getStatus())) { throw new BusinessException( "error.order.invalid.status", "Cannot process order in status: " + order.getStatus() ).withParams(order.getStatus(), "PENDING"); // Creates: param0=CONFIRMED, param1=PENDING } } } ``` -------------------------------- ### Publish Domain Events with EventPublisher in Java Source: https://context7.com/xm-online/xm-commons/llms.txt This Java code demonstrates how to use the EventPublisher to publish domain events. It shows the creation of a DomainEvent object with various details like ID, timestamps, aggregate information, and a payload. The `publish` method is then called with a topic and the event object. Dependencies include `xm-commons-domainevent`. ```java import com.icthh.xm.commons.domainevent.domain.DomainEvent; import com.icthh.xm.commons.domainevent.domain.DomainEventPayload; import com.icthh.xm.commons.domainevent.service.EventPublisher; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.Map; import java.util.UUID; @Service @RequiredArgsConstructor public class OrderEventService { private final EventPublisher eventPublisher; public void publishOrderCreated(Order order) { DomainEvent event = DomainEvent.builder() .id(UUID.randomUUID()) .txId(UUID.randomUUID().toString()) .eventDate(Instant.now()) .aggregateId(order.getId().toString()) .aggregateType("Order") .aggregateName(order.getOrderNumber()) .operation("CREATE") .msName("order-service") .userKey(order.getCreatedBy()) .tenant(order.getTenantKey()) .meta(Map.of( "orderType", order.getType(), "priority", order.getPriority() )) .payload(new DomainEventPayload() { @Override public Object getBefore() { return null; } @Override public Object getAfter() { return Map.of( "orderId", order.getId(), "totalAmount", order.getTotalAmount(), "items", order.getItems().size() ); } }) .build(); // Publish to configured transport (Kafka topic, outbox table, etc.) // Source parameter used to select transport configuration eventPublisher.publish("order-events", event); } public void publishOrderStatusChange(Order order, String oldStatus, String newStatus) { DomainEvent event = DomainEvent.builder() .id(UUID.randomUUID()) .eventDate(Instant.now()) .aggregateId(order.getId().toString()) .aggregateType("Order") .operation("UPDATE") .msName("order-service") .tenant(order.getTenantKey()) .payload(new DomainEventPayload() { @Override public Object getBefore() { return Map.of("status", oldStatus); } @Override public Object getAfter() { return Map.of("status", newStatus); } }) .build(); eventPublisher.publish("order-events", event); } static class Order { Long getId() { return 1L; } String getOrderNumber() { return "ORD-001"; } String getType() { return "STANDARD"; } String getPriority() { return "NORMAL"; } java.math.BigDecimal getTotalAmount() { return java.math.BigDecimal.valueOf(99.99); } java.util.List getItems() { return java.util.List.of("item1", "item2"); } String getCreatedBy() { return "user123"; } String getTenantKey() { return "ACME"; } } } ``` -------------------------------- ### Access Security Context with XmAuthenticationContext in Java Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to use XmAuthenticationContextHolder to retrieve authentication and authorization details. It shows how to check for authentication, identify anonymous users, extract user login and key, access OAuth2 token information, and retrieve authorities and custom details. It also covers accessing remote address and session information. ```java import com.icthh.xm.commons.security.XmAuthenticationContext; import com.icthh.xm.commons.security.XmAuthenticationContextHolder; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Optional; import java.util.Set; @Service public class SecurityAwareService { private final XmAuthenticationContextHolder authContextHolder; public SecurityAwareService(XmAuthenticationContextHolder authContextHolder) { this.authContextHolder = authContextHolder; } public void checkAuthentication() { XmAuthenticationContext ctx = authContextHolder.getContext(); // Check authentication state if (!ctx.hasAuthentication()) { System.out.println("No authentication present"); return; } if (ctx.isAnonymous()) { System.out.println("Anonymous user"); return; } if (ctx.isFullyAuthenticated()) { // Get login (never null for authenticated users) String login = ctx.getRequiredLogin(); String userKey = ctx.getRequiredUserKey(); System.out.println("User: " + login + ", Key: " + userKey); // Get OAuth2 token information Optional token = ctx.getTokenValue(); Optional tokenType = ctx.getTokenType(); // Get authorities Set authorities = ctx.getAuthoritiesSet(); System.out.println("Authorities: " + authorities); // Get client ID (for client credentials flow) Optional clientId = ctx.getClientId(); // Get OAuth2 scopes Set scopes = ctx.getScope(); // Get custom details from token String language = ctx.getDetailsValue("language", "en"); Optional customValue = ctx.getAdditionalDetailsValue("customField"); } } public void getRemoteInfo() { XmAuthenticationContext ctx = authContextHolder.getContext(); // Get remote address and session Optional remoteAddress = ctx.getRemoteAddress(); Optional sessionId = ctx.getSessionId(); remoteAddress.ifPresent(ip -> System.out.println("Request from: " + ip)); } } ``` -------------------------------- ### Java Permission Checking with PermissionCheckService Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how to use PermissionCheckService in Java to perform role-based access control checks. It covers basic privilege checks, resource-specific permissions, and type-based access control. Requires Spring Security context and the xm-commons-permission library. ```java import com.icthh.xm.commons.permission.service.PermissionCheckService; import com.icthh.xm.commons.permission.service.translator.SpelTranslator; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @Service public class SecuredResourceService { private final PermissionCheckService permissionCheckService; public SecuredResourceService(PermissionCheckService permissionCheckService) { this.permissionCheckService = permissionCheckService; } public void accessResource(Long resourceId) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // Check basic privilege if (!permissionCheckService.hasPermission(auth, "RESOURCE.READ")) { throw new SecurityException("Access denied: missing RESOURCE.READ privilege"); } // Check privilege with resource context Resource resource = loadResource(resourceId); if (!permissionCheckService.hasPermission(auth, resource, "RESOURCE.UPDATE")) { throw new SecurityException("Access denied: cannot update this resource"); } // Check with resource type if (!permissionCheckService.hasPermission(auth, resourceId, "Resource", "RESOURCE.DELETE")) { throw new SecurityException("Access denied: cannot delete resource"); } } public String getSecuredQueryCondition(String privilegeKey, SpelTranslator translator) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // Create SQL condition from SpEL permission expression // Replaces #subject.* variables with actual values String condition = permissionCheckService.createCondition(auth, privilegeKey, translator); // Returns something like: "owner_id = 'user123' OR department IN ('IT', 'HR')" return condition; } private Resource loadResource(Long id) { return new Resource(); // Placeholder } static class Resource {} } ``` -------------------------------- ### Java: Implement RefreshableConfiguration for Feature Flags Source: https://context7.com/xm-online/xm-commons/llms.txt This Java code demonstrates how to implement the RefreshableConfiguration interface to manage feature flags dynamically. It listens for changes in YAML configuration files, parses them, and provides a method to check if a feature is enabled for a given tenant. Dependencies include Jackson for YAML processing and Spring annotations for component management. ```java import com.icthh.xm.commons.config.client.api.RefreshableConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component public class FeatureFlagConfiguration implements RefreshableConfiguration { private static final String CONFIG_PATH = "/config/tenants/{tenantName}/feature-flags.yml"; private final AntPathMatcher matcher = new AntPathMatcher(); private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); private final ConcurrentHashMap tenantFlags = new ConcurrentHashMap<>(); @Override public void onInit(String configKey, String configValue) { // Called during application startup for existing configs onRefresh(configKey, configValue); } @Override public void onRefresh(String updatedKey, String config) { // Called when configuration changes at runtime String tenant = matcher.extractUriTemplateVariables(CONFIG_PATH, updatedKey).get("tenantName"); if (config == null || config.isBlank()) { tenantFlags.remove(tenant); log.info("Feature flags removed for tenant: {}", tenant); return; } try { FeatureFlags flags = yamlMapper.readValue(config, FeatureFlags.class); tenantFlags.put(tenant, flags); log.info("Feature flags updated for tenant: {}, flags: {}", tenant, flags); } catch (Exception e) { log.error("Failed to parse feature flags for tenant: {}", tenant, e); } } @Override public boolean isListeningConfiguration(String updatedKey) { // Return true if this config path should be handled return matcher.match(CONFIG_PATH, updatedKey); } @Override public void refreshFinished(Collection paths) { // Called after a batch of configurations have been refreshed log.info("Configuration refresh completed for paths: {}", paths); } public boolean isFeatureEnabled(String tenant, String featureName) { FeatureFlags flags = tenantFlags.get(tenant); return flags != null && flags.isEnabled(featureName); } static class FeatureFlags { private java.util.Map features = new java.util.HashMap<>(); public boolean isEnabled(String feature) { return features.getOrDefault(feature, false); } } } ``` -------------------------------- ### Business Exception Handling Source: https://context7.com/xm-online/xm-commons/llms.txt Provides a mechanism for throwing translatable, parameterized exceptions for business rule violations. ```APIDOC ## BusinessException ### Description `BusinessException` allows for throwing exceptions with translatable messages and parameters, facilitating localized error reporting on the client side. ### Usage Instantiate `BusinessException` with an error code, an optional default message, and an optional map of parameters. Parameters can be used for message templating. ### Exception Types - **Simple Exception**: `throw new BusinessException("Error message")` - **Exception with Error Code**: `throw new BusinessException("error.code", "Default message")` - **Exception with Parameters**: `throw new BusinessException("error.code", "Default message", Map.of("param1", "value1"))` - **Exception with Positional Parameters**: `throw new BusinessException("error.code", "Default message").withParams(value1, value2)` ### Example LEP script (Java): ```java import com.icthh.xm.commons.exceptions.BusinessException; import java.util.Map; public class OrderService { public void processOrder(Order order) { // Simple exception with default error code if (order == null) { throw new BusinessException("Order cannot be null"); } // Exception with custom error code if (order.getItems().isEmpty()) { throw new BusinessException("error.order.empty", "Order must contain at least one item"); } // Exception with parameters for translation if (order.getTotalAmount().compareTo(java.math.BigDecimal.valueOf(10000)) > 0) { throw new BusinessException("error.order.limit.exceeded", "Order exceeds maximum limit", Map.of("maxLimit", "10000", "currency", "USD", "actual", order.getTotalAmount().toString())); } // Exception with positional parameters using withParams() if (!"PENDING".equals(order.getStatus())) { throw new BusinessException("error.order.invalid.status", "Cannot process order in status: " + order.getStatus()).withParams(order.getStatus(), "PENDING"); } } } ``` ### Client-side Translation Example (i18n): ```json { "error.order.limit.exceeded": "Order amount ${actual} ${currency} exceeds maximum limit of ${maxLimit} ${currency}", "error.order.invalid.status": "Cannot process order. Current status is {{param0}}, expected {{param1}}" } ``` ``` -------------------------------- ### TenantCacheManager Configuration and Usage in Java Source: https://context7.com/xm-online/xm-commons/llms.txt This snippet demonstrates how to configure and use TenantCacheManager in a Spring Boot application. It includes setting up the TenantAwareCacheManager bean and using @Cacheable for automatic caching, as well as manual cache operations. ```java import com.icthh.xm.commons.cache.TenantCacheManager; import com.icthh.xm.commons.cache.service.TenantAwareCacheManager; import com.icthh.xm.commons.tenant.TenantContextHolder; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; @Configuration public class CacheConfig { @Bean public TenantCacheManager tenantCacheManager( CacheManager delegateCacheManager, TenantContextHolder tenantContextHolder) { return new TenantAwareCacheManager(delegateCacheManager, tenantContextHolder); } } @Service public class CachedDataService { private final TenantCacheManager cacheManager; public CachedDataService(TenantCacheManager cacheManager) { this.cacheManager = cacheManager; } // Cache key becomes: "TENANT_A@products" for tenant TENANT_A @Cacheable(cacheNames = "products", cacheManager = "tenantCacheManager") public Product getProduct(String productId) { // Expensive database lookup return loadProductFromDatabase(productId); } public void manualCacheOperations() { // Get tenant-specific cache Cache productsCache = cacheManager.getCache("products"); if (productsCache != null) { // Put value productsCache.put("product-123", new Product("Widget", 29.99)); // Get value Product cached = productsCache.get("product-123", Product.class); // Evict single entry productsCache.evict("product-123"); } // Evict all caches for current tenant cacheManager.evictCaches(); // Build cache key manually String cacheKey = TenantCacheManager.buildKey("TENANT_A", "products"); // Returns: "TENANT_A@products" // Build tenant prefix String prefix = TenantCacheManager.buildPrefix("TENANT_A"); // Returns: "TENANT_A@" } private Product loadProductFromDatabase(String productId) { return new Product("Product " + productId, 19.99); } static class Product { String name; double price; Product(String name, double price) { this.name = name; this.price = price; } } } ``` -------------------------------- ### Automatic Service Logging with ServiceLoggingAspect (Java) Source: https://context7.com/xm-online/xm-commons/llms.txt Demonstrates how ServiceLoggingAspect automatically logs method entries, exits, parameters, return values, execution times, and exceptions for Spring services. It shows configuration using annotations like @LoggingAspectConfig to exclude parameters or handle collections, and @IgnoreLogginAspect to disable logging for specific classes or methods. Error logging with stack traces is also illustrated. ```java import com.icthh.xm.commons.logging.aop.IgnoreLogginAspect; import com.icthh.xm.commons.logging.LoggingAspectConfig; import org.springframework.stereotype.Service; @Service public class OrderProcessingService { // Automatically logged: // srv:start: OrderProcessingService.processOrder, input: [orderId=123, priority=HIGH] // srv:stop: OrderProcessingService.processOrder, result: OrderResult{...}, time = 45 ms public OrderResult processOrder(Long orderId, String priority) { // Business logic return new OrderResult(orderId, "PROCESSED"); } // Configure logging behavior with annotation @LoggingAspectConfig( inputExcludeParams = "sensitiveData", // Don't log this parameter inputCollectionAware = true, // Truncate large collections resultCollectionAware = true ) public void processWithSensitiveData(String orderId, String sensitiveData) { // Logged without sensitiveData parameter } // Errors are automatically logged with stack trace: // srv:stop: OrderProcessingService.riskyOperation, error: IllegalStateException: ..., time = 12 ms public void riskyOperation() { throw new IllegalStateException("Something went wrong"); } static class OrderResult { Long orderId; String status; OrderResult(Long orderId, String status) { this.orderId = orderId; this.status = status; } } } // Exclude entire class or method from logging @Service @IgnoreLogginAspect public class HighVolumeService { public void frequentOperation() { // Not logged to avoid log spam } // Or exclude specific methods @IgnoreLogginAspect public byte[] getBinaryData() { return new byte[1000]; } } // Configure logging levels per package/class/method via YAML: /* logging: config: service: com.example.order: OrderProcessingService: processOrder: level: DEBUG logInput: HIDE logResult: FULL */ ```