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:
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:
However, ClientConfig (tqcommon/src/main/java/com/perun/tlinq/util/ClientConfig.java) does not have a getAppProperty() method. The correct API chain is:
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:
- The field mappings defined in
hotel-entities.xmlfor the GoGlobal factory (section 6.1) would be unused by the supplier interface path. - The
GoGlobalHotelFacade.searchOffers()would need to manually mapGGHotelOfferfields toCHotelOfferfields, duplicating the mapping logic thatEntityTransformeralready 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¶
-
Fix
getAppProperty()API call (section 3.5). UseClientConfig.instance().getConfig().getProperty("hotel.online.supplier")instead of the non-existentgetAppProperty(). -
Move plugin class to client package (section 1.1). Use
com.perun.tlinq.client.goglobal.framework.GoGlobalPluginto match Tiqets and Amadeus conventions. UpdateconstructorClassintourlinq-config.xmlaccordingly. -
Resolve EntityFacade bypass (section 4.1). Either route through
EntityFacadeto leverage existing mapping infrastructure, or accept that thehotel-entities.xmlGoGlobal field mappings will only be used for non-supplier-interface operations.
6.2 Naming Improvements¶
-
Drop
GGprefix from DB entities (section 2.3). UseCountryEntity,CityEntity, etc. within thecom.perun.tlinq.client.goglobal.dbpackage. -
Use parameterized types in
OnlineHotelSupplierImethod signatures (section 4.2).
6.3 Architecture Improvements¶
-
Consider eliminating
OnlineHotelSupplierRegistryin favor of leveraging the existingClientServiceFactoryresolution. Thehotel.online.supplierAppProperty can be read directly inHotelSearchFacadeto determine which factory to use forEntityFacade.search(). -
Add session token to supplier interface or pass it through
CHotelSearchto enable persistence operations downstream. -
Add shutdown handling to
GoGlobalPluginfor the scheduled executor. -
Add
GoGlobalCacheManagerfor in-memory caching of reference data.
6.4 Configuration¶
- Do not duplicate Database configuration in
goglobal-client.xml. UseClientConfig.instance().getDB(dbName)for DB resolution, matching the Rayna pattern.
6.5 Implementation Order Adjustment¶
-
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. -
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:
-
The
OnlineHotelSupplierIinterface 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. -
The
ClientConfig.getAppProperty()method does not exist and must be corrected before implementation. -
Minor naming convention deviations (plugin class location, DB entity prefix) should be aligned with the newer Tiqets/Amadeus conventions.
-
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.