Skip to content

GoGlobal Hotel Integration Plugin - Architecture Compliance Review

Review Date: 2026-02-09 Reviewed Document: Writerside/topics/go_global_integration_plan.md Reviewer: Architect Agent


1. Pattern Compliance

1.1 Plugin Lifecycle Pattern

The plan follows the established AbstractPlugin pattern correctly. The GoGlobalPlugin extends AbstractPlugin (defined at tqcommon/src/main/java/com/perun/tlinq/framework/AbstractPlugin.java, lines 1-13), which requires implementations of initializePlugin(), getProperty(), and setProperty().

Compliant aspects: - Constructor loads config early, matching RaynaB2BActPlugin (line 25-26) and TiqetsPlugin (line 42-63). - initializePlugin() order (DB check, refresh scheduling, service factory init) matches RaynaB2BActPlugin (lines 29-53) and TiqetsPlugin (lines 66-123). - Static data refresh scheduling follows the established pattern from both Rayna (SDRefreshRunner with ScheduledExecutorService) and Tiqets (TiqetsDataRefresher).

DEVIATION: Plugin class location. The plan places GoGlobalPlugin.java at tqgglbl/src/main/java/com/perun/tlinq/framework/GoGlobalPlugin.java (section 4.1). However, existing plugins are inconsistent: - Rayna uses com.perun.tlinq.framework package (top-level in tqryb2b): tqryb2b/src/main/java/com/perun/tlinq/framework/RaynaB2BActPlugin.java - Amadeus uses com.perun.tlinq.client.amadeus.framework package: tqamds/src/main/java/com/perun/tlinq/client/amadeus/framework/AmadeusPlugin.java - Tiqets uses com.perun.tlinq.client.tiqets.framework package: tqtiqets/src/main/java/com/perun/tlinq/client/tiqets/framework/TiqetsPlugin.java

The plan uses the older Rayna convention (com.perun.tlinq.framework). The newer convention (Tiqets, Amadeus) places the plugin class within the client package hierarchy (com.perun.tlinq.client.goglobal.framework). The plan should follow the newer convention for consistency with the most recent plugins.

1.2 Service Factory Pattern

The plan's GoGlobalServiceFactory follows the established pattern from RaynaServiceFactory (tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/service/RaynaServiceFactory.java): - Implements RemoteServiceFactoryI (defined at tqcommon/src/main/java/com/perun/tlinq/service/RemoteServiceFactoryI.java) - Double-checked locking singleton (getInstance()) matching Rayna (lines 63-71) and Amadeus (lines 69-77) - createService() resolves service classes via reflection from plugin config

Compliant.

1.3 Entity Service Pattern

The plan's GoGlobalEntityService correctly mirrors RaynaEntityService (tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/service/RaynaEntityService.java), which implements RemoteServiceI, EntityReadServiceI, and EntitySearchServiceI.

Compliant.

1.4 Client Config Pattern

The plan's GoGlobalClientConfig follows the RaynaClientConfig singleton pattern (tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/util/RaynaClientConfig.java): - Reads from TLINQ_HOME system property or environment variable - JAXB unmarshalling of plugin-specific XML config - Singleton with synchronized initialization - Delegates to GoGlobalPluginConfig JAXB object

Compliant.

1.5 DB Session Pattern

The plan's GoGlobalDBSession follows RaynaDBSession (tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/util/RaynaDBSession.java): - Static initializer block for Hibernate SessionFactory - Manual addAnnotatedClass() calls for each JPA entity - Uses ClientConfig.instance() for DB URL resolution - Static getSession() returning _factory.openSession()

Compliant.

1.6 JAXB Plugin Config Pattern

The plan's GoGlobalPluginConfig follows RaynaPluginConfig (tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/config/RaynaPluginConfig.java): - @XmlRootElement, @XmlAccessorType(XmlAccessType.FIELD) - PropertyList for PluginProperties - ServiceList for Services

Compliant.


2. Naming Conventions

2.1 Module Name

DEVIATION: Module naming inconsistency. The plan proposes module name tqgglbl. The existing naming pattern is:

Module Abbreviation Source
tqryb2b Rayna B2B Rayna + B2B
tqamds Amadeus Amadeus shortened
tqtiqets Tiqets Tiqets full
tqodoo Odoo Odoo full

tqgglbl is not immediately recognizable as "GoGlobal". Consider tqgoglobal or tqgglb for better readability. However, tqgglbl is acceptable if the team agrees.

2.2 Package Naming

DEVIATION: Package name goglobal vs abbreviated form. The plan uses com.perun.tlinq.client.goglobal.*. Existing plugins use:

Plugin Package
Rayna com.perun.tlinq.client.ryb2b.* (abbreviated)
Amadeus com.perun.tlinq.client.amadeus.* (full name)
Tiqets com.perun.tlinq.client.tiqets.* (full name)

The plan's goglobal package name is consistent with the newer Amadeus/Tiqets convention. Compliant.

2.3 Class Naming

DEVIATION: Native entity prefix inconsistency. The plan uses GG prefix for native entities (e.g., GGHotelOffer, GGBooking, GGEntity). Existing plugins use:

Plugin Prefix Example
Rayna Ry RyProduct, RyActEntity
Amadeus Amd AmdHotelOffer, AmadeusEntity
Tiqets Tq TqProduct, TqEntity

The GG prefix is short and recognizable. This is acceptable and follows the abbreviated prefix pattern established by all plugins.

DEVIATION: DB entity naming. The plan names DB entities as GGCountryEntity, GGCityEntity, etc. Existing Rayna DB entities do not have a prefix: CountryEntity, CityEntity at tqryb2b/src/main/java/com/perun/tlinq/client/ryb2b/db/. Tiqets DB entities also use no prefix: CountryEntity, CityEntity at tqtiqets/src/main/java/com/perun/tlinq/client/tiqets/db/.

Recommendation: Drop the GG prefix on DB entities to match the convention: CountryEntity, CityEntity, HotelEntity, StarRatingEntity, RefreshLogEntity. The package namespace (com.perun.tlinq.client.goglobal.db) already disambiguates them.

2.4 Config XML Root Element

The plan uses <GoGlobalPluginConfig> as the XML root element. This follows the convention: <RaynaPluginConfig> in config/rayna-client.xml (line 5).

Compliant.


3. Configuration Consistency

3.1 tourlinq-config.xml Plugin Registration

The plan's plugin registration (section 1.2) is consistent with existing entries. Comparing:

Plan:

<Plugin name="GoGlobalPlugin" constructorClass="com.perun.tlinq.framework.GoGlobalPlugin">

Existing (Tiqets):

<Plugin name="TiqetsPlugin" constructorClass="com.perun.tlinq.client.tiqets.framework.TiqetsPlugin">

DEVIATION: The constructorClass in the plan uses com.perun.tlinq.framework.GoGlobalPlugin, matching the older Rayna convention. If the plugin class is moved to the newer package convention as recommended in section 1.1, this should be com.perun.tlinq.client.goglobal.framework.GoGlobalPlugin.

3.2 ServiceFactory Registration

The plan's ServiceFactory registration matches the established pattern. Comparing with tourlinq-config.xml (lines 121-130 for Tiqets):

Plan:

<ServiceFactory name="GoGlobalServiceFactory" code="GOGLOBAL" type="remote" enabled="true"
                class="com.perun.tlinq.client.goglobal.service.GoGlobalServiceFactory">

Compliant. The code, type, enabled, and class attributes are all present and consistent.

3.3 Client Config XML Structure

The plan's goglobal-client.xml structure matches rayna-client.xml: - <PluginProperties> with property elements - <Databases> section - <Services> section with Service elements having name, class, method, entity, idField

DEVIATION: Databases section is redundant. The goglobal-client.xml includes a <Databases> section, but RaynaDBSession (lines 25-26 of RaynaDBSession.java) resolves the database URL via ClientConfig.instance() (i.e., from tourlinq-config.xml), not from the plugin-specific client XML. The rayna-client.xml also has a <Databases> section (lines 23-28), but it is not used -- RaynaDBSession reads ClientConfig.instance().getDB(dbName).

Recommendation: Include the <Databases> section for documentation/fallback consistency but do not rely on it. Ensure GoGlobalDBSession reads the DB URL from ClientConfig.instance().getDB(dbName) as the primary source, matching RaynaDBSession.

3.4 hotel-entities.xml Factory Configuration

The plan adds GoGlobal factories to HotelOffer, HotelByName, and HotelByCity entities. This follows the multi-factory pattern already established for HotelOffer (which currently has AmadeusServiceFactory at config/entities/hotel-entities.xml, line 389).

DEVIATION: Entity class attribute for HotelByName and HotelByCity. The existing HotelByName entity (line 419) uses class="com.perun.tlinq.client.amadeus.entity.AmdHotel" -- it points directly to the Amadeus native entity, not a canonical entity. This is an anomaly in the existing codebase. For GoGlobal, the plan should define a canonical entity (like CHotelReference in tqapp) or reuse CHotelOffer to avoid coupling the entity configuration to a specific plugin's class.

3.5 AppProperties for Supplier Selection

CRITICAL ISSUE: ClientConfig.getAppProperty() does not exist. The plan's OnlineHotelSupplierRegistry (section 2.2, line 196) calls:

String active = ClientConfig.instance().getAppProperty("hotel.online.supplier");

However, ClientConfig (tqcommon/src/main/java/com/perun/tlinq/util/ClientConfig.java) does not have a getAppProperty() method. The correct API chain is:

String active = ClientConfig.instance().getConfig().getProperty("hotel.online.supplier");

This uses PluginConfig.getProperty() which accesses the AppProperties PropertyList (at PluginConfig.java, line 68-70).

3.6 api-roles.properties

The plan correctly identifies 4 missing endpoints (hotel/searchOffers, hotel/fetchOfferResults, hotel/searchByName, hotel/getByCity) and adds them with guest,agent,admin permissions. This matches the existing hotel endpoints pattern at config/api-roles.properties (line 193, hotel/listHotels=guest,agent,admin).

Compliant.


4. Interface Design: OnlineHotelSupplierI

4.1 Architectural Fit

The OnlineHotelSupplierI interface introduces a new abstraction layer that does not exist in the current architecture. The existing pattern uses EntityFacade -> ClientServiceFactory -> RemoteServiceFactoryI -> service execution. The plan introduces a parallel path:

Current:   HotelApi -> HotelSearchFacade -> EntityFacade -> AmadeusServiceFactory -> service
Proposed:  HotelApi -> OnlineHotelSupplierRegistry -> GoGlobalHotelFacade -> GoGlobalSoapClient

CONCERN: Bypassing EntityFacade/EntityTransformer. The plan has GoGlobalHotelFacade implementing OnlineHotelSupplierI and directly returning List<CHotelOffer>. This bypasses the EntityFacade -> EntityTransformer pipeline, which means:

  1. The field mappings defined in hotel-entities.xml for the GoGlobal factory (section 6.1) would be unused by the supplier interface path.
  2. The GoGlobalHotelFacade.searchOffers() would need to manually map GGHotelOffer fields to CHotelOffer fields, duplicating the mapping logic that EntityTransformer already provides.

Recommendation: Two options: - Option A (Preferred): Keep OnlineHotelSupplierI but have its implementation call through EntityFacade.search("HotelOffer", criteria) with the GoGlobal factory, thus leveraging the existing mapping infrastructure. The HotelSearchFacade.executeBasicSearch() method (at tqapp/src/main/java/com/perun/tlinq/entity/hotel/HotelSearchFacade.java, line 144) already calls ef.search("HotelOffer", scl) which uses the default factory. Simply adding GoGlobal as the new default factory for HotelOffer achieves the same effect without a new interface. - Option B: Keep the direct supplier interface but remove the field mappings from hotel-entities.xml for GoGlobal (since they would be redundant), and accept the dual mapping approach.

4.2 Interface Method Signatures

DEVIATION: Use of raw types. The interface uses raw types:

Object createBooking(Map bookingParams);
Object getBookingStatus(String bookingCode);
List searchHotelsByName(String keyword, String cityCode);

The project convention uses parameterized types. Existing facades return List<TlinqEntity> (e.g., EntityFacade.search() at line 102) or domain-specific types.

Recommendation: Use typed return values:

CHotelBooking createBooking(Map<String, Object> bookingParams);
CHotelBookingStatus getBookingStatus(String bookingCode);
List<CHotelReference> searchHotelsByName(String keyword, String cityCode);

4.3 Registry Pattern

The OnlineHotelSupplierRegistry is a new singleton pattern not present elsewhere in the codebase. Existing service resolution uses ClientServiceFactory.instance(factoryName) which looks up factories by name from tourlinq-config.xml.

CONCERN: The registry introduces a second service resolution mechanism alongside ClientServiceFactory. This could lead to confusion about which resolution path to use for hotel operations.

Recommendation: Consider using the existing ClientServiceFactory resolution and simply changing the defaultFactory for hotel entities, or adding the factory routing logic directly to HotelSearchFacade using ClientConfig.instance().getConfig().getProperty("hotel.online.supplier") without a new registry class.


5. Missing Considerations

5.1 Error Handling Convention

The plan does not detail error handling for the SOAP client. Existing plugins wrap errors in TlinqClientException with specific TlinqErr codes: - TlinqErr.CONFIG_ERROR for configuration issues - TlinqErr.REMOTE_ERROR for remote service failures - TlinqErr.GENERAL for unexpected errors

Reference: RaynaServiceFactory.java (lines 39-57), AmadeusServiceFactory.java (lines 91-97).

Recommendation: Document the error mapping strategy from GoGlobal API error responses to TlinqClientException error codes.

5.2 Session Token Propagation

The EntityFacade constructor requires a session token (line 31-33 of EntityFacade.java). The OnlineHotelSupplierI.searchOffers(CHotelSearch) does not include a session token parameter.

Missing: How will the supplier implementation access the session token needed to create EntityFacade instances for persisting search results? The HotelSearchFacade stores the session in thisSessionId (line 39). The supplier interface needs either a session parameter or a different persistence strategy.

5.3 @TlinqClientEntity Annotation

The plan mentions @TlinqClientEntity annotation on GGHotelOffer (section 4.1), but this annotation is not referenced in any existing code in the codebase. This appears to be a non-existent annotation.

Recommendation: Remove the @TlinqClientEntity reference or clarify if this is a new annotation to be created. Native entities in existing plugins use RemoteEntityI interface (e.g., Rayna's RyActEntity and Amadeus's AmadeusEntity).

5.4 Shutdown/Cleanup

The Tiqets plugin implements a shutdown() method (TiqetsPlugin.java, lines 202-216) for graceful shutdown of the scheduler. The plan's GoGlobalPlugin does not mention shutdown handling for the SDRefreshRunner's ScheduledExecutorService.

Recommendation: Add a shutdown() method to GoGlobalPlugin matching the Tiqets pattern, ensuring the executor is properly terminated on application shutdown.

5.5 Cache Manager

Both Rayna (RaynaCacheManager) and Tiqets (TiqetsCacheManager) have cache manager classes. The plan does not include a GoGlobalCacheManager, instead relying on direct DB queries for cached data. This is functionally acceptable but may result in higher DB load for frequently accessed data (like city/country lookups).

Recommendation: Consider adding a GoGlobalCacheManager for frequently accessed reference data (countries, cities), following the TiqetsCacheManager pattern.

5.6 TripMakerApi Integration

The plan mentions modifying TripMakerApi.searchAccommodations() (section 2.4) but does not detail the changes. The current TripMakerApi likely calls the hotel search facade. The dual-mode routing needs to be consistently applied there as well.

5.7 Test Unit Structure

The plan mentions tests (step 16) but does not specify test structure. Per CLAUDE.md, tests should include: - Database integration tests (CRUD using native entities) - Facade method tests (canonical entity mapping verification) - E2E integration tests (API-level)

Each test class must initialize TLINQ_HOME in a @BeforeAll or @BeforeEach method.


6. Recommendations

6.1 Critical Fixes

  1. Fix getAppProperty() API call (section 3.5). Use ClientConfig.instance().getConfig().getProperty("hotel.online.supplier") instead of the non-existent getAppProperty().

  2. Move plugin class to client package (section 1.1). Use com.perun.tlinq.client.goglobal.framework.GoGlobalPlugin to match Tiqets and Amadeus conventions. Update constructorClass in tourlinq-config.xml accordingly.

  3. Resolve EntityFacade bypass (section 4.1). Either route through EntityFacade to leverage existing mapping infrastructure, or accept that the hotel-entities.xml GoGlobal field mappings will only be used for non-supplier-interface operations.

6.2 Naming Improvements

  1. Drop GG prefix from DB entities (section 2.3). Use CountryEntity, CityEntity, etc. within the com.perun.tlinq.client.goglobal.db package.

  2. Use parameterized types in OnlineHotelSupplierI method signatures (section 4.2).

6.3 Architecture Improvements

  1. Consider eliminating OnlineHotelSupplierRegistry in favor of leveraging the existing ClientServiceFactory resolution. The hotel.online.supplier AppProperty can be read directly in HotelSearchFacade to determine which factory to use for EntityFacade.search().

  2. Add session token to supplier interface or pass it through CHotelSearch to enable persistence operations downstream.

  3. Add shutdown handling to GoGlobalPlugin for the scheduled executor.

  4. Add GoGlobalCacheManager for in-memory caching of reference data.

6.4 Configuration

  1. Do not duplicate Database configuration in goglobal-client.xml. Use ClientConfig.instance().getDB(dbName) for DB resolution, matching the Rayna pattern.

6.5 Implementation Order Adjustment

  1. Step 4 (OnlineHotelSupplierI) should be revisited in light of the EntityFacade bypass concern. If the team decides to route through EntityFacade, the interface may be simplified or eliminated.

  2. Step 13 (Refactor HotelSearchFacade) should come earlier, as it affects the design of the supplier interface. Understanding the exact integration point in HotelSearchFacade.executeBasicSearch() (line 144) is prerequisite to designing the supplier interface.


7. Risk Assessment

7.1 High Risk

Risk Impact Mitigation
EntityFacade bypass creates dual mapping paths Field mapping changes need to be synchronized in two places (XML config and Java code), increasing maintenance burden Route through EntityFacade or clearly document the mapping ownership
Non-existent getAppProperty() method Compile error; blocks all supplier resolution Fix the API call before implementation begins
HotelByName/HotelByCity entities use Amadeus-specific classes config/entities/hotel-entities.xml lines 419, 438 use AmdHotel as the canonical class, creating a dependency from config to Amadeus plugin Create a canonical CHotelReference entity in tqapp before adding GoGlobal factories

7.2 Medium Risk

Risk Impact Mitigation
SOAP 1.2 transport is new to the codebase No existing SOAP handling patterns to follow; all other plugins use REST/JSON Thorough testing of XML envelope construction; consider using a SOAP library
Dual-mode API routing complexity searchMode parameter adds conditional logic to multiple endpoints, increasing test surface Comprehensive E2E tests for both modes; default mode clearly documented
Static data refresh for large datasets GoGlobal hotel database may be significantly larger than Rayna/Tiqets datasets; batch import could cause memory issues Implement batch processing with configurable batch sizes; use streaming for large datasets
Missing shutdown handling Scheduled executor threads may leak on application restart Implement shutdown() method as in TiqetsPlugin

7.3 Low Risk

Risk Impact Mitigation
Amadeus hotel code retained but disabled Code rot over time; disabled code may confuse developers Add clear comments; consider a deprecation timeline
Plugin class location convention mismatch Minor inconsistency; does not affect functionality Follow the recommendation in section 6.1
DB entity naming prefix convention Minor inconsistency; does not affect functionality Follow the recommendation in section 6.2

7.4 Integration Risk with Existing Code

The plan modifies three critical existing files: 1. HotelSearchFacade.java -- the executeBasicSearch() method (line 75-146) is the core search execution point. Changes here affect all hotel search paths including searchAccommodation (line 698 of HotelApi.java) and TripMaker integration. 2. HotelApi.java -- adding searchMode routing affects the public API contract. Existing consumers that do not send searchMode will receive GoGlobal results (the plan defaults to "online"), which is a breaking behavioral change from Amadeus results. 3. hotel-entities.xml -- disabling Amadeus factories changes the entity resolution for HotelOffer, HotelByName, HotelByCity. Any code that explicitly references AmadeusServiceFactory for hotel operations will break.

Recommendation: Verify all callers of EntityFacade.search("HotelOffer", ...) and EntityFacade.search("HotelByName", ...) to ensure they are compatible with the factory switch. A search across the codebase for these entity names should be performed before implementation.


Summary

The GoGlobal integration plan is largely compliant with established TQPro patterns. The primary concerns are:

  1. The OnlineHotelSupplierI interface bypasses the EntityFacade/EntityTransformer pipeline, creating a parallel service resolution path that does not exist in the current architecture. This is the most significant architectural decision and should be explicitly approved.

  2. The ClientConfig.getAppProperty() method does not exist and must be corrected before implementation.

  3. Minor naming convention deviations (plugin class location, DB entity prefix) should be aligned with the newer Tiqets/Amadeus conventions.

  4. Missing considerations (shutdown handling, session token propagation, cache manager, error handling strategy) should be addressed in the implementation plan.

The plan demonstrates good understanding of the codebase structure and follows most conventions correctly. With the recommended fixes, it will be ready for implementation.