SAP / olingo-jpa-processor-v4

The JPA Processor fills the gap between Olingo V4 and the database, by providing a mapping between JPA metadata and OData metadata, generating queries and supporting the entity manipulations.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Duplicate entities in odata response

nyordanoff opened this issue · comments

Hello,

We have Bundle entity in our domain which has ManyToMany relation to both API and Destination entities:

  • Bundle
@Entity(name = "consumptionBundle")
@Table(name = "tenants_bundles")
public class BundleEntity {
    ...
    @ManyToMany(mappedBy = "consumptionBundles", fetch = FetchType.LAZY)
    private Set<APIEntity> apis;

    @ManyToMany(mappedBy = "consumptionBundles", fetch = FetchType.LAZY)
    private Set<DestinationEntity> destinations;
    ...
}
  • API
@Entity(name = "api")
@Table(name = "tenants_apis")
public class APIEntity {
    ...
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "tenants_api_bundle_reference",
            joinColumns = {
                    @JoinColumn(name = "api_definition_id", referencedColumnName = "id"),
                    @JoinColumn(name = "formation_id", referencedColumnName = "formation_id"),
            },
            inverseJoinColumns = {
                    @JoinColumn(name = "bundle_id", referencedColumnName = "id"),
                    @JoinColumn(name = "formation_id", referencedColumnName = "formation_id"),
            }
    )
    private Set<BundleEntity> consumptionBundles;
   ...
}
  • Destination
@Entity(name = "destination")
@Table(name = "tenants_destinations")
public class DestinationEntity {
    ...
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "tenants_destinations",
            joinColumns = {
                    @JoinColumn(name = "id", referencedColumnName = "id"),
                    @JoinColumn(name = "formation_id", referencedColumnName = "formation_id"),
            },
            inverseJoinColumns = {
                    @JoinColumn(name = "bundle_id", referencedColumnName = "id"),
                    @JoinColumn(name = "formation_id", referencedColumnName = "formation_id"),
            }
    )
    private Set<BundleEntity> consumptionBundles;
    ...
}

For reproducing purposes, lets assume that we have 2 APIs pointing to 1 Bundle which Bundle has 1 Destination.

If we call /v0/consumptionBundles?$expand=destinations everything is OK -> we receive 1 Bundle with 1 Destination.

However, if we call /v0/apis?$expand=consumptionBundles($expand=destinations) there are duplicate Destinations in the response -> we receive 2 APIs (OK) with 1 Bundle in each (OK) with 2 same Destinations in each Bundle (should be 1 Dest):

{
    "@odata.context": "$metadata#apis(consumptionBundles(destinations()))",
    "value": [
        {
            "title": "foo-api-1",
            "id": "349af461-f94f-45c4-b4df-0a97513a5211",
            "consumptionBundles": [
                {
                    "title": "foo",
                    "id": "96fbf863-93ad-4c59-9464-61753e212e77",
                    "destinations": [
                        {
                            "name": "dest-foo",
                            "id": "22028605-5435-45b3-99bb-ae9a54cd9174"
                        },
                        {
                            "name": "dest-foo",
                            "id": "22028605-5435-45b3-99bb-ae9a54cd9174"
                        }
                    ]
                }
            ]
        },
        {
            "title": "bar-api-1",
            "id": "e710fee0-d954-476b-96d3-4e1b9e6007d1",
            "consumptionBundles": [
                {
                    "title": "foo",
                    "id": "96fbf863-93ad-4c59-9464-61753e212e77",
                    "destinations": [
                        {
                            "name": "dest-foo",
                            "id": "22028605-5435-45b3-99bb-ae9a54cd9174"                            
                        },
                        {
                            "name": "dest-foo",
                            "id": "22028605-5435-45b3-99bb-ae9a54cd9174"
                        }
                    ]
                }
            ]
        }
    ]
}

To locally reproduce this issue, you can:

  • checkout the duplicate-destinations-issue-setup branch and start the service locally from it
  • execute these insert statements so that you have the required bare minimum of entities needed
INSERT INTO public.business_tenant_mappings (id, external_name, external_tenant, provider_name, status, type) VALUES ('3e64ebae-38b5-46a0-b1ed-9ccee153a0ae', 'default', '3e64ebae-38b5-46a0-b1ed-9ccee153a0ae', 'Compass', 'Active', 'account') ON CONFLICT DO NOTHING;
INSERT INTO public.business_tenant_mappings (id, external_name, external_tenant, provider_name, status, type) VALUES ('1eba80dd-8ff6-54ee-be4d-77944d17b10b', 'foo', '1eba80dd-8ff6-54ee-be4d-77944d17b10b', 'Compass', 'Active', 'account') ON CONFLICT DO NOTHING;
INSERT INTO public.business_tenant_mappings (id, external_name, external_tenant, provider_name, status, type) VALUES ('af9f84a9-1d3a-4d9f-ae0c-94f883b33b6e', 'bar', 'af9f84a9-1d3a-4d9f-ae0c-94f883b33b6e', 'Compass', 'Active', 'account') ON CONFLICT DO NOTHING;

--

INSERT INTO public.applications (id, name, description, status_condition, status_timestamp, healthcheck_url, integration_system_id, provider_name, base_url, labels, ready, created_at, updated_at, deleted_at, error, app_template_id, correlation_ids, system_number, documentation_labels, system_status, local_tenant_id, application_namespace, tags) VALUES ('4c63e3b2-3301-4796-bc95-9fb5b2780342', 'system-instance-name', null, 'INITIAL', '2024-02-28 11:56:35.483467', null, null, 'compass', null, null, true, '2024-02-28 11:56:35.483562', null, null, null, null, null, null, null, null, null, null, null);

--

INSERT INTO public.tenant_applications (tenant_id, id, owner, source) VALUES ('3e64ebae-38b5-46a0-b1ed-9ccee153a0ae', '4c63e3b2-3301-4796-bc95-9fb5b2780342', true, '3e64ebae-38b5-46a0-b1ed-9ccee153a0ae');

--

INSERT INTO public.bundles (id, app_id, name, description, instance_auth_request_json_schema, default_instance_auth, ord_id, short_description, links, labels, credential_exchange_strategies, ready, created_at, updated_at, deleted_at, error, correlation_ids, documentation_labels, tags, version, resource_hash, local_tenant_id, app_template_version_id, last_update) VALUES ('96fbf863-93ad-4c59-9464-61753e212e77', '4c63e3b2-3301-4796-bc95-9fb5b2780342', 'foo', 'Foo bar', null, null, null, null, null, null, null, true, '2024-02-28 11:56:35.551719', null, null, null, null, null, null, null, null, null, null, null);

---

INSERT INTO public.app_templates (id, name, description, application_input, placeholders, access_level, application_namespace, created_at, updated_at) VALUES ('49d9b48b-efea-4935-a7f3-c0ca61223206', 'SAP app-template-name', 'app-template-desc', '{"name": "{{name}}", "labels": {"a": ["b", "c"], "d": ["e", "f"], "applicationType": "SAP app-template-name"}, "baseUrl": null, "bundles": null, "webhooks": [{"url": "http://url.com", "auth": null, "mode": null, "type": "CONFIGURATION_CHANGED", "timeout": null, "version": null, "urlTemplate": null, "inputTemplate": null, "retryInterval": null, "headerTemplate": null, "outputTemplate": null, "statusTemplate": null, "correlationIdKey": null}], "description": "test {{display-name}}", "providerName": "compass-tests", "localTenantID": null, "healthCheckURL": "http://url.valid", "statusCondition": null, "integrationSystemID": null, "applicationNamespace": null}', '[{"Name": "name", "JSONPath": "new-placeholder-name-json-path", "Optional": null, "Description": "app"}, {"Name": "display-name", "JSONPath": "new-placeholder-display-name-json-path", "Optional": null, "Description": "new-placeholder-display-name"}]', 'GLOBAL', null, '2024-03-07 17:39:49.711056', '2024-03-07 17:39:49.711056');

---

INSERT INTO public.app_template_versions (id, app_template_id, version, title, correlation_ids, release_date, created_at) VALUES ('54d9b48b-efea-4935-a7f3-c0ca61223206', '49d9b48b-efea-4935-a7f3-c0ca61223206', '1', null, null, null, '2024-03-07 17:40:12.000000');

---

INSERT INTO public.vendors (ord_id, app_id, title, labels, partners, id, documentation_labels, tags, app_template_version_id) VALUES ('vendor:ord.id', '4c63e3b2-3301-4796-bc95-9fb5b2780342', 'vendor-1', null, null, '2c23e3b2-3301-4796-bc95-9fb5b2780342', null, null, '54d9b48b-efea-4935-a7f3-c0ca61223206');
INSERT INTO public.vendors (ord_id, app_id, title, labels, partners, id, documentation_labels, tags, app_template_version_id) VALUES ('globalvendor:ord.id', null, 'global-vendor-1', null, null, '6c23e3b2-3301-4796-bc95-9fb5b2780342', null, null, '54d9b48b-efea-4935-a7f3-c0ca61223206');

---

INSERT INTO public.products (ord_id, app_id, title, short_description, vendor, parent, labels, correlation_ids, id, documentation_labels, tags, app_template_version_id, description) VALUES ('ord:product.id', '4c63e3b2-3301-4796-bc95-9fb5b2780342', 'product-1', 'short desc', 'vendor:ord.id', null, null, null, '1c23e3b2-3301-4796-bc95-9fb5b2780342', null, null, '54d9b48b-efea-4935-a7f3-c0ca61223206', 'desc');

---

INSERT INTO public.packages (id, ord_id, title, short_description, description, version, package_links, links, licence_type, tags, countries, labels, policy_level, app_id, custom_policy_level, vendor, part_of_products, line_of_business, industry, resource_hash, documentation_labels, support_info, app_template_version_id, runtime_restriction) VALUES ('fd5e9820-a8ae-4748-bf3f-049ef91804fa', ':package:manuallyAddedIntegrationDependencies:v1', 'Integration Dependencies package', 'Manually added package', 'This package contains manually added integration dependencies', '1.0.0', null, null, null, null, null, null, 'sap:core:v1', '4c63e3b2-3301-4796-bc95-9fb5b2780342', null, 'vendor:ord.id', '["ord:product.id"]', null, null, null, null, null, null, null);

---

INSERT INTO public.api_definitions (id, app_id, name, description, group_name, default_auth, version_value, version_deprecated, version_deprecated_since, version_for_removal, ord_id, short_description, system_instance_aware, api_protocol, tags, countries, links, api_resource_links, release_status, sunset_date, changelog_entries, labels, package_id, visibility, disabled, part_of_products, line_of_business, industry, ready, created_at, updated_at, deleted_at, error, implementation_standard, custom_implementation_standard, custom_implementation_standard_description, target_urls, extensible, successors, resource_hash, documentation_labels, policy_level, custom_policy_level, supported_use_cases, local_tenant_id, app_template_version_id, correlation_ids, direction, last_update, deprecation_date, responsible, usage) VALUES ('e710fee0-d954-476b-96d3-4e1b9e6007d1', '4c63e3b2-3301-4796-bc95-9fb5b2780342', 'bar-api-1', null, null, null, null, null, null, null, null, null, null, 'rest', '["tag1", "tag2"]', null, null, null, 'active', null, null, null, 'fd5e9820-a8ae-4748-bf3f-049ef91804fa', 'public', null, '["ord:product.id"]', null, null, true, '2024-03-05 13:39:30.737075', null, null, null, null, null, null, '["https://target.url"]', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
INSERT INTO public.api_definitions (id, app_id, name, description, group_name, default_auth, version_value, version_deprecated, version_deprecated_since, version_for_removal, ord_id, short_description, system_instance_aware, api_protocol, tags, countries, links, api_resource_links, release_status, sunset_date, changelog_entries, labels, package_id, visibility, disabled, part_of_products, line_of_business, industry, ready, created_at, updated_at, deleted_at, error, implementation_standard, custom_implementation_standard, custom_implementation_standard_description, target_urls, extensible, successors, resource_hash, documentation_labels, policy_level, custom_policy_level, supported_use_cases, local_tenant_id, app_template_version_id, correlation_ids, direction, last_update, deprecation_date, responsible, usage) VALUES ('349af461-f94f-45c4-b4df-0a97513a5211', '4c63e3b2-3301-4796-bc95-9fb5b2780342', 'foo-api-1', null, null, null, null, null, null, null, null, null, null, 'rest', '["tag1", "tag2"]', null, null, null, 'active', null, null, null, 'fd5e9820-a8ae-4748-bf3f-049ef91804fa', 'public', null, '["ord:product.id"]', null, null, true, '2024-03-05 13:40:01.554421', null, null, null, null, null, null, '["https://target.url"]', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

---

INSERT INTO public.bundle_references (api_def_id, event_def_id, bundle_id, api_def_url, id, is_default_bundle) VALUES ('e710fee0-d954-476b-96d3-4e1b9e6007d1', null, '96fbf863-93ad-4c59-9464-61753e212e77', 'https://target.url', 'e8cb92c5-e8bb-46b3-b0a4-7a15b9164ac1', null);
INSERT INTO public.bundle_references (api_def_id, event_def_id, bundle_id, api_def_url, id, is_default_bundle) VALUES ('349af461-f94f-45c4-b4df-0a97513a5211', null, '96fbf863-93ad-4c59-9464-61753e212e77', 'https://target.url', 'f70ae64a-27a1-4b39-9dec-afb1349362a4', null);

---

INSERT INTO public.destinations (id, name, type, url, authentication, bundle_id, tenant_id, revision, formation_assignment_id, instance_id) VALUES ('22028605-5435-45b3-99bb-ae9a54cd9174', 'dest-foo', 'basic', 'http://test.com/v2', 'basic', '96fbf863-93ad-4c59-9464-61753e212e77', '3e64ebae-38b5-46a0-b1ed-9ccee153a0ae', '3e24ebae-38b5-46a0-b1ed-9ccee153a0ae', null, null);

Note: this issue may sound similar to this one I recently opened, but there the problem was that we had missing JOIN columns which affected the uniqueness in the response which is not the case here.

Hello,

the issue shall be solved with version 2.1.1. In case the problem still exists, please reopen the issue.