diff --git a/.gitignore b/.gitignore index 22625d2eb..d927c9022 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,6 @@ tmp/ local.properties .loadpath -# Eclipse Core -.project - # External tool builders .externalToolBuilders/ @@ -66,4 +63,5 @@ hs_err_pid* # --- Derby stuff **/jpa-processor/test/** -/.vscode/ +/.vscode/ +/.idea/ diff --git a/.project b/.project new file mode 100644 index 000000000..35c726d43 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + olingo-jpa-processor-v4 + + + + + + + + diff --git a/README.md b/README.md index 275796726..5f0d36b6b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ There is no further development for this major version. ### 2.x.x -The current version is based on [Jakarta 10](https://projects.eclipse.org/releases/jakarta-10), so [JPA 3.1.0](https://projects.eclipse.org/projects/ee4j.jpa/releases/3.1) or [Jakarta Persistence Specification](https://github.com/jakartaee/persistence), receptively and [Jakarta Servlet 6.0](https://projects.eclipse.org/projects/ee4j.servlet/releases/6.0). Test are performed using [Eclipselink 4.0.2](https://projects.eclipse.org/projects/ee4j.eclipselink/releases/4.0.2), but there is no real dependency to a JPA implementation. This version requires Java [17](https://sap.github.io/SapMachine/#download). +The current version, [2.0.2](https://github.com/SAP/olingo-jpa-processor-v4/releases/tag/2.0.2), is based on [Jakarta 10](https://projects.eclipse.org/releases/jakarta-10), so [JPA 3.1.0](https://projects.eclipse.org/projects/ee4j.jpa/releases/3.1) or [Jakarta Persistence Specification](https://github.com/jakartaee/persistence), receptively and [Jakarta Servlet 6.0](https://projects.eclipse.org/projects/ee4j.servlet/releases/6.0). Test are performed using [Eclipselink 4.0.2](https://projects.eclipse.org/projects/ee4j.eclipselink/releases/4.0.2), but there is no real dependency to a JPA implementation. This version requires Java [17](https://sap.github.io/SapMachine/#download). The current version comes with [Olingo 4.10.0](https://github.com/apache/olingo-odata4), which does not support Jakarta. Till Olingo supports Jakarta, requests get mapped by the JPA Processor. diff --git a/additionalWords.directory b/additionalWords.directory index 338fd9304..6244fae27 100644 --- a/additionalWords.directory +++ b/additionalWords.directory @@ -56,4 +56,9 @@ Servlet Jakarta CUD JPAO -subquery \ No newline at end of file +subquery +skiptoken +Redis +icao +iata +MULTI diff --git a/jpa-archetype/.project b/jpa-archetype/.project new file mode 100644 index 000000000..eccaa1071 --- /dev/null +++ b/jpa-archetype/.project @@ -0,0 +1,28 @@ + + + jpa-archetype + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa-archetype/odata-jpa-archetype-spring/.project b/jpa-archetype/odata-jpa-archetype-spring/.project new file mode 100644 index 000000000..98302e60f --- /dev/null +++ b/jpa-archetype/odata-jpa-archetype-spring/.project @@ -0,0 +1,23 @@ + + + jpa-archetype-spring + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/pom.xml b/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/pom.xml index 91053d4d7..fedea3134 100644 --- a/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/pom.xml +++ b/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.0 + 3.2.3 diff --git a/jpa-archetype/pom.xml b/jpa-archetype/pom.xml index f47af6a1b..88fb5de8f 100644 --- a/jpa-archetype/pom.xml +++ b/jpa-archetype/pom.xml @@ -4,17 +4,17 @@ 4.0.0 com.sap.olingo odata-jpa-archetype - 2.1.0-SNAPSHOT + 2.1.0-SNAPSHOT pom https://github.com/SAP/olingo-jpa-processor-v4 UTF-8 17 - 2.1.0-SNAPSHOT + 2.1.0 - odata-jpa-archetype-spring + odata-jpa-archetype-spring \ No newline at end of file diff --git a/jpa-tutorial/.asciidoctorconfig.adoc b/jpa-tutorial/.asciidoctorconfig.adoc new file mode 100644 index 000000000..d3cddb773 --- /dev/null +++ b/jpa-tutorial/.asciidoctorconfig.adoc @@ -0,0 +1,12 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// + Initial AsciiDoc editor configuration file - V1.0 + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Did not find any configuration files, so creating this at project root level. +// If you do not like those files to be generated - you can turn it off inside Asciidoctor Editor preferences. +// +// You can define editor specific parts here. +// For example: with next line you could set imagesdir attribute to subfolder "images" relative to the folder where this config file is located. +// :imagesdir: {asciidoctorconfigdir}/images +// +// For more information please take a look at https://github.com/de-jcup/eclipse-asciidoctor-editor/wiki/Asciidoctor-configfiles diff --git a/jpa-tutorial/.project b/jpa-tutorial/.project new file mode 100644 index 000000000..6b8fa3861 --- /dev/null +++ b/jpa-tutorial/.project @@ -0,0 +1,11 @@ + + + jpa-tutorial + + + + + + + + diff --git a/jpa-tutorial/Questions/HowToBuildServerDrivenPaging.adoc b/jpa-tutorial/Questions/HowToBuildServerDrivenPaging.adoc new file mode 100644 index 000000000..90bac97df --- /dev/null +++ b/jpa-tutorial/Questions/HowToBuildServerDrivenPaging.adoc @@ -0,0 +1,694 @@ += How to build server driven paging? + +== Introduction + +OData describes that a server can restrict the number of returned records e.g., to prevent DoS attacks or + to prevent that the server dies with an OutOfMemory exception. Implementing this so called +http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398310[Server-Driven Paging] +requires the knowledge about a couple of details such as: + +- Heap Size of the web service +- Width respectively memory consumption of an instance of an entity +- Expected number of results of a $expand +- Expected number of parallel processed requests on a service instance +- ... + +This makes a general implementation impossible. Instead the JPA Processor provides a hook to calculate the pages of a request. +This hook must implement interface https://github.com/SAP/olingo-jpa-processor-v4/blob/main/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPagingProvider.java[`JPAODataPagingProvider`], +which contains two methods `getFirstPage` and `getNextPage`. `getFirstPage` is called in case a query does not contain a `$skiptoken`. +It either returns an instance of https://github.com/SAP/olingo-jpa-processor-v4/blob/main/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPage.java[`JPAODataPage`], +which describes the subset of the requested entities that shall be returned or null, so all entities are returned. +If a request has a `$skiptoken` method `getNextPage` is called. In case this method does not return a `JPAODataPage` instance, which describes the next page to be retrieved, a _http 410, "Gone"_, exception is raised. + +As a paging provider connects to multiple requests, it is put to the session context: + +[source,java] +---- + @Bean + public JPAODataSessionContextAccess sessionContext(@Autowired final EntityManagerFactory emf) + throws ODataException { + + return JPAODataServiceContext.with() + ... + .setPagingProvider(new PagingProvider(buildPagingProvider()))//<1> + ... + } + + private PagingProvider buildPagingProvider() { //<2> + final Map pageSizes = new HashMap<>(); + pageSizes.put("People", 10); + + return new PagingProvider(pageSizes); + } +---- + +<1> An instance of the paging provider set in the session context. +<2> Creation of a Map containing a page size for each relevant entity set. + +Depending on the usage of the service, three cases can be distinguished, which have an increasing complexity. +They are discussed below. Even so the first scenario (one service instance using one thread) is not very likely, +it is worth to read the chapter, as it contains some general hints. + +== Single service instance single thread + +Implementing server driven paging, we must answer some questions. The first, general question, is how the skip-token should look like. +There are two obvious options: + +. The skip-token is a string that contains all the information needed to build the next page and to determine the last page. +. The skip-token is a random key. + +Both have drawbacks. If we provide a string, the client can manipulate it, so the server must check e.g., that the client does not request to many entities. +If a random skip-token is used, the server must store information about the query. + +For this tutorial we use the second option, as it seams to be easier to implement. So, lets have a look at the next questions that need to be answered: + +. How to store the necessary information to build a query from the skip-token? +. How to prevent to many open skip-token and create a memory leak? +. Can skip-token be used multiple times? +. What page sizes should be used? + +For this case, single service instance and just one thread, we can cache the necessary information in a `Map` with the skip-token as key. +To limit the memory consumption, we add a `Queue` that gives the skip-token an order. This enable us to remove the oldest entry, if the +cache limit is reached. + +[NOTE] +==== +The example is guided by https://github.com/SAP/olingo-jpa-processor-v4/blob/main/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java[JPAExamplePagingProvider], +which is part of `odata-jpa-processor` +==== + +The frame of out paging provider will look as follows: + +[source,java] +---- +public class PagingProvider implements JPAODataPagingProvider { + + private static final int BUFFER_SIZE = 500; //<1> + private final Map pageCache; //<2> + private final Queue index; //<3> + private final Map maxPageSizes; //<4> + + public PagingProvider(final Map pageSizes) { //<4> + maxPageSizes = Collections.unmodifiableMap(pageSizes); + pageCache = new HashMap<>(BUFFER_SIZE); + index = new LinkedList<>(); + } + + ... +} +---- +<1> Definition of the size of the cache. So, we wont have more then 500 skip-token in the cache. +<2> Map to cache the query information. +<3> Queue to control the cache limit. +<4> Map to store the page sizes per entity type, which is provided when an instance of the paging provider is created. + +If we look carefully at the first part of the implementation, we see that we need a class that takes the information +needed to create the next page: + +[source,java] +---- + private static record CacheEntry(Long last, //<1> + JPAODataPage page) {} //<2> +---- +<1> The last index to be returned for the request. +<2> The page provided. + +[NOTE] +==== +If the cache stores to last top value, it could happen that entries are missed in case they are created while a +client retrieves page by page. Nevertheless, as determine the last top include a count query, so a round trip to the database, +this information is not calculated again. +==== + +Having done this preparation, we can start to implement `getFirstPage` and `getNextPage`. + +[WARNING] +==== +With 2.1.0 `JPAODataPagingProvider` got a new set of methods. Do not implement the old, deprecated once. + +==== + +First things first. Let's implement `getFirstPage`: + +[source,java] +---- + @Override + public Optional getFirstPage( + final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, + final UriInfo uriInfo, //<1> + @Nullable final Integer preferredPageSize, + final JPACountQuery countQuery, + final EntityManager em) throws ODataApplicationException { + + final UriResource root = uriInfo.getUriResourceParts().get(0); //<1> + // Paging will only be done for Entity Sets. It may also be needed for functions + if (root instanceof final UriResourceEntitySet entitySet) { + // Check if Entity Set shall be packaged + final Integer maxSize = maxPageSizes.get(entitySet.getEntitySet().getName()); + if (maxSize != null) { + // Read $top and $skip + final Integer skipValue = uriInfo.getSkipOption() != null ? uriInfo.getSkipOption().getValue() : 0; + final Integer topValue = uriInfo.getTopOption() != null ? uriInfo.getTopOption().getValue() : null; + // Determine page size + final Integer pageSize = preferredPageSize != null && preferredPageSize < maxSize ? preferredPageSize : maxSize; //<2> + if (topValue != null && topValue <= pageSize) //<3> + return Optional.of(new JPAODataPage(uriInfo, skipValue, topValue, null)); + // Determine end of list + final Long maxResults = countQuery.countResults(); //<4> + final Long count = topValue != null && (topValue + skipValue) < maxResults + ? topValue.longValue() : maxResults - skipValue; //<5> + final Long last = topValue != null && (topValue + skipValue) < maxResults + ? (topValue + skipValue) : maxResults; //<6> + // Create a unique skip token if needed + String skipToken = null; + if (pageSize < count) + skipToken = UUID.randomUUID().toString(); //<7> + // Create page information + final JPAODataPage page = new JPAODataPage(uriInfo, skipValue, pageSize, skipToken); + // Cache page to be able to fulfill next link based request + if (skipToken != null) + addToCache(page, last); //<8> + return Optional.of(page); + } + } + return Optional.empty(); + } +---- + +<1> UriInfo is a class provided by Olingo. It contains the parsed request information. The implementation looks at +the root of the request to decide if paging shall be considered. This may not always be the right thing, as +for chains of navigations the last part is retrieved from the database and will get the page limitation, based on the root. +<2> A client can ask for certain page size by using `odata.maxpagesize` preference header. The paging provider shall respect this as +long as the value is lower the maximum supported. +<3> Skip further processing if no paging is required. +<4> Determine maximum number of results that can be expected. +<5> Determine requested number of results. Needed to decide if paging is needed. +<6> Determine the last result requested. Needed to be able to stop the paging. +<7> If paging is required, create a random skip-token. +<8> Add the page to the cache. + +Now we must implement method `addToCache`, which is responsible to organize it: + +[source, java] +---- + private void addToCache(final JPAODataPage page, final Long count) { + if (pageCache.size() == BUFFER_SIZE) //<1> + pageCache.remove(index.poll()); + + pageCache.put((String) page.skipToken(), new CacheEntry(count, page)); + index.add((String) page.skipToken()); + } +---- + +<1> If the cache is full, the oldest is removed. + +With the implementation we already have, plus an empty one for `getNextPage`, we can test the paging and see +if the skip-token is provided in the response of the request. + +Last step is to implement `getNextPage`: + +[source, java] +---- + @Override + public Optional getNextPage( + @Nonnull final String skipToken, + final OData odata, + final ServiceMetadata serviceMetadata, + final JPARequestParameterMap requestParameter, + final EntityManager em) { + final CacheEntry previousPage = pageCache.get(skipToken.replace("'", "")); //<1> + if (previousPage != null) { + // Calculate next page + final Integer skip = previousPage.page().skip() + previousPage.page().top(); + // Create a new skip token, if next page is not the last one + String nextToken = null; + if (skip + previousPage.page().top() < previousPage.last()) //<2> + nextToken = UUID.randomUUID().toString(); + final int top = (int) ((skip + previousPage.page().top()) < previousPage.last() + ? previousPage.page().top() : previousPage.last() - skip); //<3> + final JPAODataPage page = new JPAODataPage(previousPage.page().uriInfo(), skip, top, nextToken); + if (nextToken != null) + addToCache(page, previousPage.last()); + return Optional.of(page); + } + // skip token not found => let JPA Processor handle this by return http.gone + return Optional.empty(); + } +---- + +<1> Look for query information in the cache. +<2> Check if this is the last page. +<3> Calculate the value of $top, which may be different for the last page. + +We are done and can test our complete server driven paging. + +== Single service instance multiple threads + +The main difference, when we go from a single thread to multiple threads, is that we get a race condition in the cache handling. +This becomes harder as we have two collections, which must be kept in sync. We can solve this by synchronizing the cache accesses: + +[source,java] +---- +public class JPAExamplePagingProvider implements JPAODataPagingProvider { + + private static final Object lock = new Object(); //<1> + + ... + + + private void addToCache(final JPAODataPage page, final Long count) { + + synchronized (lock) { //<2> + if (pageCache.size() == cacheSize) + pageCache.remove(index.poll()); + + pageCache.put((String) page.skipToken(), new CacheEntry(count, page)); + index.add((String) page.skipToken()); + } + } + + ... +} + +---- +<1> Introduction of a lock object needed for the synchronization. +<2> Synchronization of the cache access. + +== Multiple service instances +In case we have multiple instances of our service, the standard situation for microservices, we usually do not know which instance +will handle a request. It may or may not be the same that has handled the request before. This holds also true for server driven paging. +Therefore, we need to make the query information available for all instances, which requires a central backing service +that can be reached from each instance of our service. Two options will be described below. + +One remark needs to be given up front. The processing of an OData request requires an instance of interface _UriInfo_. +Unfortunately, _UriInfoImpl_ is not serializable. Instead of that we will store the URL +and make use of Olingo's URL parser to get the _UriInfo_ back. + + +=== Use the database +We have already a backing service in place, the database. To store the pages, we must create a corresponding table: + +[source,sql] +---- +CREATE TABLE "Trippin"."Pages" ( + "token" varchar(255) NOT NULL, + "skip" int4 NOT NULL, + "top" int4 NOT NULL, + "count" int4 NOT NULL, + "baseUri" varchar(1000) NULL, + "oDataPath" varchar(1000) NULL, + "queryPath" varchar(1000) NULL, + "fragments" varchar(1000) NULL, + CONSTRAINT "Pages_pkey" PRIMARY KEY (token) +); +---- + +To access the table, we create the corresponding entity: + +[source,java] +---- +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; + +@EdmIgnore +@Entity +@Table(schema = "\"Trippin\"", name = "\"Pages\"") +public class Pages { + + @Id + @Column(name = "\"token\"") + private String token; + + @Column(name = "\"skip\"") + private Integer skip; + + @Column(name = "\"top\"") + private Integer top; + + @Column(name = "\"last\"") + private Integer last; + + @Column(name = "\"baseUri\"") + private String baseUri; + + @Column(name = "\"oDataPath\"") + private String oDataPath; + + @Column(name = "\"queryPath\"") + private String queryPath; + + @Column(name = "\"fragments\"") + private String fragments; + + public Pages() { + // Needed for JPA + } + + public Pages(final String token, final Integer skip, final Integer top, final Integer last, final String baseUri, + final String oDataPath, final String queryPath, final String fragments) { + super(); + this.token = token; + this.skip = skip; + this.top = top; + this.last = last; + this.baseUri = baseUri; + this.oDataPath = oDataPath; + this.queryPath = queryPath; + this.fragments = fragments; + } + + public Pages(final Pages previousPage, final int skip, final String token) { + super(); + this.token = token; + this.skip = skip; + this.top = previousPage.top; + this.last = previousPage.last; + this.baseUri = previousPage.baseUri; + this.oDataPath = previousPage.oDataPath; + this.queryPath = previousPage.queryPath; + this.fragments = previousPage.fragments; + } + + public String getToken() { + return token; + } + + public Integer getSkip() { + return skip; + } + + public Integer getTop() { + return top; + } + + public String getBaseUri() { + return baseUri; + } + + public String getODataPath() { + return oDataPath; + } + + public String getQueryPath() { + return queryPath; + } + + public String getFragments() { + return fragments; + } + + public Integer getLast() { + return last; + } +} +---- + +To store the page information on the database we need to replace the call of `addToCache` +from above by calling a method to insert a new row: + +[source,java] +---- + @Override + public Optional getFirstPage( + final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, + final UriInfo uriInfo, + @Nullable final Integer preferredPageSize, + final JPACountQuery countQuery, + final EntityManager em) throws ODataApplicationException { + + ... + if(skipToken != null) + savePage(pathInformation, em, last, page); //<1> + ... + } + +---- +<1> Calling method to save a page on the database. + +The `savePage` looks as follows: + +[source,java] +---- + private void savePage(final JPAODataPathInformation pathInformation, final EntityManager em, final Long last, + final JPAODataPage page) { + + if (page.skipToken() != null) { + final Pages pagesItem = new Pages((String) page.skipToken(), page.skip(), page.top(), last > Integer.MAX_VALUE + ? Integer.MAX_VALUE : last.intValue(), + pathInformation.baseUri(), pathInformation.oDataPath(), pathInformation.queryPath(), + pathInformation.fragments()); + em.getTransaction().begin(); + em.persist(pagesItem); + em.getTransaction().commit(); + } + } +---- + +Having done that, we have to go ahead and handle the retrieval of the next page: + +[source,java] +---- + @Override + public Optional getNextPage(@Nonnull final String skipToken, final OData odata, + final ServiceMetadata serviceMetadata, final JPARequestParameterMap requestParameter, final EntityManager em) { + final Pages previousPage = em.find(Pages.class, skipToken.replace("'", "")); //<1> + if (previousPage != null) { + try { + final UriInfo uriInfo = new Parser(serviceMetadata.getEdm(), odata) + .parseUri(previousPage.getODataPath(), previousPage.getQueryPath(), previousPage.getFragments(), + previousPage.getBaseUri()); //<2> + final Integer skipValue = previousPage.getSkip() + previousPage.getTop(); + final Integer topValue = skipValue + previousPage.getTop() > previousPage.getLast() + ? previousPage.getLast() - skipValue : previousPage.getTop(); + final String newToken = skipValue + topValue < previousPage.getLast() ? UUID.randomUUID().toString() : null; + final JPAODataPage nextPage = new JPAODataPage(uriInfo, skipValue, topValue, newToken); + replacePage(previousPage, nextPage, em); //<3> + return Optional.of(nextPage); + } catch (final ODataException e) { + return Optional.empty(); + } + } + return Optional.empty(); + } +---- +<1> Reading the previous page. +<2> Calling Olingo's URI parser to get a UriInfo. +<3> Save the next page on the database. + +For this variant we want to remove the already processed page on the database be the new page. This is the reason why we cannot use `savePage` here: + +[source,java] +---- + private void replacePage(final Pages previousPage, final JPAODataPage newPage, final EntityManager em) { + + em.getTransaction().begin(); + em.remove(previousPage); + if (newPage.skipToken() != null) { + final Pages pagesItem = new Pages(previousPage, newPage.skip(), (String) newPage.skipToken()); + em.persist(pagesItem); + } + em.getTransaction().commit(); + } +---- + +[WARNING] +==== +We cannot force the client to read all pages. That is, we must take into account that over the time the Page table +get bigger and bigger, filled with garbage. To get rid of it, we have to have a clean-up job, removing old entries. + +==== + + +=== Use an external cache +As an alternative we can use an external cache that offers a lifetime for its entries. There might be other option, but +for this tutorial, we use Redis. It will not be described how to set it up. There are a lot of tutorial out there that handle this topic. +For the tutorial we assume Redis it is available. + +Even so Spring offers an encapsulation to access Redis, we use Jedis as Java API. We get it by adding the following dependency to our POM: + +[source,XML] +---- + + redis.clients + jedis + +---- + +To be able to use Jedis within our paging provider we first must create a JedisPool. We +extend class ProcessorConfiguration for this: + +[source, java] +---- +public class ProcessorConfiguration { + public static final String REQUEST_ID = "RequestId"; + public static final String REDIS = "Redis"; //<1> + + @Bean + JedisPool jedisPool() { + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setJmxEnabled(false); + return new JedisPool(poolConfig, "localhost", 6379); //<2> + } +---- + +<1> Constant used as identifier for the JedisPool in the request context. +<2> Creation of the JedisPool with host and port. + +Next, we need to make it available: + +[source,java] +---- + JPAODataRequestContext requestContext(@Autowired final JedisPool jedisPool) { + return JPAODataRequestContext.with() + ... + .setParameter(REDIS, jedisPool) //<1> + ... + .build(); + } + +---- +<1> Add JedisPool instance as a parameter to the request context + +We store the page information as key - value pairs. We start with a set of constants containing the keys. We also have to adopt +the interface of `savePage` + +[source,java] +---- + private static final int EXPIRES_AFTER = 300; // <1> + private static final int MAX_SIZE = 50; // Page size + private static final String FRAGMENTS = "fragments"; + private static final String QUERY_PATH = "queryPath"; + private static final String O_DATA_PATH = "oDataPath"; + private static final String BASE_URI = "baseUri"; + private static final String LAST = "last"; + private static final String TOP = "top"; + private static final String SKIP = "skip"; + + ... + + @Override + public Optional getFirstPage( + final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, + final UriInfo uriInfo, + @Nullable final Integer preferredPageSize, + final JPACountQuery countQuery, + final EntityManager em) throws ODataApplicationException { + + ... + if(skipToken != null) + savePage(pathInformation, last, page, requestParameter.get(ProcessorConfiguration.REDIS));//<2> + ... + } + +---- +<1> Lifetime in seconds. +<2> Using the new interface of `savePage`. + +[source,java] +---- + private void savePage(final JPAODataPathInformation pathInformation, final Long last, + final JPAODataPage page, final Object pool) { + + if (page.skipToken() != null + && pool instanceof final JedisPool jedisPool) { + try (var jedis = jedisPool.getResource()) { + final Map values = new HashMap<>(); + putIfNotNull(values, SKIP, page.skip()); + putIfNotNull(values, TOP, page.top()); + putIfNotNull(values, LAST, last > Integer.MAX_VALUE ? Integer.MAX_VALUE : last.intValue()); + putIfNotNull(values, BASE_URI, pathInformation.baseUri()); + putIfNotNull(values, O_DATA_PATH, pathInformation.oDataPath()); + putIfNotNull(values, QUERY_PATH, pathInformation.queryPath()); + putIfNotNull(values, FRAGMENTS, pathInformation.fragments()); + + final Pipeline pipeline = jedis.pipelined(); + pipeline.hset((String) page.skipToken(), values); + pipeline.expire((String) page.skipToken(), EXPIRES_AFTER); + pipeline.sync(); + } catch (final JedisConnectionException e) { + log.error("Redis exception", e); + throw e; + } + } + } + + private void putIfNotNull(@Nonnull final Map values, @Nonnull final String name, + @Nullable final Integer value) { + if (value != null) + values.put(name, Integer.toString(value)); + + } + + private void putIfNotNull(@Nonnull final Map values, @Nonnull final String name, + @Nullable final String value) { + if (value != null) + values.put(name, value); + } +---- + +Also `getNextPage` has to be adopted: + +[source, java] +---- + @Override + public Optional getNextPage(@Nonnull final String skipToken, final OData odata, + final ServiceMetadata serviceMetadata, final JPARequestParameterMap requestParameter, final EntityManager em) { + final Map previousPage = getPreviousPage(skipToken, requestParameter.get( + ProcessorConfiguration.REDIS)); //<1> + if (previousPage.size() > 0) { + try { + final UriInfo uriInfo = new Parser(serviceMetadata.getEdm(), odata) + .parseUri(getString(previousPage, O_DATA_PATH), getString(previousPage, QUERY_PATH), getString(previousPage, + FRAGMENTS), getString(previousPage, BASE_URI)); + final Integer skipValue = getInteger(previousPage, SKIP) + getInteger(previousPage, TOP); + final Integer topValue = skipValue + getInteger(previousPage, TOP) > getInteger(previousPage, LAST) + ? getInteger(previousPage, LAST) - skipValue : getInteger(previousPage, TOP); + final String newToken = skipValue + topValue < getInteger(previousPage, LAST) ? UUID.randomUUID().toString() + : null; + final JPAODataPage nextPage = new JPAODataPage(uriInfo, skipValue, topValue, newToken); + replacePage(previousPage, nextPage, requestParameter.get(ProcessorConfiguration.REDIS)); //<2> + return Optional.of(nextPage); + } catch (final ODataException e) { + Optional.empty(); + } + } + return Optional.empty(); + } + + private Map getPreviousPage(final String skipToken, final Object pool) { + if (skipToken != null + && pool instanceof final JedisPool jedisPool) { + try (var jedis = jedisPool.getResource()) { + final Map values = jedis.hgetAll(skipToken.replace("'", "")); + if (values != null) + return values; + } + } + return Collections.emptyMap(); + } + + @CheckForNull + private String getString(@Nonnull final Map values, @Nonnull final String name) { + return values.get(name); + } + + @Nonnull + private Integer getInteger(@Nonnull final Map values, @Nonnull final String name) { + return Integer.valueOf(Objects.requireNonNull(values.get(name), "Missing value for " + name)); + } +---- + +<1> Retrieval of previous page. +<2> Writing the next page. + +[WARNING] +==== +Using Redis helps us to keep our cache clean, but, as usual, we do not get this for free. We have to operate another component. + +==== diff --git a/jpa-tutorial/Questions/WhatIsTheProblemWithInAndExist.adoc b/jpa-tutorial/Questions/WhatIsTheProblemWithInAndExist.adoc index a209ad0f8..665a9cc2f 100644 --- a/jpa-tutorial/Questions/WhatIsTheProblemWithInAndExist.adoc +++ b/jpa-tutorial/Questions/WhatIsTheProblemWithInAndExist.adoc @@ -20,7 +20,7 @@ INSERT INTO "Product" VALUES (4, 'Trousers', 'white', 2); INSERT INTO "Product" VALUES (5, 'Shirt', 'red', 1); ---- -So, there is a table "Product" with some columns, like the color of the product. Let's assume we like to find all the products that are either blue or white. The following SQL query would do the job: +So, there is a table "Product" with some columns, like the color of the product. Let's assume we like to find all the products that are either _blue_ or _white_. The following SQL query would do the job: [source,sql] ---- @@ -187,9 +187,9 @@ SELECT COUNT(*) FROM "AdministrativeDivision" t0 WHERE (t0."CodePublisher", t0."CodeID", t0."DivisionCode") IN ( SELECT t1."CodePublisher",t1."ParentCodeID", t1."ParentDivisionCode" - FROM "OLINGO"."AdministrativeDivision" t1 - GROUP BY t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" - HAVING (COUNT(t1."DivisionCode") >= 2)) + FROM "OLINGO"."AdministrativeDivision" t1 + GROUP BY t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" + HAVING (COUNT(t1."DivisionCode") >= 2)) ---- @@ -200,12 +200,12 @@ SELECT COUNT(*) FROM "AdministrativeDivision" t0 WHERE EXISTS ( SELECT t1."CodePublisher" - FROM "AdministrativeDivision" t1 - WHERE t0."CodePublisher" = "CodePublisher" - AND t0."CodeID" = t1."ParentCodeID" - AND t0."DivisionCode" = t1."ParentDivisionCode" - GROUP BY t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" - HAVING (COUNT(t1."DivisionCode") >= 2)) + FROM "AdministrativeDivision" t1 + WHERE t0."CodePublisher" = "CodePublisher" + AND t0."CodeID" = t1."ParentCodeID" + AND t0."DivisionCode" = t1."ParentDivisionCode" + GROUP BY t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" + HAVING (COUNT(t1."DivisionCode") >= 2)) ---- The query should count the number of administrative divisions that have at least two subdivisions. @@ -223,3 +223,21 @@ The following execution times could be measured: This is not a scientific measurement, but gives a good impression about the difference or the performance increase an IN can give. So, in case such a query is required and the relation comprises multiple columns, we cannot use the Criteria Builder, we have to generate a parameterized query. + +One last remark: In case we look for result that do not match in the results of the sub-query, we have to use NOT IN or +NOT EXISTS, respectively. The variant behave a bit different, if the result set of the sub-query contains Null values. +NOT EXISTS behaves as expected, but NOT IN will not return a result or create an error. So, for NOT IN we have to add +Null check e.g., to find all the administrative divisions that have no subdivisions: +[source,sql] +---- +SELECT count(*) + FROM "AdministrativeDivision" t0 + WHERE (t0."CodePublisher", t0."CodeID", t0."DivisionCode") NOT IN ( + SELECT t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" + FROM "AdministrativeDivision" t1 + WHERE t1."CodePublisher" IS NOT NULL + AND t1."ParentCodeID" IS NOT NULL + AND t1."ParentDivisionCode" IS NOT NULL + GROUP BY t1."CodePublisher", t1."ParentCodeID", t1."ParentDivisionCode" + HAVING (COUNT(t1."CodePublisher") <> 0)) +---- \ No newline at end of file diff --git a/jpa-tutorial/QuickStart/QuickStart.adoc b/jpa-tutorial/QuickStart/QuickStart.adoc index a8ae653aa..4123d5303 100644 --- a/jpa-tutorial/QuickStart/QuickStart.adoc +++ b/jpa-tutorial/QuickStart/QuickStart.adoc @@ -46,7 +46,7 @@ This should contain the information about archetype: com.sap.olingo odata-jpa-archetype-spring - 2.0.0 + 2.1.0 diff --git a/jpa/odata-jpa-annotation/.project b/jpa/odata-jpa-annotation/.project index 800458209..3e42f49f5 100644 --- a/jpa/odata-jpa-annotation/.project +++ b/jpa/odata-jpa-annotation/.project @@ -32,12 +32,9 @@ - org.eclipse.jem.workbench.JavaEMFNature - org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature org.eclipse.wst.common.project.facet.core.nature - org.whitesource.eclipse.plugin.WSNature diff --git a/jpa/odata-jpa-coverage/.project b/jpa/odata-jpa-coverage/.project new file mode 100644 index 000000000..86e0d38bf --- /dev/null +++ b/jpa/odata-jpa-coverage/.project @@ -0,0 +1,17 @@ + + + jpa-coverage + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/jpa/odata-jpa-coverage/pom.xml b/jpa/odata-jpa-coverage/pom.xml index cf230382c..87ff98e04 100644 --- a/jpa/odata-jpa-coverage/pom.xml +++ b/jpa/odata-jpa-coverage/pom.xml @@ -4,7 +4,7 @@ com.sap.olingo odata-jpa - 2.1.0-SNAPSHOT + 2.1.0-SNAPSHOT odata-jpa-coverage @@ -35,6 +35,7 @@ com.sap.olingo odata-jpa-test + compile com.sap.olingo @@ -87,4 +88,4 @@ pom - \ No newline at end of file + diff --git a/jpa/odata-jpa-metadata/.project b/jpa/odata-jpa-metadata/.project index 3de8bd583..29cac8c24 100644 --- a/jpa/odata-jpa-metadata/.project +++ b/jpa/odata-jpa-metadata/.project @@ -15,16 +15,6 @@ - - org.eclipse.wst.validation.validationbuilder - - - - - org.whitesource.eclipse.plugin.WSbuilder - - - org.eclipse.m2e.core.maven2Builder @@ -32,7 +22,6 @@ - org.eclipse.jem.workbench.JavaEMFNature org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature diff --git a/jpa/odata-jpa-metadata/pom.xml b/jpa/odata-jpa-metadata/pom.xml index c798a5b50..da1ecf585 100644 --- a/jpa/odata-jpa-metadata/pom.xml +++ b/jpa/odata-jpa-metadata/pom.xml @@ -5,9 +5,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - com.sap.olingo - odata-jpa - 2.1.0-SNAPSHOT + com.sap.olingo + odata-jpa + 2.1.0-SNAPSHOT odata-jpa-metadata diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAnnotatable.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAnnotatable.java index e5083ad90..93530dde0 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAnnotatable.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAnnotatable.java @@ -5,9 +5,13 @@ import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AliasAccess; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.TermAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; /** + * Gives access to OData annotations * * @author Oliver Grande * @since 1.1.1 @@ -25,5 +29,77 @@ public interface JPAAnnotatable { @CheckForNull CsdlAnnotation getAnnotation(@Nonnull String alias, @Nonnull String term) throws ODataJPAModelException; + /** + * Returns the value of a given property of an annotation. E.g., + * getAnnotationValue("Capabilities", "FilterRestrictions", "filterable") + *

+ * The value is returned as instance of corresponding type, with the following features + *

    + *
  • Enumerations are returned as strings
  • + *
  • Path are returned as {@link JPAPath}
  • + *
  • Navigation path are returned as {@link JPAAssociationPath}
  • + *
+ * @param alias of the vocabulary. + * @param term of the annotation in question. + * @param property the value is requested for. + * @return The value of the property + * @throws ODataJPAModelException + * @since 2.1.0 + */ + @CheckForNull + Object getAnnotationValue(@Nonnull String alias, @Nonnull String term, @Nonnull String property) + throws ODataJPAModelException; + + /** + * Returns the value of a given property of an annotation. E.g., + * getAnnotationValue("Capabilities", "FilterRestrictions", "filterable", Boolean.class) + *

+ * The value is returned as instance of corresponding type, with the following features + *

    + *
  • Enumerations are returned as strings
  • + *
  • Path are returned as {@link JPAPath}
  • + *
  • Navigation path are returned as {@link JPAAssociationPath}
  • + *
+ * @param Java type of annotation. + * @param alias of the vocabulary. + * @param term of the annotation in question. + * @param propertyName the value is requested for. + * @param type java type of property e.g., Boolean.class. + * @return + * @throws ODataJPAModelException + * @since 2.1.0 + */ + @SuppressWarnings("unchecked") + @CheckForNull + default T getAnnotationValue(@Nonnull final String alias, @Nonnull final String term, + @Nonnull final String property, @Nonnull final Class type) throws ODataJPAModelException { + return (T) getAnnotationValue(alias, term, property); + } + + /** + * Returns the value of a given property of an annotation. E.g., + * getAnnotationValue(Aliases.CAPABILITIES, Terms.FILTER_RESTRICTIONS, FilterRestrictionsProperties.FILTERABLE, Boolean.class) + *

+ * The value is returned as instance of corresponding type, with the following features + *

    + *
  • Enumerations are returned as strings
  • + *
  • Path are returned as {@link JPAPath}
  • + *
  • Navigation path are returned as {@link JPAAssociationPath}
  • + *
+ * @param Java type of annotation. + * @param alias of the vocabulary. + * @param term of the annotation in question. + * @param property the value is requested for. + * @param type java type of property e.g., Boolean.class. + * @return + * @throws ODataJPAModelException + * @since 2.1.0 + */ + @CheckForNull + default T getAnnotationValue(@Nonnull final AliasAccess alias, @Nonnull final TermAccess term, + @Nonnull final PropertyAccess property, @Nonnull final Class type) throws ODataJPAModelException { + return getAnnotationValue(alias.alias(), term.term(), property.property(), type); + } + String getExternalName(); } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAttribute.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAttribute.java index d76a593cd..95f0b48ad 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAttribute.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAAttribute.java @@ -86,19 +86,29 @@ public interface JPAAttribute extends JPAElement, JPAAnnotatable { */ public boolean isCollection(); - public boolean isComplex(); + public default boolean isComplex() { + return false; + } /** * True if the property has an enum as type * @return */ - public boolean isEnum(); + public default boolean isEnum() { + return false; + } - public boolean isEtag(); + public default boolean isEtag() { + return false; + } - public boolean isKey(); + public default boolean isKey() { + return false; + } - public boolean isSearchable(); + public default boolean isSearchable() { + return false; + } public boolean hasProtection(); diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java index b33641e6e..62e27d70c 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java @@ -88,7 +88,8 @@ public enum MessageKeys implements ODataJPAMessageKey { PATH_ELEMENT_NOT_EMBEDDABLE, DB_TYPE_NOT_DETERMINED, FILE_NOT_FOUND, - MISSING_ONE_TO_ONE_ANNOTATION; + MISSING_ONE_TO_ONE_ANNOTATION, + ENTITY_TYPE_NOT_FOUND; @Override public String getKey() { diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelInternalException.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelInternalException.java new file mode 100644 index 000000000..87c8bbb1c --- /dev/null +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelInternalException.java @@ -0,0 +1,12 @@ +package com.sap.olingo.jpa.metadata.core.edm.mapper.exception; + +public class ODataJPAModelInternalException extends RuntimeException { + + private static final long serialVersionUID = 1L; + public final ODataJPAModelException rootCause; + + public ODataJPAModelInternalException(final ODataJPAModelException rootCause) { + super(); + this.rootCause = rootCause; + } +} diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySet.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySet.java index c51608ddc..d4ec2eb07 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySet.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySet.java @@ -1,7 +1,11 @@ package com.sap.olingo.jpa.metadata.core.edm.mapper.impl; +import static com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException.MessageKeys.ENTITY_TYPE_NOT_FOUND; + import java.util.List; +import javax.annotation.CheckForNull; + import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; @@ -37,6 +41,7 @@ final class IntermediateEntitySet extends IntermediateTopLevelEntity implements * @return */ @Override + @CheckForNull public JPAEntityType getODataEntityType() { if (entityType.asTopLevelOnly()) return (JPAEntityType) entityType.getBaseType(); @@ -56,7 +61,7 @@ protected synchronized void lazyBuildEdmItem() throws ODataJPAModelException { postProcessor.processEntitySet(this); edmEntitySet = new CsdlEntitySet(); - final CsdlEntityType edmEt = ((IntermediateEntityType) getODataEntityType()).getEdmItem(); + final var edmEt = determineEdmType(); edmEntitySet.setName(getExternalName()); edmEntitySet.setType(buildFQN(edmEt.getName())); @@ -85,4 +90,11 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw return filterAnnotation(alias, term); } + private CsdlEntityType determineEdmType() throws ODataJPAModelException { + final IntermediateEntityType type = (IntermediateEntityType) getODataEntityType(); + if (type != null) + return type.getEdmItem(); + throw new ODataJPAModelException(ENTITY_TYPE_NOT_FOUND, getInternalName()); + } + } \ No newline at end of file diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityType.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityType.java index 0ef16e410..979d3fd37 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityType.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityType.java @@ -30,6 +30,7 @@ import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlDynamicExpression; import org.apache.olingo.server.api.uri.UriResourceProperty; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEntityType; @@ -42,8 +43,8 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAQueryExtension; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelInternalException; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateEntityTypeAccess; /** @@ -87,10 +88,48 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw return filterAnnotation(alias, term); } + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + + try { + return Optional.ofNullable(getAnnotation(alias, term)) + .map(CsdlAnnotation::getExpression) + .map(expression -> getAnnotationValue(property, expression)) + .orElse(null); + } catch (final ODataJPAModelInternalException e) { + throw e.rootCause; + } + } + + @Override + protected Object getAnnotationDynamicValue(final String property, final CsdlDynamicExpression expression) + throws ODataJPAModelInternalException { + try { + if (expression.isRecord()) { + // This may create a problem if the property in question is a record itself. Currently non is supported in + // standard + final var propertyValue = findAnnotationPropertyValue(property, expression); + if (propertyValue.isPresent()) { + return getAnnotationValue(property, propertyValue.get()); + } + } else if (expression.isCollection()) { + return getAnnotationCollectionValue(expression); + } else if (expression.isPropertyPath()) { + return getPath(expression.asPropertyPath().getValue()); + } else if (expression.isNavigationPropertyPath()) { + return getAssociationPath(expression.asNavigationPropertyPath().getValue()); + } + return null; + } catch (final ODataJPAModelException e) { + throw new ODataJPAModelInternalException(e); + } + } + @Override public Optional getAttribute(final String internalName) throws ODataJPAModelException { buildEdmTypeIfEmpty(); - final Optional a = super.getAttribute(internalName); + final var a = super.getAttribute(internalName); if (a.isPresent()) return a; return getKey(internalName); @@ -99,7 +138,7 @@ public Optional getAttribute(final String internalName) throws ODa @Override public Optional getAttribute(final UriResourceProperty uriResourceItem) throws ODataJPAModelException { buildEdmTypeIfEmpty(); - final Optional a = super.getAttribute(uriResourceItem); + final var a = super.getAttribute(uriResourceItem); if (a.isPresent()) return a; return getKey(uriResourceItem); @@ -107,7 +146,7 @@ public Optional getAttribute(final UriResourceProperty uriResource @Override public JPACollectionAttribute getCollectionAttribute(final String externalName) throws ODataJPAModelException { - final JPAPath path = getPath(externalName); + final var path = getPath(externalName); if (path != null && path.getLeaf() instanceof final JPACollectionAttribute collectionAttribute) return collectionAttribute; return null; @@ -115,13 +154,13 @@ public JPACollectionAttribute getCollectionAttribute(final String externalName) @Override public String getContentType() throws ODataJPAModelException { - final IntermediateSimpleProperty stream = getStreamProperty(); + final var stream = getStreamProperty(); return stream.getContentType(); } @Override public JPAPath getContentTypeAttributePath() throws ODataJPAModelException { - final String propertyInternalName = getStreamProperty().getContentTypeProperty(); + final var propertyInternalName = getStreamProperty().getContentTypeProperty(); if (propertyInternalName == null || propertyInternalName.isEmpty()) { return null; } @@ -131,7 +170,7 @@ public JPAPath getContentTypeAttributePath() throws ODataJPAModelException { @Override public Optional getDeclaredAttribute(@Nonnull final String internalName) throws ODataJPAModelException { - final Optional a = super.getDeclaredAttribute(internalName); + final var a = super.getDeclaredAttribute(internalName); if (a.isPresent()) return a; return getKey(internalName); @@ -212,7 +251,7 @@ public Optional> getQueryExtension( @Override public List getSearchablePath() throws ODataJPAModelException { - final List allPath = getPathList(); + final var allPath = getPathList(); final List searchablePath = new ArrayList<>(); for (final JPAPath p : allPath) { if (p.getLeaf().isSearchable()) @@ -375,7 +414,7 @@ boolean dbEquals(final String dbCatalog, final String dbSchema, @Nonnull final S } boolean determineAbstract() { - final int modifiers = jpaManagedType.getJavaType().getModifiers(); + final var modifiers = jpaManagedType.getJavaType().getModifiers(); return Modifier.isAbstract(modifiers); } @@ -420,7 +459,7 @@ private void addKeyAttribute(final List intermediateKey, final Lis private CsdlPropertyRef asPropertyRef(final JPAAttribute idAttribute) { // TODO setAlias - final CsdlPropertyRef keyElement = new CsdlPropertyRef(); + final var keyElement = new CsdlPropertyRef(); keyElement.setName(idAttribute.getExternalName()); return keyElement; } @@ -433,10 +472,10 @@ private void buildEdmTypeIfEmpty() throws ODataJPAModelException { private List buildEmbeddedIdKey(final JPAAttribute attribute) throws ODataJPAModelException { - final JPAStructuredType id = ((IntermediateEmbeddedIdProperty) attribute).getStructuredType(); + final var id = ((IntermediateEmbeddedIdProperty) attribute).getStructuredType(); final List keyElements = new ArrayList<>(id.getTypeClass().getDeclaredFields().length); - final Field[] keyFields = id.getTypeClass().getDeclaredFields(); - for (int i = 0; i < keyFields.length; i++) { + final var keyFields = id.getTypeClass().getDeclaredFields(); + for (var i = 0; i < keyFields.length; i++) { id.getAttribute(keyFields[i].getName()).ifPresent(keyElements::add); } return keyElements; @@ -455,7 +494,7 @@ private boolean determineAsEntitySet() { } private boolean determineAsSingleton() { - final EdmEntityType jpaEntityType = this.jpaManagedType.getJavaType().getAnnotation(EdmEntityType.class); + final var jpaEntityType = this.jpaManagedType.getJavaType().getAnnotation(EdmEntityType.class); return jpaEntityType != null && (jpaEntityType.as() == EdmTopLevelElementRepresentation.AS_SINGLETON || jpaEntityType.as() == EdmTopLevelElementRepresentation.AS_SINGLETON_ONLY); } @@ -473,7 +512,7 @@ private Optional> determineExtensio extensionQueryProvider = Optional.of(Optional.empty()); final Optional jpaEntityType = getAnnotation(jpaJavaType, EdmEntityType.class); if (jpaEntityType.isPresent()) { - final Class provider = (Class) jpaEntityType + final var provider = (Class) jpaEntityType .get().extensionProvider(); final Class defaultProvider = EdmQueryExtensionProvider.class; if (provider != null && provider != defaultProvider) @@ -492,7 +531,7 @@ private void determineHasEtag() throws ODataJPAModelException { etagPath = Optional.of(getPath(property.getValue().getExternalName(), false)); } } - if (getBaseType() instanceof IntermediateEntityType baseEntityType) + if (getBaseType() instanceof final IntermediateEntityType baseEntityType) etagPath = Optional.ofNullable(baseEntityType.getEtagPath()); } @@ -523,4 +562,5 @@ private List resolveEmbeddedId(final IntermediateEmbeddedIdPropert throws ODataJPAModelException { return ((IntermediateStructuredType) embeddedId.getStructuredType()).getEdmItem().getProperties(); } + } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateModelElement.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateModelElement.java index f31f62471..3ca21ac60 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateModelElement.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateModelElement.java @@ -8,12 +8,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.provider.CsdlAbstractEdmItem; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression.ConstantExpressionType; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlDynamicExpression; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlExpression; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlPropertyValue; import com.sap.olingo.jpa.metadata.api.JPAEdmMetadataPostProcessor; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmAnnotation; @@ -22,6 +26,7 @@ import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataAnnotatable; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelInternalException; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateModelItemAccess; abstract class IntermediateModelElement implements IntermediateModelItemAccess { @@ -97,8 +102,8 @@ protected List extractEdmModelElements( for (final IntermediateModelElement bufferItem : mappingBuffer.values()) { if (!bufferItem.toBeIgnored) { // NOSONAR - final IntermediateModelElement element = bufferItem; - final CsdlAbstractEdmItem edmItem = element.getEdmItem(); + final var element = bufferItem; + final var edmItem = element.getEdmItem(); if (!element.ignore()) extractionTarget.add((T) edmItem); } @@ -156,7 +161,7 @@ protected void getAnnotations(final List edmAnnotations, final C * @return */ protected final String buildFQTableName(final String schema, final String name) { - final StringBuilder fqt = new StringBuilder(); + final var fqt = new StringBuilder(); if (schema != null && !schema.isEmpty()) { fqt.append(schema); fqt.append("."); @@ -166,13 +171,12 @@ protected final String buildFQTableName(final String schema, final String name) } private void extractAnnotations(final List edmAnnotations, final AnnotatedElement element, - final String internalName) - throws ODataJPAModelException { - final EdmAnnotation jpaAnnotation = element.getAnnotation(EdmAnnotation.class); + final String internalName) throws ODataJPAModelException { + final var jpaAnnotation = element.getAnnotation(EdmAnnotation.class); if (jpaAnnotation != null) { - final CsdlAnnotation edmAnnotation = new CsdlAnnotation(); - final String qualifier = jpaAnnotation.qualifier(); + final var edmAnnotation = new CsdlAnnotation(); + final var qualifier = jpaAnnotation.qualifier(); edmAnnotation.setTerm(jpaAnnotation.term()); edmAnnotation.setQualifier(qualifier.isEmpty() ? null : qualifier); if (!(jpaAnnotation.constantExpression().type() == ConstantExpressionType.Int @@ -244,7 +248,7 @@ protected IntermediateAnnotationInformation getAnnotationInformation() { } protected CsdlAnnotation filterAnnotation(final String alias, final String term) { - final String annotationFqn = annotationInformation.getReferences().convertAlias(alias) + "." + term; + final var annotationFqn = annotationInformation.getReferences().convertAlias(alias) + "." + term; return edmAnnotations.stream() .filter(a -> annotationFqn.equals(a.getTerm())) .findFirst() @@ -256,6 +260,29 @@ protected void retrieveAnnotations(final ODataAnnotatable annotatable, final App edmAnnotations.addAll(provider.getAnnotations(applicability, annotatable, annotationInformation.getReferences())); } + protected Object getAnnotationValue(final String property, final CsdlExpression annotation) + throws ODataJPAModelInternalException { + if (annotation.isDynamic()) { + return getAnnotationDynamicValue(property, annotation.asDynamic()); + } + return getAnnotationConstantValue(annotation.asConstant()); + } + + protected Object getAnnotationDynamicValue(final String property, final CsdlDynamicExpression expression) + throws ODataJPAModelInternalException { + return null; + } + + protected Object getAnnotationConstantValue(final CsdlConstantExpression expression) { + return switch (expression.getType()) { + case Bool -> Boolean.valueOf(expression.getValue()); + case Int -> Integer.valueOf(expression.getValue()); + case String -> expression.getValue(); + case EnumMember -> expression.getValue(); + default -> throw new IllegalArgumentException("Unexpected value: " + expression.getType()); + }; + } + protected Map findJavaAnnotation(final String packageName, final Class clazz) { return findJavaAnnotation(packageName, clazz.getAnnotations()); } @@ -275,4 +302,21 @@ private Map findJavaAnnotation(final String packageName, fin return result; } + protected Optional findAnnotationPropertyValue(final String property, + final CsdlDynamicExpression expression) { + return expression.asRecord() + .getPropertyValues().stream() + .filter(value -> property.equals(value.getProperty())) + .findFirst() + .map(CsdlPropertyValue::getValue); + } + + protected Object getAnnotationCollectionValue(final CsdlDynamicExpression expression) { + final List parthList = new ArrayList<>(); + for (final var item : expression.asCollection().getItems()) { + parthList.add(getAnnotationValue("", item)); + } + return parthList; + } + } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationProperty.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationProperty.java index ae702435e..81c92fe14 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationProperty.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationProperty.java @@ -3,6 +3,7 @@ import static com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException.MessageKeys.MISSING_ONE_TO_ONE_ANNOTATION; import static com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException.MessageKeys.REFERENCED_PROPERTY_NOT_FOUND; +import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.util.ArrayList; @@ -10,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -36,12 +38,15 @@ import org.apache.olingo.commons.api.edm.provider.CsdlOnDelete; import org.apache.olingo.commons.api.edm.provider.CsdlOnDeleteAction; import org.apache.olingo.commons.api.edm.provider.CsdlReferentialConstraint; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlDynamicExpression; import com.sap.olingo.jpa.metadata.api.JPAJoinColumn; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtectedBy; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmTransientPropertyCalculator; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmVisibleFor; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataAnnotatable; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; @@ -50,6 +55,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException.MessageKeys; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelIgnoreException; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelInternalException; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateNavigationPropertyAccess; /** @@ -63,7 +69,7 @@ * @param Type of the parent the navigation belongs to, also called source type */ final class IntermediateNavigationProperty extends IntermediateModelElement implements - IntermediateNavigationPropertyAccess, JPAAssociationAttribute { + IntermediateNavigationPropertyAccess, JPAAssociationAttribute, ODataAnnotatable { private static final Log LOGGER = LogFactory.getLog(IntermediateNavigationProperty.class); @@ -195,31 +201,6 @@ public boolean isCollection() { return jpaAttribute.isCollection(); } - @Override - public boolean isComplex() { - return false; - } - - @Override - public boolean isEnum() { - return false; - } - - @Override - public boolean isEtag() { - return false; - } - - @Override - public boolean isKey() { - return false; - } - - @Override - public boolean isSearchable() { - return false; - } - @Override public boolean isTransient() { return false; @@ -269,6 +250,36 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw return filterAnnotation(alias, term); } + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + + try { + return Optional.ofNullable(getAnnotation(alias, term)) + .map(a -> a.getExpression()) + .map(expression -> getAnnotationValue(property, expression)) + .orElse(null); + } catch (final ODataJPAModelInternalException e) { + throw e.rootCause; + } + + } + + @Override + protected Object getAnnotationDynamicValue(final String property, final CsdlDynamicExpression expression) + throws ODataJPAModelInternalException { + + if (expression.isRecord()) { + // This may create a problem if the property in question is a record itself. Currently non is supported in + // standard + final var propertyValue = findAnnotationPropertyValue(property, expression); + if (propertyValue.isPresent()) { + return getAnnotationValue(property, propertyValue.get()); + } + } + return null; + } + PersistentAttributeType getJoinCardinality() { return jpaAttribute.getPersistentAttributeType(); } @@ -311,7 +322,7 @@ boolean isMapped() { */ private String buildDefaultName(final boolean isSourceOne) throws ODataJPAModelException { - final StringBuilder columnName = new StringBuilder(jpaAttribute.getName()); + final var columnName = new StringBuilder(jpaAttribute.getName()); columnName.append('_'); if (isSourceOne) columnName.append(((IntermediateSimpleProperty) ((IntermediateEntityType) targetType) @@ -323,7 +334,7 @@ private String buildDefaultName(final boolean isSourceOne) throws ODataJPAModelE } private IntermediateJoinColumn buildImplicitJoinColumnPair(final boolean isSourceOne) throws ODataJPAModelException { - final IntermediateJoinColumn intermediateColumn = new IntermediateJoinColumn(buildDefaultName(isSourceOne), + final var intermediateColumn = new IntermediateJoinColumn(buildDefaultName(isSourceOne), fillMissingName()); if (LOGGER.isTraceEnabled()) LOGGER.trace(getExternalName() + ": Add join condition with default name = " + intermediateColumn.toString()); @@ -333,8 +344,8 @@ private IntermediateJoinColumn buildImplicitJoinColumnPair(final boolean isSourc private void buildJoinColumnPairList(final boolean isSourceOne, int implicitColumns, final List result, final JoinColumn[] columns) throws ODataJPAModelException { for (final JoinColumn column : columns) { - final String refColumnName = column.referencedColumnName(); - final String name = column.name(); + final var refColumnName = column.referencedColumnName(); + final var name = column.name(); result.add(buildOneJoinColumnPair(isSourceOne, column)); if (refColumnName == null || refColumnName.isEmpty() || name == null || name.isEmpty()) { implicitColumns += 1; @@ -371,15 +382,15 @@ private void buildJoinColumns(final boolean isSourceOne, final AnnotatedElement private List buildJoinColumnsFromAnnotations(final boolean isSourceOne, final AnnotatedElement annotatedElement) throws ODataJPAModelException { - final int implicitColumns = 0; + final var implicitColumns = 0; final List result = new ArrayList<>(); - final JoinColumn[] columns = annotatedElement.getAnnotation(JoinColumns.class) != null ? annotatedElement + final var columns = annotatedElement.getAnnotation(JoinColumns.class) != null ? annotatedElement .getAnnotation(JoinColumns.class).value() : null; if (columns != null) { buildJoinColumnPairList(isSourceOne, implicitColumns, result, columns); } else { - final JoinColumn column = annotatedElement.getAnnotation(JoinColumn.class); + final var column = annotatedElement.getAnnotation(JoinColumn.class); if (column != null) { result.add(buildOneJoinColumnPair(isSourceOne, column)); } else { @@ -392,10 +403,10 @@ private List buildJoinColumnsFromAnnotations(final boole private List buildJoinColumnsMapped(final String mappedBy) throws ODataJPAModelException { - int implicitColumns = 0; - final List result = invertJoinColumns(targetType.getJoinColumns(mappedBy)); + var implicitColumns = 0; + final var result = invertJoinColumns(targetType.getJoinColumns(mappedBy)); for (final IntermediateJoinColumn intermediateColumn : result) { - final String columnName = intermediateColumn.getName(); + final var columnName = intermediateColumn.getName(); if (columnName == null || columnName.isEmpty()) { implicitColumns += 1; if (implicitColumns > 1) @@ -412,18 +423,23 @@ private void buildJoinConditionInfo() throws ODataJPAModelException { if (jpaAttribute.getJavaMember() instanceof AnnotatedElement && joinColumns.isEmpty()) { - final AnnotatedElement annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); + final var annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); // Determine referential constraint - final boolean isOwner = !mappedBy.isPresent(); + final var isOwner = !mappedBy.isPresent(); buildJoinColumns(isOwner, annotatedElement); determineReferentialConstraints(annotatedElement); } } + @Override + public Map javaAnnotations(final String packageName) { + return findJavaAnnotation(packageName, ((AnnotatedElement) this.jpaAttribute.getJavaMember())); + } + private void buildNaviProperty() throws ODataJPAModelException { this.setExternalName(nameBuilder.buildNaviPropertyName(jpaAttribute)); - + retrieveAnnotations(this, Applicability.NAVIGATION_PROPERTY); evaluateAnnotation(); targetType = schema.getEntityType(determineTargetClass()); @@ -444,40 +460,37 @@ private void buildNaviProperty() throws ODataJPAModelException { private void evaluateAnnotation() throws ODataJPAModelException { if (this.jpaAttribute.getJavaMember() instanceof AnnotatedElement) { - final EdmIgnore jpaIgnore = ((AnnotatedElement) this.jpaAttribute.getJavaMember()).getAnnotation( + final var jpaIgnore = ((AnnotatedElement) this.jpaAttribute.getJavaMember()).getAnnotation( EdmIgnore.class); if (jpaIgnore != null) { this.setIgnore(true); } - final jakarta.persistence.JoinTable jpaJoinTable = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaJoinTable = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(jakarta.persistence.JoinTable.class); joinTable = jpaJoinTable != null ? new IntermediateJoinTable(this, jpaJoinTable, schema) : null; - final AnnotatedElement annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); + final var annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); cardinality = jpaAttribute.getPersistentAttributeType(); if (cardinality != null) { - switch (cardinality) { - case ONE_TO_MANY: + mappedBy = switch (cardinality) { + case ONE_TO_MANY -> { // Association '%1$s' of '%2$s' requires a cardinality annotation. - final OneToMany oneTOMany = Optional.ofNullable(annotatedElement.getAnnotation(OneToMany.class)) + final var oneTOMany = Optional.ofNullable(annotatedElement.getAnnotation(OneToMany.class)) .orElseThrow(() -> new ODataJPAModelException(MISSING_ONE_TO_ONE_ANNOTATION, internalName, sourceType .getInternalName())); - mappedBy = Optional.ofNullable(returnNullIfEmpty(oneTOMany.mappedBy())); - break; - case ONE_TO_ONE: + yield Optional.ofNullable(returnNullIfEmpty(oneTOMany.mappedBy())); + } + case ONE_TO_ONE -> { // Association '%1$s' of '%2$s' requires a cardinality annotation. - final OneToOne annotation = Optional.ofNullable(annotatedElement.getAnnotation(OneToOne.class)) + final var annotation = Optional.ofNullable(annotatedElement.getAnnotation(OneToOne.class)) .orElseThrow(() -> new ODataJPAModelException(MISSING_ONE_TO_ONE_ANNOTATION, internalName, sourceType .getInternalName())); - mappedBy = Optional.ofNullable(returnNullIfEmpty(annotation.mappedBy())); - break; - case MANY_TO_MANY: - mappedBy = Optional.ofNullable(returnNullIfEmpty( - annotatedElement.getAnnotation(ManyToMany.class).mappedBy())); - break; - default: - mappedBy = Optional.empty(); - } + yield Optional.ofNullable(returnNullIfEmpty(annotation.mappedBy())); + } + case MANY_TO_MANY -> Optional.ofNullable(returnNullIfEmpty( + annotatedElement.getAnnotation(ManyToMany.class).mappedBy())); + default -> Optional.empty(); + }; } else { mappedBy = Optional.empty(); } @@ -496,7 +509,7 @@ private Class determineTargetClass() { private IntermediateJoinColumn buildOneJoinColumnPair(final boolean isSourceOne, final JoinColumn column) throws ODataJPAModelException { - IntermediateJoinColumn intermediateColumn = new IntermediateJoinColumn(column); + var intermediateColumn = new IntermediateJoinColumn(column); if (cardinality == PersistentAttributeType.ONE_TO_MANY) intermediateColumn = new IntermediateJoinColumn(intermediateColumn.getReferencedColumnName(), intermediateColumn.getName()); @@ -507,14 +520,14 @@ private IntermediateJoinColumn buildOneJoinColumnPair(final boolean isSourceOne, } private void checkConsistency() throws ODataJPAModelException { - final EdmProtectedBy jpaProtectedBy = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaProtectedBy = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmProtectedBy.class); if (jpaProtectedBy != null) { // Navigation Properties do not support EdmProtectedBy throw new ODataJPAModelException(MessageKeys.NOT_SUPPORTED_PROTECTED_NAVIGATION, this.sourceType.getTypeClass() .getCanonicalName(), this.internalName); } - final EdmVisibleFor jpaFieldGroups = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaFieldGroups = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmVisibleFor.class); if (jpaFieldGroups != null) { throw new ODataJPAModelException(MessageKeys.NOT_SUPPORTED_NAVIGATION_PART_OF_GROUP, @@ -526,25 +539,25 @@ private void checkConsistency() throws ODataJPAModelException { private void determineMappedBy() { if (jpaAttribute.getJavaMember() instanceof AnnotatedElement) { - final AnnotatedElement annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); + final var annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); cardinality = jpaAttribute.getPersistentAttributeType(); switch (cardinality) { case ONE_TO_MANY: - final OneToMany cardinalityOtM = annotatedElement.getAnnotation(OneToMany.class); + final var cardinalityOtM = annotatedElement.getAnnotation(OneToMany.class); edmNaviProperty.setOnDelete(edmOnDelete != null ? edmOnDelete : setJPAOnDelete(cardinalityOtM.cascade())); break; case ONE_TO_ONE: - final OneToOne cardinalityOtO = annotatedElement.getAnnotation(OneToOne.class); + final var cardinalityOtO = annotatedElement.getAnnotation(OneToOne.class); edmNaviProperty.setNullable(cardinalityOtO.optional()); edmNaviProperty.setOnDelete(edmOnDelete != null ? edmOnDelete : setJPAOnDelete(cardinalityOtO.cascade())); break; case MANY_TO_ONE: - final ManyToOne cardinalityMtO = annotatedElement.getAnnotation(ManyToOne.class); + final var cardinalityMtO = annotatedElement.getAnnotation(ManyToOne.class); edmNaviProperty.setNullable(cardinalityMtO.optional()); edmNaviProperty.setOnDelete(edmOnDelete != null ? edmOnDelete : setJPAOnDelete(cardinalityMtO.cascade())); break; case MANY_TO_MANY: - final ManyToMany cardinalityMtM = annotatedElement.getAnnotation(ManyToMany.class); + final var cardinalityMtM = annotatedElement.getAnnotation(ManyToMany.class); edmNaviProperty.setOnDelete(edmOnDelete != null ? edmOnDelete : setJPAOnDelete(cardinalityMtM.cascade())); break; default: @@ -577,8 +590,8 @@ private void determinePartner() throws ODataJPAModelException { private Optional determineReferentialConstraint( final IntermediateJoinColumn intermediateColumn) throws ODataJPAModelException, ODataJPAModelIgnoreException { - final CsdlReferentialConstraint constraint = new CsdlReferentialConstraint(); - boolean ignore = false; + final var constraint = new CsdlReferentialConstraint(); + var ignore = false; IntermediateModelElement p = null; p = sourceType.getPropertyByDBField(intermediateColumn.getName()); if (p != null) { @@ -609,16 +622,16 @@ private Optional determineReferentialConstraint( private void determineReferentialConstraints(final AnnotatedElement annotatedElement) throws ODataJPAModelException { - final AssociationOverride overwrite = annotatedElement.getAnnotation(AssociationOverride.class); + final var overwrite = annotatedElement.getAnnotation(AssociationOverride.class); if (overwrite != null || joinTable != null) return; - final List constraints = edmNaviProperty.getReferentialConstraints(); + final var constraints = edmNaviProperty.getReferentialConstraints(); constraints.clear(); - boolean ignore = false; + var ignore = false; for (final IntermediateJoinColumn intermediateColumn : joinColumns) { try { - Optional constraint = determineReferentialConstraint(intermediateColumn); + var constraint = determineReferentialConstraint(intermediateColumn); if (!constraint.isPresent()) constraint = determineReverseReferentialConstraint(intermediateColumn); constraints.add(constraint.orElseThrow( @@ -635,8 +648,8 @@ private void determineReferentialConstraints(final AnnotatedElement annotatedEle private Optional determineReverseReferentialConstraint( final IntermediateJoinColumn intermediateColumn) throws ODataJPAModelException, ODataJPAModelIgnoreException { - final CsdlReferentialConstraint constraint = new CsdlReferentialConstraint(); - boolean ignore = false; + final var constraint = new CsdlReferentialConstraint(); + var ignore = false; IntermediateModelElement p = null; p = sourceType.getPropertyByDBField(intermediateColumn.getReferencedColumnName()); if (p != null) { @@ -670,8 +683,8 @@ private String fillMissingName() throws ODataJPAModelException { private void fillMissingName(final boolean isSourceOne, final IntermediateJoinColumn intermediateColumn) throws ODataJPAModelException { - final String referencedColumnName = intermediateColumn.getReferencedColumnName(); - final String name = intermediateColumn.getName(); + final var referencedColumnName = intermediateColumn.getReferencedColumnName(); + final var name = intermediateColumn.getName(); if (isSourceOne && (name == null || name.isEmpty())) intermediateColumn.setName(((IntermediateSimpleProperty) ((IntermediateEntityType) sourceType) @@ -699,7 +712,7 @@ private List invertJoinColumns(final List javaAnnotations(final String packageName) { return findJavaAnnotation(packageName, ((AnnotatedElement) this.jpaAttribute.getJavaMember())); @@ -269,7 +251,7 @@ protected void buildProperty(final JPAEdmNameBuilder nameBuilder) throws ODataJP protected FullQualifiedName determineTypeByPersistenceType(final Enum persistenceType) throws ODataJPAModelException { if (PersistentAttributeType.BASIC.equals(persistenceType) || PersistenceType.BASIC.equals(persistenceType)) { - final IntermediateModelElement odataType = getODataPrimitiveType(); + final var odataType = getODataPrimitiveType(); if (odataType == null) return getSimpleType(); else @@ -310,6 +292,35 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw return filterAnnotation(alias, term); } + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + + try { + return Optional.ofNullable(getAnnotation(alias, term)) + .map(a -> a.getExpression()) + .map(expression -> getAnnotationValue(property, expression)) + .orElse(null); + } catch (final ODataJPAModelInternalException e) { + throw e.rootCause; + } + + } + + @Override + protected Object getAnnotationDynamicValue(final String property, final CsdlDynamicExpression expression) + throws ODataJPAModelInternalException { + if (expression.isRecord()) { + // This may create a problem if the property in question is a record itself. Currently non is supported in + // standard + final var propertyValue = findAnnotationPropertyValue(property, expression); + if (propertyValue.isPresent()) { + return getAnnotationValue(property, propertyValue.get()); + } + } + return null; + } + /** * Check consistency of provided attribute e.g. check id attribute was annotated with unsupported annotations * @throws ODataJPAModelException @@ -318,7 +329,7 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw CsdlMapping createMapper() { if (!isLob() && !(getConverter() == null || isEnum())) { - final CsdlMapping mapping = new CsdlMapping(); + final var mapping = new CsdlMapping(); mapping.setInternalName(this.getExternalName()); mapping.setMappedJavaClass(dbType); return mapping; @@ -331,14 +342,14 @@ CsdlMapping createMapper() { abstract void determineIsVersion(); void determineProtection() throws ODataJPAModelException { - final EdmProtections jpaProtections = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaProtections = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmProtections.class); if (jpaProtections != null) { for (final EdmProtectedBy jpaProtectedBy : jpaProtections.value()) { determineOneProtection(jpaProtectedBy); } } else { - final EdmProtectedBy jpaProtectedBy = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaProtectedBy = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmProtectedBy.class); if (jpaProtectedBy != null) { determineOneProtection(jpaProtectedBy); @@ -347,7 +358,7 @@ void determineProtection() throws ODataJPAModelException { } void determineSearchable() { - final EdmSearchable jpaSearchable = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaSearchable = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmSearchable.class); if (jpaSearchable != null) searchable = true; @@ -381,11 +392,11 @@ FullQualifiedName getSimpleType() throws ODataJPAModelException { SRID getSRID() { SRID result = null; if (jpaAttribute.getJavaMember() instanceof AnnotatedElement) { - final AnnotatedElement annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); - final EdmGeospatial spatialDetails = annotatedElement.getAnnotation(EdmGeospatial.class); + final var annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); + final var spatialDetails = annotatedElement.getAnnotation(EdmGeospatial.class); if (spatialDetails != null) { - final String srid = spatialDetails.srid(); - final Dimension dimension = spatialDetails.dimension(); + final var srid = spatialDetails.srid(); + final var dimension = spatialDetails.dimension(); if (srid.isEmpty()) // Set default values external: See https://issues.apache.org/jira/browse/OLINGO-1564 result = SRID.valueOf(dimension == Dimension.GEOMETRY ? "0" : "4326"); @@ -419,7 +430,7 @@ boolean protectionWithWildcard(final String claimName, final Class clazz) void setFacet() throws ODataJPAModelException { if (jpaAttribute.getJavaMember() instanceof AnnotatedElement) { - final Column jpaColumn = ((AnnotatedElement) jpaAttribute.getJavaMember()).getAnnotation(Column.class); + final var jpaColumn = ((AnnotatedElement) jpaAttribute.getJavaMember()).getAnnotation(Column.class); if (jpaColumn != null) { edmProperty.setNullable(jpaColumn.nullable()); edmProperty.setSrid(getSRID()); @@ -504,8 +515,8 @@ private void setPrecisionScale(final Column jpaColumn) { */ private String convertPath(final String internalPath) { - final String[] pathSegments = internalPath.split(JPAPath.PATH_SEPARATOR); - final StringBuilder externalPath = new StringBuilder(); + final var pathSegments = internalPath.split(JPAPath.PATH_SEPARATOR); + final var externalPath = new StringBuilder(); for (final String segment : pathSegments) { externalPath.append(nameBuilder.buildPropertyName(segment)); externalPath.append(JPAPath.PATH_SEPARATOR); @@ -543,13 +554,14 @@ private Constructor> determineCalcul } private void determineDBFieldName() { - final Column jpaColumnDetails = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaColumnDetails = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(Column.class); if (jpaColumnDetails != null) { // TODO allow default name dbFieldName = jpaColumnDetails.name(); if (dbFieldName.isEmpty()) { - final StringBuilder stringBuilder = new StringBuilder(DB_FIELD_NAME_PATTERN); + final var stringBuilder = new StringBuilder(DB_FIELD_NAME_PATTERN); + stringBuilder.replace(1, 3, internalName); dbFieldName = stringBuilder.toString(); } @@ -558,9 +570,9 @@ private void determineDBFieldName() { // for @Id attributes. Try another way to get the information try { if (jpaAttribute.getDeclaringType() != null) { - final Field declaringClass = jpaAttribute.getDeclaringType().getJavaType().getDeclaredField(jpaAttribute + final var declaringClass = jpaAttribute.getDeclaringType().getJavaType().getDeclaredField(jpaAttribute .getName()); - final Column jpaColumn = ((AnnotatedElement) declaringClass).getAnnotation(Column.class); + final var jpaColumn = ((AnnotatedElement) declaringClass).getAnnotation(Column.class); if (jpaColumn != null) dbFieldName = jpaColumn.name(); } @@ -577,7 +589,7 @@ private void determineDBFieldName() { * */ private void determineFieldGroups() { - final EdmVisibleFor jpaFieldGroups = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaFieldGroups = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmVisibleFor.class); if (jpaFieldGroups != null) fieldGroups = Arrays.stream(jpaFieldGroups.value()).toList(); @@ -586,7 +598,7 @@ private void determineFieldGroups() { } private void determineIgnore() { - final EdmIgnore jpaIgnore = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaIgnore = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmIgnore.class); if (jpaIgnore != null) { this.setIgnore(true); @@ -595,12 +607,12 @@ private void determineIgnore() { @SuppressWarnings("unchecked") private void determineInternalTypesFromConverter() throws ODataJPAModelException { - final Convert jpaConverter = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaConverter = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(Convert.class); if (jpaConverter != null) { try { - final Type[] converterType = jpaConverter.converter().getGenericInterfaces(); - final Type[] types = ((ParameterizedType) converterType[0]).getActualTypeArguments(); + final var converterType = jpaConverter.converter().getGenericInterfaces(); + final var types = ((ParameterizedType) converterType[0]).getActualTypeArguments(); entityType = (Class) types[0]; dbType = (Class) types[1]; conversionRequired = !JPATypeConverter.isSupportedByOlingo(entityType); @@ -616,13 +628,13 @@ private void determineInternalTypesFromConverter() throws ODataJPAModelException private void determineOneProtection(final EdmProtectedBy jpaProtectedBy) throws ODataJPAModelException { List externalNames; - final String protectionClaimName = jpaProtectedBy.name(); + final var protectionClaimName = jpaProtectedBy.name(); if (externalProtectedPathNames.containsKey(protectionClaimName)) externalNames = externalProtectedPathNames.get(protectionClaimName).getPath(); else externalNames = new ArrayList<>(2); if (jpaAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { - final String internalProtectedPath = jpaProtectedBy.path(); + final var internalProtectedPath = jpaProtectedBy.path(); if (internalProtectedPath.length() == 0) { throw new ODataJPAModelException(COMPLEX_PROPERTY_MISSING_PROTECTION_PATH, this.managedType.getJavaType() .getCanonicalName(), this.internalName); @@ -645,7 +657,7 @@ private List determineRequiredAttributesTransient(final EdmTransient jpa * */ void determineTransient() throws ODataJPAModelException { - final EdmTransient jpaTransient = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + final var jpaTransient = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) .getAnnotation(EdmTransient.class); if (jpaTransient != null) { if (isKey()) @@ -658,7 +670,7 @@ void determineTransient() throws ODataJPAModelException { private boolean isLob() { if (jpaAttribute != null && jpaAttribute.getJavaMember() instanceof AnnotatedElement) { - final AnnotatedElement annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); + final var annotatedElement = (AnnotatedElement) jpaAttribute.getJavaMember(); if (annotatedElement != null && annotatedElement.getAnnotation(Lob.class) != null) { return true; } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimpleProperty.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimpleProperty.java index 9fa3b203e..09c30350c 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimpleProperty.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimpleProperty.java @@ -149,18 +149,18 @@ String getDefaultValue() throws ODataJPAModelException { else constructor = jpaAttribute.getDeclaringType().getJavaType().getConstructor(); final Object pojo = constructor.newInstance(); - field.setAccessible(true); - final Object value = field.get(pojo); - if (value != null) - valueString = value.toString(); - } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { + if (field.trySetAccessible()) { + final Object value = field.get(pojo); + if (value != null) + valueString = value.toString(); + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new ODataJPAModelException(ODataJPAModelException.MessageKeys.PROPERTY_DEFAULT_ERROR, e, jpaAttribute.getName()); - } catch (final InstantiationException e) { + } catch (final InstantiationException | NoSuchMethodException e) { // Class could not be instantiated e.g. abstract class like - // Business Partner => default could not be determined - // and will be ignored + // Business Partner or missing parameter free constructor + // => default could not be determined and will be ignored LOGGER.debug("Default could not be determined: " + jpaAttribute.getDeclaringType().getJavaType().getName() + "#" diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSingleton.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSingleton.java index 95825193f..d1aa5824d 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSingleton.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSingleton.java @@ -3,7 +3,6 @@ import java.util.List; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; -import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; import org.apache.olingo.commons.api.edm.provider.CsdlSingleton; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; @@ -43,7 +42,7 @@ protected synchronized void lazyBuildEdmItem() throws ODataJPAModelException { postProcessor.processSingleton(this); edmSingleton = new CsdlSingleton(); - final CsdlEntityType edmEt = ((IntermediateEntityType) getODataEntityType()).getEdmItem(); + final var edmEt = ((IntermediateEntityType) getODataEntityType()).getEdmItem(); edmSingleton.setName(getExternalName()); edmSingleton.setType(buildFQN(edmEt.getName())); edmSingleton.setMapping(null); @@ -71,5 +70,4 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw } return filterAnnotation(alias, term); } - } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateTopLevelEntity.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateTopLevelEntity.java index 2aabd4ea8..51db26f3a 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateTopLevelEntity.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateTopLevelEntity.java @@ -6,21 +6,22 @@ import java.util.Map; import java.util.Optional; +import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; import org.apache.olingo.commons.api.edm.provider.CsdlNavigationPropertyBinding; +import org.apache.olingo.commons.api.edm.provider.annotation.CsdlDynamicExpression; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmQueryExtensionProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataAnnotatable; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataNavigationPath; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataPathNotFoundException; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataPropertyPath; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAQueryExtension; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPATopLevelEntity; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelInternalException; abstract class IntermediateTopLevelEntity extends IntermediateModelElement implements JPATopLevelEntity, ODataAnnotatable { @@ -36,23 +37,23 @@ abstract class IntermediateTopLevelEntity extends IntermediateModelElement imple protected List determinePropertyBinding() throws ODataJPAModelException { final List navigationPropBindingList = new ArrayList<>(); - final List navigationPropertyList = entityType.getAssociationPathList(); + final var navigationPropertyList = entityType.getAssociationPathList(); if (navigationPropertyList != null && !navigationPropertyList.isEmpty()) { // http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part3-csdl/odata-v4.0-errata02-os-part3-csdl-complete.html#_Toc406398035 for (final JPAAssociationPath navigationPropertyPath : navigationPropertyList) { - final JPAStructuredType targetType = navigationPropertyPath.getTargetType(); + final var targetType = navigationPropertyPath.getTargetType(); if (targetType instanceof IntermediateEntityType && !(((IntermediateEntityType) targetType).asEntitySet() || ((IntermediateEntityType) targetType) .asSingleton())) { continue; } - final CsdlNavigationPropertyBinding navigationPropBinding = new CsdlNavigationPropertyBinding(); + final var navigationPropBinding = new CsdlNavigationPropertyBinding(); navigationPropBinding.setPath(navigationPropertyPath.getAlias()); // TODO Check is FQN is better here - final JPAAssociationAttribute navigationProperty = navigationPropertyPath.getLeaf(); + final var navigationProperty = navigationPropertyPath.getLeaf(); navigationPropBinding.setTarget(nameBuilder.buildEntitySetName(navigationProperty.getTargetEntity() .getExternalName())); navigationPropBindingList.add(navigationPropBinding); @@ -109,4 +110,43 @@ public Annotation javaAnnotation(final String name) { public Map javaAnnotations(final String packageName) { return entityType.javaAnnotations(packageName); } + + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + + try { + return Optional.ofNullable(getAnnotation(alias, term)) + .map(CsdlAnnotation::getExpression) + .map(expression -> getAnnotationValue(property, expression)) + .orElse(null); + } catch (final ODataJPAModelInternalException e) { + throw e.rootCause; + } + } + + @Override + protected Object getAnnotationDynamicValue(final String property, final CsdlDynamicExpression expression) + throws ODataJPAModelInternalException { + try { + if (expression.isRecord()) { + // This may create a problem if the property in question is a record itself. Currently non is supported in + // standard + final var propertyValue = findAnnotationPropertyValue(property, expression); + if (propertyValue.isPresent()) { + return getAnnotationValue(property, propertyValue.get()); + } + } else if (expression.isCollection()) { + return getAnnotationCollectionValue(expression); + } else if (expression.isPropertyPath()) { + return getEntityType().getPath(expression.asPropertyPath().getValue()); + } else if (expression.isNavigationPropertyPath()) { + return getEntityType().getAssociationPath(expression.asNavigationPropertyPath().getValue()); + } + return null; + } catch (final ODataJPAModelException e) { + throw new ODataJPAModelInternalException(e); + } + } + } \ No newline at end of file diff --git a/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties b/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties index 763d3155e..c96ff251b 100644 --- a/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties +++ b/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties @@ -23,6 +23,7 @@ ODataJPAModelException.INVALID_DESCRIPTION_PROPERTY = The attribute '%2$s' has n ODataJPAModelException.INVALID_COLLECTION_TYPE = Base type of collection '%1$s' of structured type '%2$s' not found ODataJPAModelException.INVALID_TOP_LEVEL_SETTING = The Type '%1$s' has a contradicting annotations ODataJPAModelException.INVALID_NAVIGATION_PROPERTY = The association '%2$s' has not been found at '%1$s' +ODataJPAModelException.ENTITY_TYPE_NOT_FOUND = Entity type for '%1$s' could not be determined ODataJPAModelException.FUNC_UNBOUND_ENTITY_SET = Entity Set Path shall only provided for bound functions. Function '%1$s' is unbound. ODataJPAModelException.FUNC_RETURN_TYPE_ENTITY_SET = Entity Set Path shall only if a function returns an entity or collection of entities. Function '%1$s' has a wrong return type. diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySetTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySetTest.java index 22f04af53..77e58218b 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySetTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntitySetTest.java @@ -4,28 +4,29 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import jakarta.persistence.Entity; import jakarta.persistence.metamodel.EntityType; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; -import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; +import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression.ConstantExpressionType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.reflections8.Reflections; import com.sap.olingo.jpa.metadata.api.JPAEdmMetadataPostProcessor; @@ -33,17 +34,25 @@ import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEnumeration; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; -import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataNavigationPath; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataPathNotFoundException; -import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataPropertyPath; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.TermAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntitySet; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateEntitySetAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateEntityTypeAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateNavigationPropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediatePropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; +import com.sap.olingo.jpa.metadata.core.edm.mapper.util.AnnotationTestHelper; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.CountRestrictionsProperties; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.ExpandRestrictionsProperties; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.FilterRestrictionsProperties; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.Terms; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.UpdateMethod; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.UpdateRestrictionsProperties; +import com.sap.olingo.jpa.metadata.odata.v4.general.Aliases; +import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCapabilitiesAnnotationsProvider; import com.sap.olingo.jpa.processor.core.testmodel.ABCClassification; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivisionDescription; import com.sap.olingo.jpa.processor.core.testmodel.AnnotationsParent; @@ -61,7 +70,7 @@ class IntermediateEntitySetTest extends TestMappingRoot { @BeforeEach void setup() throws ODataJPAModelException { IntermediateModelElement.setPostProcessor(new DefaultEdmPostProcessor()); - final Reflections reflections = mock(Reflections.class); + final var reflections = mock(Reflections.class); when(reflections.getTypesAnnotatedWith(EdmEnumeration.class)).thenReturn(new HashSet<>(Arrays.asList( ABCClassification.class))); @@ -75,72 +84,72 @@ void setup() throws ODataJPAModelException { @Test void checkAnnotationSet() throws ODataJPAModelException { IntermediateModelElement.setPostProcessor(new PostProcessor()); - final IntermediateEntityType et = new IntermediateEntityType<>(nameBuilder, + final var et = new IntermediateEntityType(nameBuilder, getEntityType("AdministrativeDivisionDescription"), schema); - final IntermediateEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final List act = es.getEdmItem().getAnnotations(); + final var es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + final var act = es.getEdmItem().getAnnotations(); assertEquals(1, act.size()); assertEquals("Capabilities.TopSupported", act.get(0).getTerm()); } @Test void checkODataEntityTypeDiffers() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType("BestOrganization"), schema); - final IntermediateEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + final var es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final JPAEntityType odataEt = es.getODataEntityType(); + final var odataEt = es.getODataEntityType(); assertEquals("BusinessPartner", odataEt.getExternalName()); } @Test void checkODataEntityTypeSame() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType("Organization"), schema); - final IntermediateEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + final var es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final JPAEntityType odataEt = es.getODataEntityType(); + final var odataEt = es.getODataEntityType(); assertEquals("Organization", odataEt.getExternalName()); } @Test void checkEdmItemContainsODataEntityType() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType("BestOrganization"), schema); - final IntermediateEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final CsdlEntitySet act = es.getEdmItem(); + final var es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + final var act = es.getEdmItem(); assertEquals(et.buildFQN("BusinessPartner").getFullQualifiedNameAsString(), act.getType()); } @Test void checkConvertStringToPathWithSimplePath() throws ODataJPAModelException, ODataPathNotFoundException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final IntermediateTopLevelEntity es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final ODataPropertyPath act = es.convertStringToPath("type"); + final var act = es.convertStringToPath("type"); assertNotNull(act); assertEquals("Type", act.getPathAsString()); } @Test void checkConvertStringToNavigationPathWithSimplePath() throws ODataJPAModelException, ODataPathNotFoundException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final IntermediateTopLevelEntity es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final ODataNavigationPath act = es.convertStringToNavigationPath("roles"); + final var act = es.convertStringToNavigationPath("roles"); assertNotNull(act); assertEquals("Roles", act.getPathAsString()); } @Test void checkJavaAnnotationsOneAnnotation() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final IntermediateTopLevelEntity es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final Map act = es.javaAnnotations(EdmEntityType.class.getPackage().getName()); + final var act = es.javaAnnotations(EdmEntityType.class.getPackage().getName()); assertEquals(2, act.size()); assertNotNull(act.get("EdmEntityType")); assertNotNull(act.get("EdmFunctions")); @@ -148,10 +157,10 @@ void checkJavaAnnotationsOneAnnotation() throws ODataJPAModelException { @Test void checkJavaAnnotationsTwoAnnotations() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final IntermediateTopLevelEntity es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final Map act = es.javaAnnotations(Entity.class.getPackage().getName()); + final var act = es.javaAnnotations(Entity.class.getPackage().getName()); assertEquals(4, act.size()); assertNotNull(act.get("Table")); assertNotNull(act.get("Entity")); @@ -161,27 +170,27 @@ void checkJavaAnnotationsTwoAnnotations() throws ODataJPAModelException { @Test void checkJavaAnnotationsNoAnnotations() throws ODataJPAModelException { - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final IntermediateTopLevelEntity es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final Map act = es.javaAnnotations(Test.class.getPackage().toString()); + final var act = es.javaAnnotations(Test.class.getPackage().toString()); assertTrue(act.isEmpty()); } @Test void checkGetAnnotationReturnsExistingAnnotation() throws ODataJPAModelException { - createAnnotation(); - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); - final CsdlAnnotation act = es.getAnnotation("Capabilities", "FilterRestrictions"); + final var act = es.getAnnotation("Capabilities", "FilterRestrictions"); assertNotNull(act); } @Test void checkGetAnnotationReturnsNullAliasUnknown() throws ODataJPAModelException { - createAnnotation(); - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); assertNull(es.getAnnotation("Capability", "FilterRestrictions")); @@ -189,23 +198,129 @@ void checkGetAnnotationReturnsNullAliasUnknown() throws ODataJPAModelException { @Test void checkGetAnnotationReturnsNullAnnotationUnknown() throws ODataJPAModelException { - createAnnotation(); - final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); assertNull(es.getAnnotation("Capabilities", "Filter")); } - private void createAnnotation() { - final AnnotationProvider annotationProvider = mock(AnnotationProvider.class); + @Test + void checkGetAnnotationValueReturnsNullAnnotationUnknown() throws ODataJPAModelException { + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); + final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + assertNull(es.getAnnotationValue("Capabilities", "Filter", "Filterable")); + } + + static Stream expectedSimpleValue() { + return Stream.of( + Arguments.of(Terms.FILTER_RESTRICTIONS, FilterRestrictionsProperties.FILTERABLE, Boolean.class, Boolean.TRUE), + Arguments.of(Terms.UPDATE_RESTRICTIONS, UpdateRestrictionsProperties.UPDATE_METHOD, String.class, + UpdateMethod.PATCH.name()), + Arguments.of(Terms.UPDATE_RESTRICTIONS, UpdateRestrictionsProperties.DESCRIPTION, String.class, "Just to test"), + Arguments.of(Terms.EXPAND_RESTRICTIONS, ExpandRestrictionsProperties.MAX_LEVELS, Integer.class, Integer.valueOf( + 2))); + } + + @ParameterizedTest + @MethodSource("expectedSimpleValue") + void checkGetAnnotationValueSimpleProperty(final TermAccess term, final PropertyAccess propertyName, + final Class type, final Object expectedValue) throws ODataJPAModelException { + + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); + final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + + final var act = es.getAnnotationValue("Capabilities", term.term(), propertyName.property()); + assertNotNull(act); + assertTrue(type.isAssignableFrom(act.getClass())); + assertEquals(expectedValue, act); + + assertEquals(expectedValue, es.getAnnotationValue("Capabilities", term.term(), propertyName.property(), type)); + assertEquals(expectedValue, es.getAnnotationValue(Aliases.CAPABILITIES, term, propertyName, type)); + } + + static Stream expectedCollectionValue() { + return Stream.of( + Arguments.of(Terms.FILTER_RESTRICTIONS, FilterRestrictionsProperties.REQUIRED_PROPERTIES, Arrays.asList( + "ParentCodeID", "ParentDivisionCode"), false), + Arguments.of(Terms.COUNT_RESTRICTIONS, CountRestrictionsProperties.NON_COUNTABLE_NAVIGATION_PROPERTIES, Arrays + .asList("Children"), true)); + } + + @ParameterizedTest + @MethodSource("expectedCollectionValue") + void checkGetAnnotationValueCollectionProperty(final TermAccess term, final PropertyAccess propertyName, + final List expectedValues, final boolean isNavigation) throws ODataJPAModelException { + + createAnnotations(); + final var et = new IntermediateEntityType(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); + final List exp; + if (isNavigation) { + exp = expectedValues.stream().map(t -> { + try { + return et.getAssociationPath(t); + } catch (final ODataJPAModelException e) { + fail(e); + return null; + } + }).toList(); + } else { + exp = expectedValues.stream().map(t -> { + try { + return et.getPath(t); + } catch (final ODataJPAModelException e) { + fail(e); + return null; + } + }).toList(); + } + final JPAEntitySet es = new IntermediateEntitySet(nameBuilder, et, annotationInfo); + + final var act = es.getAnnotationValue("Capabilities", term.term(), propertyName.property()); + assertNotNull(act); + assertEquals(exp, act); + } + + private void createAnnotations() { + final AnnotationProvider annotationProvider = new JavaBasedCapabilitiesAnnotationsProvider();// mock(AnnotationProvider.class); final List annotations = new ArrayList<>(); - final CsdlAnnotation annotation = mock(CsdlAnnotation.class); - annotations.add(annotation); + + final List propertiesFilter = new ArrayList<>(); + final List propertiesExpand = new ArrayList<>(); + final List propertiesUpdate = new ArrayList<>(); + final List propertiesCount = new ArrayList<>(); + + annotations.add(AnnotationTestHelper.createCapabilitiesAnnotation("FilterRestrictions")); + annotations.add(AnnotationTestHelper.createCapabilitiesAnnotation("ExpandRestrictions")); + annotations.add(AnnotationTestHelper.createCapabilitiesAnnotation("UpdateRestrictions")); + annotations.add(AnnotationTestHelper.createCapabilitiesAnnotation("CountRestrictions")); + + final var termFilter = AnnotationTestHelper.addTermToCapabilitiesReferences(references, "FilterRestrictions", + "FilterRestrictionsType", propertiesFilter); + final var termExpand = AnnotationTestHelper.addTermToCapabilitiesReferences(references, "ExpandRestrictions", + "ExpandRestrictionsType", propertiesExpand); + final var termUpdate = AnnotationTestHelper.addTermToCapabilitiesReferences(references, "UpdateRestrictions", + "UpdateRestrictionsType", propertiesUpdate); + final var termCount = AnnotationTestHelper.addTermToCapabilitiesReferences(references, "CountRestrictions", + "CountRestrictionsType", propertiesCount); + + propertiesFilter.add(AnnotationTestHelper.createTermProperty("Filterable", "Edm.Boolean")); + propertiesFilter.add(AnnotationTestHelper.createTermCollectionProperty("RequiredProperties", "Edm.PropertyPath")); + propertiesExpand.add(AnnotationTestHelper.createTermProperty("MaxLevels", "Edm.Int32")); + propertiesUpdate.add(AnnotationTestHelper.createTermProperty("UpdateMethod", "Capabilities.HttpMethod")); + propertiesUpdate.add(AnnotationTestHelper.createTermProperty("Description", "Edm.String")); + propertiesCount.add(AnnotationTestHelper.createTermCollectionProperty("NonCountableNavigationProperties", + "Edm.NavigationPropertyPath")); + when(references.convertAlias("Capabilities")).thenReturn("Org.OData.Capabilities.V1"); - when(annotation.getTerm()).thenReturn("Org.OData.Capabilities.V1.FilterRestrictions"); + when(references.getTerms("Capabilities", Applicability.ENTITY_SET)) + .thenReturn(Arrays.asList(termFilter, termExpand, termUpdate, termCount)); annotationInfo.getAnnotationProvider().add(annotationProvider); - when(annotationProvider.getAnnotations(eq(Applicability.ENTITY_SET), any(), any())) - .thenReturn(annotations); } private class PostProcessor implements JPAEdmMetadataPostProcessor { @@ -233,8 +348,8 @@ public void provideReferences(final IntermediateReferenceList references) throws @Override public void processEntitySet(final IntermediateEntitySetAccess entitySet) { - final CsdlConstantExpression mimeType = new CsdlConstantExpression(ConstantExpressionType.Bool, "false"); - final CsdlAnnotation annotation = new CsdlAnnotation(); + final var mimeType = new CsdlConstantExpression(ConstantExpressionType.Bool, "false"); + final var annotation = new CsdlAnnotation(); annotation.setExpression(mimeType); annotation.setTerm("Capabilities.TopSupported"); final List annotations = new ArrayList<>(); diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityTypeTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityTypeTest.java index a0d59ba9b..ba95ea1b1 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityTypeTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateEntityTypeTest.java @@ -8,8 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,6 +24,7 @@ import org.apache.olingo.commons.api.edm.EdmProperty; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; +import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlCollection; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression.ConstantExpressionType; @@ -37,7 +36,6 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmMetadataPostProcessor; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEnumeration; -import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataNavigationPath; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.ODataPathNotFoundException; @@ -55,6 +53,11 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateNavigationPropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediatePropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; +import com.sap.olingo.jpa.metadata.core.edm.mapper.util.AnnotationTestHelper; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.ExampleProperties; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Terms; +import com.sap.olingo.jpa.metadata.odata.v4.general.Aliases; +import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCoreAnnotationsProvider; import com.sap.olingo.jpa.processor.core.errormodel.TeamWithTransientError; import com.sap.olingo.jpa.processor.core.testmodel.ABCClassification; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; @@ -991,7 +994,7 @@ void checkGetAnnotationReturnsExistingAnnotation() throws ODataJPAModelException createAnnotation(); final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); - final CsdlAnnotation act = et.getAnnotation("Capabilities", "FilterRestrictions"); + final CsdlAnnotation act = et.getAnnotation("Core", "Example"); assertNotNull(act); } @@ -1000,7 +1003,7 @@ void checkGetAnnotationReturnsNullAliasUnknown() throws ODataJPAModelException { createAnnotation(); final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); - assertNull(et.getAnnotation("Capability", "FilterRestrictions")); + assertNull(et.getAnnotation("Capability", "Example")); } @Test @@ -1008,7 +1011,26 @@ void checkGetAnnotationReturnsNullAnnotationUnknown() throws ODataJPAModelExcept createAnnotation(); final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); - assertNull(et.getAnnotation("Capabilities", "Filter")); + assertNull(et.getAnnotation("Core", "Filter")); + } + + @Test + void checkGetAnnotationValueNavigationProperty() throws ODataJPAModelException { + createAnnotation(); + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); + final var act = et.getAnnotationValue(Aliases.CORE, Terms.EXAMPLE, ExampleProperties.EXTERNAL_VALUE, String.class); + assertNotNull(act); + assertEquals("../AnnotationsParent?$filter=Parent eq null", act); + } + + @Test + void checkGetAnnotationValueReturnsNullAliasUnknown() throws ODataJPAModelException { + createAnnotation(); + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AnnotationsParent.class), schema); + assertNull(et.getAnnotationValue(Aliases.CAPABILITIES.alias(), Terms.EXAMPLE.term(), + ExampleProperties.EXTERNAL_VALUE.property())); } @Test @@ -1086,15 +1108,23 @@ void assertComplexAnnotated(final List act, final String expC } private void createAnnotation() { - final AnnotationProvider annotationProvider = mock(AnnotationProvider.class); + final var reference = annotationInfo.getReferences(); + final var annotationProvider = new JavaBasedCoreAnnotationsProvider(); final List annotations = new ArrayList<>(); - final CsdlAnnotation annotation = mock(CsdlAnnotation.class); - annotations.add(annotation); - when(references.convertAlias("Capabilities")).thenReturn("Org.OData.Capabilities.V1"); - when(annotation.getTerm()).thenReturn("Org.OData.Capabilities.V1.FilterRestrictions"); + final List properties = new ArrayList<>(); + + properties.add(AnnotationTestHelper.createTermProperty("Description", "Edm.String")); + properties.add(AnnotationTestHelper.createTermProperty("ExternalValue", "Edm.String")); + annotations.add(AnnotationTestHelper.createCoreAnnotation("Example")); + + final var terms = AnnotationTestHelper.addTermToCoreReferences(reference, "Example", "ExternalExampleValue", + properties); + + when(reference.convertAlias("Core")).thenReturn("Org.OData.Core.V1"); + when(reference.getTerms("Core", Applicability.ENTITY_TYPE)) + .thenReturn(Arrays.asList(terms)); + annotationInfo.getAnnotationProvider().add(annotationProvider); - when(annotationProvider.getAnnotations(eq(Applicability.ENTITY_TYPE), any(), any())) - .thenReturn(annotations); } private void assertInherited(final List act) { diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationPropertyTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationPropertyTest.java index 6ad08462a..5e680d0f2 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationPropertyTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateNavigationPropertyTest.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.List; +import jakarta.persistence.OneToMany; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.EmbeddableType; import jakarta.persistence.metamodel.EntityType; @@ -28,6 +29,7 @@ import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; import org.apache.olingo.commons.api.edm.provider.CsdlOnDelete; import org.apache.olingo.commons.api.edm.provider.CsdlOnDeleteAction; +import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.CsdlReferentialConstraint; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression.ConstantExpressionType; @@ -43,6 +45,7 @@ import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEnumeration; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtectedBy; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmVisibleFor; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAJoinTable; @@ -53,9 +56,15 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateNavigationPropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediatePropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; +import com.sap.olingo.jpa.metadata.core.edm.mapper.util.AnnotationTestHelper; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.ExampleProperties; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Terms; +import com.sap.olingo.jpa.metadata.odata.v4.general.Aliases; +import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCoreAnnotationsProvider; import com.sap.olingo.jpa.processor.core.errormodel.MissingCardinalityAnnotation; import com.sap.olingo.jpa.processor.core.testmodel.ABCClassification; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; +import com.sap.olingo.jpa.processor.core.testmodel.AnnotationsParent; import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToManyTarget; import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneSource; import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneTarget; @@ -80,11 +89,13 @@ class IntermediateNavigationPropertyTest extends TestMappingRoot { private IntermediateSchema errorSchema; private JPAEdmMetadataPostProcessor processor; private IntermediateAnnotationInformation annotationInfo; + private IntermediateReferences reference; @BeforeEach void setup() throws ODataJPAModelException { final Reflections reflections = mock(Reflections.class); - annotationInfo = new IntermediateAnnotationInformation(new ArrayList<>()); + reference = mock(IntermediateReferences.class); + annotationInfo = new IntermediateAnnotationInformation(new ArrayList<>(), reference); when(reflections.getTypesAnnotatedWith(EdmEnumeration.class)).thenReturn(new HashSet<>(Arrays.asList( ABCClassification.class))); @@ -911,13 +922,77 @@ void checkGetAnnotationReturnsNullAnnotationUnknown() throws ODataJPAModelExcept void checkMissingCardinalityAnnotationThrowsError() throws ODataJPAModelException { final EntityType et = errorHelper.getEntityType(MissingCardinalityAnnotation.class); final Attribute jpaAttribute = errorHelper.getDeclaredAttribute(et, "oneTeam"); -// final IntermediateNavigationProperty cut = new IntermediateNavigationProperty<>(errorNameBuilder, -// errorSchema.getEntityType(et.getJavaType()), jpaAttribute, schema); assertThrows(ODataJPAModelException.class, () -> new IntermediateNavigationProperty<>( errorNameBuilder, errorSchema.getEntityType(et.getJavaType()), jpaAttribute, schema)); } + @Test + void checkGetAnnotationVaueReturnsNullAliasUnknown() throws ODataJPAModelException { + createAnnotation(); + final EntityType et = helper.getEntityType(AnnotationsParent.class); + final Attribute jpaAttribute = helper.getDeclaredAttribute(et, "children"); + final IntermediateNavigationProperty cut = new IntermediateNavigationProperty<>(nameBuilder, schema + .getEntityType(et.getJavaType()), jpaAttribute, schema); + assertNull(cut.getAnnotationValue(Aliases.CAPABILITIES.alias(), Terms.EXAMPLE.term(), + ExampleProperties.EXTERNAL_VALUE.property())); + } + + @Test + void checkGetAnnotationValueNavigationProperty() throws ODataJPAModelException { + createAnnotation(); + final EntityType et = helper.getEntityType(AnnotationsParent.class); + final Attribute jpaAttribute = helper.getDeclaredAttribute(et, "children"); + final IntermediateNavigationProperty cut = new IntermediateNavigationProperty<>(nameBuilder, schema + .getEntityType(et.getJavaType()), jpaAttribute, schema); + final var act = cut.getAnnotationValue(Aliases.CORE, Terms.EXAMPLE, ExampleProperties.EXTERNAL_VALUE, String.class); + assertNotNull(act); + assertEquals("../AnnotationsParent?$filter=Children/$count eq 0", act); + } + + @Test + void checkJavaAnnotationsOneAnnotation() throws ODataJPAModelException { + createAnnotation(); + final EntityType et = helper.getEntityType(AnnotationsParent.class); + final Attribute jpaAttribute = helper.getDeclaredAttribute(et, "children"); + final IntermediateNavigationProperty cut = new IntermediateNavigationProperty<>(nameBuilder, schema + .getEntityType(et.getJavaType()), jpaAttribute, schema); + final var act = cut.javaAnnotations(OneToMany.class.getPackage().getName()); + assertEquals(1, act.size()); + assertNotNull(act.get("OneToMany")); + } + + @Test + void checkJavaAnnotationsNoAnnotations() throws ODataJPAModelException { + createAnnotation(); + final EntityType et = helper.getEntityType(AnnotationsParent.class); + final Attribute jpaAttribute = helper.getDeclaredAttribute(et, "children"); + final IntermediateNavigationProperty cut = new IntermediateNavigationProperty<>(nameBuilder, schema + .getEntityType(et.getJavaType()), jpaAttribute, schema); + final var act = cut.javaAnnotations(Test.class.getPackage().getName()); + assertTrue(act.isEmpty()); + } + + private void createAnnotation() { + final var reference = annotationInfo.getReferences(); + final var annotationProvider = new JavaBasedCoreAnnotationsProvider(); + final List annotations = new ArrayList<>(); + final List properties = new ArrayList<>(); + + properties.add(AnnotationTestHelper.createTermProperty("Description", "Edm.String")); + properties.add(AnnotationTestHelper.createTermProperty("ExternalValue", "Edm.String")); + annotations.add(AnnotationTestHelper.createCoreAnnotation("Example")); + + final var terms = AnnotationTestHelper.addTermToCoreReferences(reference, "Example", "ExternalExampleValue", + properties); + + when(reference.convertAlias("Core")).thenReturn("Org.OData.Core.V1"); + when(reference.getTerms("Core", Applicability.NAVIGATION_PROPERTY)) + .thenReturn(Arrays.asList(terms)); + + annotationInfo.getAnnotationProvider().add(annotationProvider); + } + private Attribute createDummyAttribute() { final Attribute jpaAttribute = mock(Attribute.class); final ManagedType managedType = mock(ManagedType.class); diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java index fa4a3b391..2ec8db7f5 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java @@ -11,15 +11,12 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; import java.math.BigDecimal; @@ -28,10 +25,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.List; -import java.util.Map; import jakarta.persistence.Column; import jakarta.persistence.metamodel.Attribute; @@ -42,9 +37,8 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.geo.Geospatial.Dimension; -import org.apache.olingo.commons.api.edm.geo.SRID; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; -import org.apache.olingo.commons.api.edm.provider.CsdlProperty; +import org.apache.olingo.commons.api.edm.provider.CsdlTypeDefinition; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression; import org.apache.olingo.commons.api.edm.provider.annotation.CsdlConstantExpression.ConstantExpressionType; import org.junit.jupiter.api.BeforeEach; @@ -57,15 +51,18 @@ import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmGeospatial; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtectedBy; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtections; -import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; -import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.JPAReferences; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateEntityTypeAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateNavigationPropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediatePropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; +import com.sap.olingo.jpa.metadata.core.edm.mapper.util.AnnotationTestHelper; import com.sap.olingo.jpa.metadata.core.edm.mapper.util.MemberDouble; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.GenaralProperty; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Terms; +import com.sap.olingo.jpa.metadata.odata.v4.general.Aliases; +import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCoreAnnotationsProvider; import com.sap.olingo.jpa.processor.core.errormodel.TeamWithTransientCalculatorConstructorError; import com.sap.olingo.jpa.processor.core.errormodel.TeamWithTransientCalculatorError; import com.sap.olingo.jpa.processor.core.errormodel.TeamWithTransientKey; @@ -106,7 +103,7 @@ void checkPropertyCanBeCreated() throws ODataJPAModelException { @Test void checkGetPropertyName() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals("Type", property.getEdmItem().getName(), "Wrong name"); } @@ -114,7 +111,7 @@ void checkGetPropertyName() throws ODataJPAModelException { @Test void checkGetPropertyDBFieldName() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals("\"Type\"", property.getDBFieldName(), "Wrong name"); } @@ -122,7 +119,7 @@ void checkGetPropertyDBFieldName() throws ODataJPAModelException { @Test void checkGetPropertySimpleType() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals(EdmPrimitiveTypeKind.String.getFullQualifiedName().getFullQualifiedNameAsString(), property.getEdmItem().getType(), "Wrong type"); @@ -132,7 +129,7 @@ void checkGetPropertySimpleType() throws ODataJPAModelException { void checkGetPropertyComplexType() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "communicationData"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals(PUNIT_NAME + ".CommunicationData", property.getEdmItem().getType(), "Wrong type"); } @@ -140,7 +137,7 @@ void checkGetPropertyComplexType() throws ODataJPAModelException { @Test void checkGetPropertyEnumTypeWithoutConverter() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Organization.class), "aBCClass"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals("com.sap.olingo.jpa.ABCClassification", property.getEdmItem().getType(), "Wrong type"); } @@ -148,7 +145,7 @@ void checkGetPropertyEnumTypeWithoutConverter() throws ODataJPAModelException { @Test void checkGetPropertyEnumTypeWithoutConverterMustNotHaveMapper() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Organization.class), "aBCClass"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getEdmItem().getMapping()); @@ -157,7 +154,7 @@ void checkGetPropertyEnumTypeWithoutConverterMustNotHaveMapper() throws ODataJPA @Test void checkGetPropertyEnumTypeWithConverter() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "accessRights"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals("com.sap.olingo.jpa.AccessRights", property.getEdmItem().getType(), "Wrong type"); @@ -186,7 +183,7 @@ void checkGetPropertyIgnoreTrue() throws ODataJPAModelException { void checkGetPropertyFacetsNullableTrue() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customString1"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertTrue(property.getEdmItem().isNullable()); @@ -196,7 +193,7 @@ void checkGetPropertyFacetsNullableTrue() throws ODataJPAModelException { void checkGetPropertyFacetsNullableTrueComplex() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute( helper.getEmbeddableType(PostalAddressData.class), "pOBox"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertTrue(property.getEdmItem().isNullable()); } @@ -204,7 +201,7 @@ void checkGetPropertyFacetsNullableTrueComplex() throws ODataJPAModelException { @Test void checkGetPropertyFacetsNullableFalse() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "eTag"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertFalse(property.getEdmItem().isNullable()); } @@ -212,7 +209,7 @@ void checkGetPropertyFacetsNullableFalse() throws ODataJPAModelException { @Test void checkGetPropertyIsETagTrue() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "eTag"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertTrue(property.isEtag()); } @@ -220,7 +217,7 @@ void checkGetPropertyIsETagTrue() throws ODataJPAModelException { @Test void checkGetPropertyIsETagFalse() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertFalse(property.isEtag()); @@ -229,16 +226,16 @@ void checkGetPropertyIsETagFalse() throws ODataJPAModelException { @Test void checkGetPropertyMaxLength() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); - - assertEquals(Integer.valueOf(1), property.getEdmItem().getMaxLength()); + + assertEquals(Integer.valueOf(1), property.getEdmItem().getMaxLength()); } @Test void checkGetPropertyMaxLengthNullForClob() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getComplexType("DummyEmbeddedToIgnore"), "command"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getEdmItem().getMaxLength()); @@ -247,35 +244,35 @@ void checkGetPropertyMaxLengthNullForClob() throws ODataJPAModelException { @Test void checkGetPropertyPrecisionDecimal() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customNum1"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); - - assertEquals(Integer.valueOf(16), property.getEdmItem().getPrecision()); + + assertEquals(Integer.valueOf(16), property.getEdmItem().getPrecision()); } @Test void checkGetPropertyScaleDecimal() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customNum1"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, helper.schema); - assertEquals(Integer.valueOf(5), property.getEdmItem().getScale()); + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + jpaAttribute, helper.schema); + assertEquals(Integer.valueOf(5), property.getEdmItem().getScale()); } @Test void checkGetPropertyPrecisionTime() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); - - assertEquals(Integer.valueOf(2), property.getEdmItem().getPrecision()); + + assertEquals(Integer.valueOf(2), property.getEdmItem().getPrecision()); } @Test void checkGetPropertyMapper() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNotNull(property.getEdmItem().getMapping()); assertEquals(Timestamp.class, property.getEdmItem().getMapping().getMappedJavaClass()); @@ -284,7 +281,7 @@ void checkGetPropertyMapper() throws ODataJPAModelException { @Test void checkGetPropertyMapperWithConverter() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Collection.class), "localDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNotNull(property.getEdmItem().getMapping()); assertEquals(java.util.Date.class, property.getEdmItem().getMapping().getMappedJavaClass()); @@ -293,7 +290,7 @@ void checkGetPropertyMapperWithConverter() throws ODataJPAModelException { @Test void checkGetNoPropertyMapperForClob() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Comment.class), "text"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getEdmItem().getMapping()); @@ -304,7 +301,7 @@ void checkPostProcessorCalled() throws ODataJPAModelException { IntermediateSimpleProperty.setPostProcessor(processor); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); property.getEdmItem(); @@ -313,12 +310,12 @@ void checkPostProcessorCalled() throws ODataJPAModelException { @Test void checkPostProcessorAnnotationAdded() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateSimpleProperty.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customString1"); - final IntermediateSimpleProperty cut = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var cut = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertEquals(1L, cut.getEdmItem().getAnnotations().stream().filter(a -> a.getTerm().equals("Immutable")).count()); @@ -326,12 +323,12 @@ void checkPostProcessorAnnotationAdded() throws ODataJPAModelException { @Test void checkGetConverterReturnedConversionRequired() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNotNull(property.getConverter()); @@ -339,11 +336,11 @@ void checkGetConverterReturnedConversionRequired() throws ODataJPAModelException @Test void checkGetConverterNullNoConverterDefined() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "customString2"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getRawConverter()); @@ -351,11 +348,11 @@ void checkGetConverterNullNoConverterDefined() throws ODataJPAModelException { @Test void checkGetConverterNullDBTypeEqJavaType() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "customString1"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getConverter()); @@ -363,11 +360,11 @@ void checkGetConverterNullDBTypeEqJavaType() throws ODataJPAModelException { @Test void checkGetConverterNullConversionNotRequired() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(DummyToBeIgnored.class), "uuid"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getConverter()); @@ -375,12 +372,12 @@ void checkGetConverterNullConversionNotRequired() throws ODataJPAModelException @Test void checkGetRawConverterReturnedConversionRequired() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNotNull(property.getRawConverter()); @@ -388,11 +385,11 @@ void checkGetRawConverterReturnedConversionRequired() throws ODataJPAModelExcept @Test void checkGetRawConverterNullNoConverterDefined() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "customString2"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNull(property.getRawConverter()); @@ -400,11 +397,11 @@ void checkGetRawConverterNullNoConverterDefined() throws ODataJPAModelException @Test void checkGetRawConverterReturnsConversionNotRequired() throws ODataJPAModelException { - final PostProcessorSetName postProcessorDouble = new PostProcessorSetName(); + final var postProcessorDouble = new PostProcessorSetName(); IntermediateModelElement.setPostProcessor(postProcessorDouble); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(DummyToBeIgnored.class), "uuid"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertNotNull(property.getRawConverter()); @@ -414,7 +411,7 @@ void checkGetRawConverterReturnsConversionNotRequired() throws ODataJPAModelExce void checkGetPropertyDefaultValue() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEmbeddableType(PostalAddressData.class), "regionCodePublisher"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertEquals("ISO", property.getEdmItem().getDefaultValue()); } @@ -423,7 +420,7 @@ void checkGetPropertyDefaultValue() throws ODataJPAModelException { void checkGetPropertyDefaultValueIgnoredOnAbstractClass() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "iD"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertNull(property.getEdmItem().getDefaultValue()); } @@ -432,7 +429,7 @@ void checkGetPropertyDefaultValueIgnoredOnAbstractClass() throws ODataJPAModelEx void checkGetPropertyIsStream() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(PersonImage.class), "image"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertTrue(property.isStream()); } @@ -443,7 +440,7 @@ void checkGetPropertyIsTransientTrue() throws ODataJPAModelException, NoSuchFiel final Attribute jpaAttribute = new IntermediateStructuredType.TransientSingularAttribute<>(helper .getEntityType(Person.class), Person.class.getDeclaredField("fullName")); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertTrue(property.isTransient()); } @@ -455,7 +452,7 @@ void checkGetPropertyIsTransientFalse() throws ODataJPAModelException, NoSuchFie final Attribute jpaAttribute = new IntermediateStructuredType.TransientSingularAttribute<>(helper .getEntityType(Person.class), Person.class.getDeclaredField("lastName")); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertFalse(property.isTransient()); } @@ -477,7 +474,7 @@ void checkGetPropertyGetCalculatorPersistentIsNull() throws ODataJPAModelExcepti final Attribute jpaAttribute = new IntermediateStructuredType.TransientSingularAttribute<>(helper .getEntityType(Person.class), Person.class.getDeclaredField("lastName")); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertNull(property.getCalculatorConstructor()); } @@ -489,7 +486,7 @@ void checkGetPropertyGetCalculatorTransientNotNull() throws ODataJPAModelExcepti final Attribute jpaAttribute = new IntermediateStructuredType.TransientSingularAttribute<>(helper .getEntityType(Person.class), Person.class.getDeclaredField("fullName")); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertNotNull(property.getCalculatorConstructor()); } @@ -576,7 +573,7 @@ void checkGetPropertyGetCalculatorNullOnPersistentProperty() throws ODataJPAMode final Attribute jpaAttribute = new IntermediateStructuredType.TransientSingularAttribute<>(helper .getEntityType(Person.class), Person.class.getDeclaredField("lastName")); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertNull(property.getCalculatorConstructor()); } @@ -585,7 +582,7 @@ void checkGetPropertyGetCalculatorNullOnPersistentProperty() throws ODataJPAMode void checkGetTypeBoxedForPrimitive() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(AdministrativeDivision.class), "population"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertEquals(Long.class, property.getType()); @@ -595,7 +592,7 @@ void checkGetTypeBoxedForPrimitive() throws ODataJPAModelException { void checkGetTypeBoxed() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(AdministrativeDivision.class), "area"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertEquals(Integer.class, property.getType()); @@ -605,7 +602,7 @@ void checkGetTypeBoxed() throws ODataJPAModelException { void checkGetTypeConversionRequired() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "creationDateTime"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), jpaAttribute, helper.schema); assertTrue(property.conversionRequired); @@ -618,7 +615,7 @@ void checkGetTypeConversionRequired() throws ODataJPAModelException { void checkGetTypeConversionNotRequired() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "birthDay"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertFalse(property.conversionRequired); @@ -653,13 +650,13 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { } }); - final Column column = mock(Column.class); - final AnnotatedElement annotations = mock(AnnotatedElement.class, withSettings().extraInterfaces(Member.class)); + final var column = mock(Column.class); + final var annotations = mock(AnnotatedElement.class, withSettings().extraInterfaces(Member.class)); when(annotations.getAnnotation(Column.class)).thenReturn(column); when(jpaAttribute.getJavaMember()).thenReturn((Member) annotations); when(column.name()).thenReturn("Test"); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertEquals(0, property.getEdmItem().getPrecision()); @@ -720,7 +717,7 @@ void checkGetPropertyProtectedAttributeClaimName() throws ODataJPAModelException jpaAttribute, helper.schema); assertEquals("UserId", property.getProtectionClaimNames().toArray(new String[] {})[0]); assertNotNull(property.getProtectionPath("UserId")); - final List actPath = property.getProtectionPath("UserId"); + final var actPath = property.getProtectionPath("UserId"); assertEquals(1, actPath.size()); assertEquals("UserName", actPath.get(0)); } @@ -740,11 +737,11 @@ void checkGetComplexPropertyProtectedAttributeClaimName() throws ODataJPAModelEx final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartnerProtected.class), "administrativeInformation"); - final EdmProtectedBy annotation = Mockito.mock(EdmProtectedBy.class); + final var annotation = Mockito.mock(EdmProtectedBy.class); when(annotation.name()).thenReturn("UserId"); when(annotation.path()).thenReturn("created/by"); - final MemberDouble memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); + final var memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); memberSpy.addAnnotation(EdmProtectedBy.class, annotation); final Attribute attributeSpy = Mockito.spy(jpaAttribute); when(attributeSpy.getJavaMember()).thenReturn(memberSpy); @@ -753,7 +750,7 @@ void checkGetComplexPropertyProtectedAttributeClaimName() throws ODataJPAModelEx attributeSpy, helper.schema); assertEquals("UserId", property.getProtectionClaimNames().toArray(new String[] {})[0]); assertNotNull(property.getProtectionPath("UserId")); - final List actPath = property.getProtectionPath("UserId"); + final var actPath = property.getProtectionPath("UserId"); assertEquals(1, actPath.size()); assertEquals("AdministrativeInformation/Created/By", actPath.get(0)); } @@ -763,18 +760,18 @@ void checkGetComplexPropertyTwoProtectedAttributeClaimName() throws ODataJPAMode final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartnerProtected.class), "administrativeInformation"); - final EdmProtections protections = Mockito.mock(EdmProtections.class); - final EdmProtectedBy protectedBy1 = Mockito.mock(EdmProtectedBy.class); + final var protections = Mockito.mock(EdmProtections.class); + final var protectedBy1 = Mockito.mock(EdmProtectedBy.class); when(protectedBy1.name()).thenReturn("UserId"); when(protectedBy1.path()).thenReturn("created/by"); - final EdmProtectedBy protectedBy2 = Mockito.mock(EdmProtectedBy.class); + final var protectedBy2 = Mockito.mock(EdmProtectedBy.class); when(protectedBy2.name()).thenReturn("UserId"); when(protectedBy2.path()).thenReturn("updated/by"); when(protections.value()).thenReturn(new EdmProtectedBy[] { protectedBy1, protectedBy2 }); - final MemberDouble memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); + final var memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); memberSpy.addAnnotation(EdmProtections.class, protections); final Attribute attributeSpy = Mockito.spy(jpaAttribute); when(attributeSpy.getJavaMember()).thenReturn(memberSpy); @@ -784,7 +781,7 @@ void checkGetComplexPropertyTwoProtectedAttributeClaimName() throws ODataJPAMode assertEquals("UserId", property.getProtectionClaimNames().toArray(new String[] {})[0]); assertNotNull(property.getProtectionPath("UserId")); - final List actPath = property.getProtectionPath("UserId"); + final var actPath = property.getProtectionPath("UserId"); assertEquals(2, actPath.size()); assertEquals("AdministrativeInformation/Created/By", actPath.get(0)); assertEquals("AdministrativeInformation/Updated/By", actPath.get(1)); @@ -795,18 +792,18 @@ void checkGetComplexPropertyTwoProtectedAttributeTwoClaimName() throws ODataJPAM final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartnerProtected.class), "administrativeInformation"); - final EdmProtections protections = Mockito.mock(EdmProtections.class); - final EdmProtectedBy protectedBy1 = Mockito.mock(EdmProtectedBy.class); + final var protections = Mockito.mock(EdmProtections.class); + final var protectedBy1 = Mockito.mock(EdmProtectedBy.class); when(protectedBy1.name()).thenReturn("UserId"); when(protectedBy1.path()).thenReturn("created/by"); - final EdmProtectedBy protectedBy2 = Mockito.mock(EdmProtectedBy.class); + final var protectedBy2 = Mockito.mock(EdmProtectedBy.class); when(protectedBy2.name()).thenReturn("Date"); when(protectedBy2.path()).thenReturn("created/at"); when(protections.value()).thenReturn(new EdmProtectedBy[] { protectedBy1, protectedBy2 }); - final MemberDouble memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); + final var memberSpy = new MemberDouble(jpaAttribute.getJavaMember()); memberSpy.addAnnotation(EdmProtections.class, protections); final Attribute attributeSpy = Mockito.spy(jpaAttribute); when(attributeSpy.getJavaMember()).thenReturn(memberSpy); @@ -815,7 +812,7 @@ void checkGetComplexPropertyTwoProtectedAttributeTwoClaimName() throws ODataJPAM attributeSpy, helper.schema); assertTrue(property.getProtectionClaimNames().contains("UserId")); - List actPath = property.getProtectionPath("UserId"); + var actPath = property.getProtectionPath("UserId"); assertEquals(1, actPath.size()); assertEquals("AdministrativeInformation/Created/By", actPath.get(0)); @@ -827,53 +824,53 @@ void checkGetComplexPropertyTwoProtectedAttributeTwoClaimName() throws ODataJPAM @Test void checkGetSRIDDefaultValueGeography() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); - final EdmGeospatial geospatial = mock(EdmGeospatial.class); + final var geospatial = mock(EdmGeospatial.class); addTypeBigDecimal(jpaColumn, jpaAttribute); when(((AnnotatedElement) jpaAttribute.getJavaMember()).getAnnotation(EdmGeospatial.class)).thenReturn(geospatial); when(geospatial.srid()).thenReturn(""); when(geospatial.dimension()).thenReturn(Dimension.GEOGRAPHY); - final IntermediateSimpleProperty cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final SRID act = cut.getSRID(); + final var cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); + final var act = cut.getSRID(); assertEquals("4326", act.toString()); } @Test void checkGetSRIDDefaultValueGeometry() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); - final EdmGeospatial geo = mock(EdmGeospatial.class); + final var geo = mock(EdmGeospatial.class); addTypeBigDecimal(jpaColumn, jpaAttribute); when(((AnnotatedElement) jpaAttribute.getJavaMember()).getAnnotation(EdmGeospatial.class)).thenReturn(geo); when(geo.srid()).thenReturn(""); when(geo.dimension()).thenReturn(Dimension.GEOMETRY); - final IntermediateSimpleProperty cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final SRID act = cut.getSRID(); + final var cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); + final var act = cut.getSRID(); assertEquals("0", act.toString()); } @Test void checkGetSRIDExplicitValue() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); - final EdmGeospatial geospatial = mock(EdmGeospatial.class); + final var geospatial = mock(EdmGeospatial.class); addTypeBigDecimal(jpaColumn, jpaAttribute); when(((AnnotatedElement) jpaAttribute.getJavaMember()).getAnnotation(EdmGeospatial.class)).thenReturn(geospatial); when(geospatial.srid()).thenReturn("31466"); when(geospatial.dimension()).thenReturn(Dimension.GEOGRAPHY); - final IntermediateSimpleProperty cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final SRID act = cut.getSRID(); + final var cut = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); + final var act = cut.getSRID(); assertEquals(Dimension.GEOGRAPHY, act.getDimension()); assertEquals("31466", act.toString()); } @Test void checkGetPropertyThrowsExceptionOnDateTimePrecisionGt12() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); when(jpaColumn.precision()).thenReturn(13); @@ -883,16 +880,16 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { return OffsetDateTime.class; } }); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final ODataJPAModelException act = assertThrows(ODataJPAModelException.class, () -> property.lazyBuildEdmItem()); + final var act = assertThrows(ODataJPAModelException.class, () -> property.lazyBuildEdmItem()); assertEquals(PROPERTY_PRECISION_NOT_IN_RANGE.getKey(), act.getId()); assertTrue(act.getMessage().contains("13")); } @Test void checkGetPropertyThrowsExceptionOnDateTimePrecisionLt0() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); when(jpaColumn.precision()).thenReturn(-1); @@ -902,21 +899,21 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { return OffsetDateTime.class; } }); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final ODataJPAModelException act = assertThrows(ODataJPAModelException.class, () -> property.lazyBuildEdmItem()); + final var act = assertThrows(ODataJPAModelException.class, () -> property.lazyBuildEdmItem()); assertEquals(PROPERTY_PRECISION_NOT_IN_RANGE.getKey(), act.getId()); assertTrue(act.getMessage().contains("-1")); } @Test void checkGetPropertyPrecisionNullOnDecimalPrecision0() throws ODataJPAModelException { - final Column jpaColumn = createDummyColumn(); + final var jpaColumn = createDummyColumn(); final Attribute jpaAttribute = createDummyAttribute(jpaColumn); addTypeBigDecimal(jpaColumn, jpaAttribute); - final IntermediateSimpleProperty property = new IntermediateSimpleProperty(nameBuilder, + final var property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final CsdlProperty act = property.getEdmItem(); + final var act = property.getEdmItem(); assertNull(act.getPrecision()); } @@ -950,7 +947,7 @@ void checkGetAnnotationReturnsExistingAnnotation() throws ODataJPAModelException final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( AnnotationsParent.class), "area"); final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final CsdlAnnotation act = property.getAnnotation("Core", "ComputedDefaultValue"); + final var act = property.getAnnotation("Core", "ComputedDefaultValue"); assertNotNull(act); } @@ -963,13 +960,35 @@ void checkGetAnnotationReturnsNullAliasUnknown() throws ODataJPAModelException { assertNull(property.getAnnotation("Capability", "ComputedDefaultValue")); } + @Test + void checkGetAnnotationValueSimpleProperty() throws ODataJPAModelException { + createAnnotation(); + final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( + AnnotationsParent.class), "area"); + final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); + final var act = property.getAnnotationValue(Aliases.CORE, Terms.COMPUTED_DEFAULT_VALUE, GenaralProperty.VALUE, + Boolean.class); + assertNotNull(act); + assertTrue(act); + } + + @Test + void checkGetAnnotationVaueReturnsNullAliasUnknown() throws ODataJPAModelException { + createAnnotation(); + final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( + AnnotationsParent.class), "area"); + final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); + assertNull(property.getAnnotationValue(Aliases.CAPABILITIES.alias(), Terms.COMPUTED_DEFAULT_VALUE.term(), + GenaralProperty.VALUE.property())); + } + @Test void checkJavaAnnotationsOneAnnotation() throws ODataJPAModelException { createAnnotation(); final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( AnnotationsParent.class), "area"); final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final Map act = property.javaAnnotations(Column.class.getPackage().getName()); + final var act = property.javaAnnotations(Column.class.getPackage().getName()); assertEquals(1, act.size()); assertNotNull(act.get("Column")); } @@ -980,27 +999,31 @@ void checkJavaAnnotationsNoAnnotations() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( AnnotationsParent.class), "area"); final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final Map act = property.javaAnnotations(Test.class.getPackage().getName()); + final var act = property.javaAnnotations(Test.class.getPackage().getName()); assertTrue(act.isEmpty()); } private void createAnnotation() { - final AnnotationProvider annotationProvider = mock(AnnotationProvider.class); - final List annotations = new ArrayList<>(); - final CsdlAnnotation annotation = mock(CsdlAnnotation.class); - final JPAReferences reference = helper.annotationInfo.getReferences(); - annotations.add(annotation); + final var reference = helper.annotationInfo.getReferences(); + final var annotationProvider = new JavaBasedCoreAnnotationsProvider();// mock(AnnotationProvider.class); + + final var typeDefinition = mock(CsdlTypeDefinition.class); + when(typeDefinition.getName()).thenReturn("Tag"); + when(typeDefinition.getUnderlyingType()).thenReturn("Edm.Boolean"); + final var terms = AnnotationTestHelper.addTermToCoreReferences(reference, "ComputedDefaultValue", "Tag", + typeDefinition); + when(reference.convertAlias("Core")).thenReturn("Org.OData.Core.V1"); - when(annotation.getTerm()).thenReturn("Org.OData.Core.V1.ComputedDefaultValue"); + when(reference.getTerms("Core", Applicability.PROPERTY)) + .thenReturn(Arrays.asList(terms)); + helper.annotationInfo.getAnnotationProvider().add(annotationProvider); - when(annotationProvider.getAnnotations(eq(Applicability.PROPERTY), any(), any())) - .thenReturn(annotations); } private Attribute createDummyAttribute(final Column jpaColumn) { final Attribute jpaAttribute = mock(Attribute.class); final ManagedType managedType = mock(ManagedType.class); - final Member javaMember = mock(Member.class, withSettings().extraInterfaces(AnnotatedElement.class)); + final var javaMember = mock(Member.class, withSettings().extraInterfaces(AnnotatedElement.class)); when(managedType.getJavaType()).thenAnswer(new Answer>() { @Override public Class answer(final InvocationOnMock invocation) throws Throwable { @@ -1021,7 +1044,7 @@ public ManagedType answer(final InvocationOnMock invocation) throws Throwable } private Column createDummyColumn() { - final Column jpaColumn = mock(Column.class); + final var jpaColumn = mock(Column.class); when(jpaColumn.name()).thenReturn("DUMMY"); when(jpaColumn.nullable()).thenReturn(true); return jpaColumn; @@ -1034,7 +1057,7 @@ public void processProperty(final IntermediatePropertyAccess property, final Str if (jpaManagedTypeClassName.equals( "com.sap.olingo.jpa.processor.core.testmodel.BusinessPartner")) { if (property.getInternalName().equals("customString1")) { - final CsdlAnnotation annotation = new CsdlAnnotation(); + final var annotation = new CsdlAnnotation(); annotation.setTerm("Immutable"); annotation.setExpression(new CsdlConstantExpression(ConstantExpressionType.Bool, "true")); property.addAnnotations(Collections.singletonList(annotation)); diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/testobjects/ConverterWithConstructorError.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/testobjects/ConverterWithConstructorError.java index ca96a1473..b5501e603 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/testobjects/ConverterWithConstructorError.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/testobjects/ConverterWithConstructorError.java @@ -21,4 +21,8 @@ public Enum[] convertToEntityAttribute(final Integer dbData) { return null; } + public int getCounter() { + return counter; + } + } diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/util/AnnotationTestHelper.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/util/AnnotationTestHelper.java new file mode 100644 index 000000000..ab9d89d03 --- /dev/null +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/util/AnnotationTestHelper.java @@ -0,0 +1,98 @@ +package com.sap.olingo.jpa.metadata.core.edm.mapper.util; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; + +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; +import org.apache.olingo.commons.api.edm.provider.CsdlComplexType; +import org.apache.olingo.commons.api.edm.provider.CsdlProperty; +import org.apache.olingo.commons.api.edm.provider.CsdlTerm; +import org.apache.olingo.commons.api.edm.provider.CsdlTypeDefinition; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.JPAReferences; + +public final class AnnotationTestHelper { + + private AnnotationTestHelper() { + super(); + } + + public static CsdlTerm addTermToCoreReferences(final JPAReferences reference, final String term, final String type, + final CsdlTypeDefinition typeDefintion) { + + final var csdlTerm = mock(CsdlTerm.class); + when(reference.getTerm(new FullQualifiedName("Org.OData.Core.V1", term))) + .thenReturn(Optional.of(csdlTerm)); + when(reference.getType(new FullQualifiedName("Core", type))) + .thenReturn(Optional.of(typeDefintion)); + + when(csdlTerm.getName()).thenReturn(term); + when(csdlTerm.getType()).thenReturn("Core." + type); + return csdlTerm; + } + + public static CsdlTerm addTermToCoreReferences(final JPAReferences reference, final String term, + final String type, final List properties) { + + final var termFilter = mock(CsdlTerm.class); + final var complexType = new CsdlComplexType(); + complexType.setProperties(properties); + when(reference.getTerm(new FullQualifiedName("Org.OData.Core.V1", term))) + .thenReturn(Optional.of(termFilter)); + when(reference.getType(new FullQualifiedName("Core", type))) + .thenReturn(Optional.of(complexType)); + + when(termFilter.getName()).thenReturn(term); + when(termFilter.getType()).thenReturn("Core." + type); + return termFilter; + } + + public static CsdlTerm addTermToCapabilitiesReferences(final JPAReferences reference, final String term, + final String type, final List properties) { + + final var termFilter = mock(CsdlTerm.class); + final var complexType = new CsdlComplexType(); + complexType.setProperties(properties); + when(reference.getTerm(new FullQualifiedName("Org.OData.Capabilities.V1", term))) + .thenReturn(Optional.of(termFilter)); + when(reference.getType(new FullQualifiedName("Capabilities", type))) + .thenReturn(Optional.of(complexType)); + + when(termFilter.getName()).thenReturn(term); + when(termFilter.getType()).thenReturn("Capabilities." + type); + return termFilter; + } + + public static CsdlAnnotation createCoreAnnotation(final String term) { + final var annotation = mock(CsdlAnnotation.class); + when(annotation.getTerm()).thenReturn("Org.OData.Core.V1." + term); + return annotation; + } + + public static CsdlAnnotation createCapabilitiesAnnotation(final String term) { + final var annotation = mock(CsdlAnnotation.class); + when(annotation.getTerm()).thenReturn("Org.OData.Capabilities.V1." + term); + return annotation; + } + + public static CsdlProperty createTermProperty(final String name, final String type) { + final var property = mock(CsdlProperty.class); + when(property.getType()).thenReturn(type); + when(property.isCollection()).thenReturn(false); + when(property.getName()).thenReturn(name); + return property; + } + + public static CsdlProperty createTermCollectionProperty(final String name, final String type) { + final var property = mock(CsdlProperty.class); + when(property.getType()).thenReturn(type); + when(property.isCollection()).thenReturn(true); + when(property.getName()).thenReturn(name); + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/.project b/jpa/odata-jpa-odata-vocabularies/.project new file mode 100644 index 000000000..883ef085b --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/.project @@ -0,0 +1,47 @@ + + + jpa-odata-vocabularies + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/CountRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/CountRestrictionsProperties.java new file mode 100644 index 000000000..f6d88e3ab --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/CountRestrictionsProperties.java @@ -0,0 +1,22 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum CountRestrictionsProperties implements PropertyAccess { + + COUNTABLE("Countable"), + NON_COUNTABLE_PROPERTIES("NonCountableProperties"), + NON_COUNTABLE_NAVIGATION_PROPERTIES("NonCountableNavigationProperties"); + + private final String property; + + private CountRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepInsertSupportProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepInsertSupportProperties.java new file mode 100644 index 000000000..5380afba6 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepInsertSupportProperties.java @@ -0,0 +1,21 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum DeepInsertSupportProperties implements PropertyAccess { + + SUPPORTED("supported"), + CONTENT_ID_SUPPORTED("contentIDSupported"); + + private final String property; + + private DeepInsertSupportProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepUpdateSupportProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepUpdateSupportProperties.java new file mode 100644 index 000000000..661a6acb8 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeepUpdateSupportProperties.java @@ -0,0 +1,21 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum DeepUpdateSupportProperties implements PropertyAccess { + + SUPPORTED("supported"), + CONTENT_ID_SUPPORTED("contentIDSupported"); + + private final String property; + + private DeepUpdateSupportProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeleteRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeleteRestrictionsProperties.java new file mode 100644 index 000000000..128c854aa --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/DeleteRestrictionsProperties.java @@ -0,0 +1,24 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum DeleteRestrictionsProperties implements PropertyAccess { + + DELETABLE("Deletable"), + NON_DELETABLE_NAVIGATION_PROPERTIES("NonDeletableNavigationProperties"), + MAX_LEVELS("MaxLevels"), + DESCRIPTION("Description"), + LONG_DESCRIPTION("LongDescription"); + + private final String property; + + private DeleteRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/ExpandRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/ExpandRestrictionsProperties.java new file mode 100644 index 000000000..ae9135eef --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/ExpandRestrictionsProperties.java @@ -0,0 +1,22 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum ExpandRestrictionsProperties implements PropertyAccess { + + EXPANDABLE("Expandable"), + NON_EXPANDABLE_PROPERTIES("NonExpandableProperties"), + MAX_LEVELS("MaxLevels"); + + private final String property; + + private ExpandRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictions.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictions.java index dc1e7e638..1cfd5bf51 100644 --- a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictions.java +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictions.java @@ -41,7 +41,7 @@ * Optional: These properties must be specified in the $filter clause (properties of derived types are not allowed * here) *

- * The properties are given as an array attributes path. In case the path + * The properties are given as an array of attributes path. In case the path * is composed, path segments joined together by forward slashes (/) e.g address/cityName. */ String[] requiredProperties() default {}; diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictionsProperties.java new file mode 100644 index 000000000..ad462bc99 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/FilterRestrictionsProperties.java @@ -0,0 +1,22 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum FilterRestrictionsProperties implements PropertyAccess { + + FILTERABLE("Filterable"), + REQUIRES_FILTER("RequiresFilter"), + REQUIRED_PROPERTIES("RequiredProperties"); + + private final String property; + + private FilterRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/InsertRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/InsertRestrictionsProperties.java new file mode 100644 index 000000000..6f3b5479e --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/InsertRestrictionsProperties.java @@ -0,0 +1,26 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum InsertRestrictionsProperties implements PropertyAccess { + + INSERTABLE("Insertable"), + NON_INSERTABLE_PROPERTIES("NonInsertableProperties"), + NON_INSERTABLE_NAVIGATION_PROPERTIES("NonInsertableNavigationProperties"), + REQUIRED_PROPERTIES("RequiredProperties"), + MAX_LEVELS("MaxLevels"), + DESCRIPTION("Description"), + LONG_DESCRIPTION("LongDescription"); + + private final String property; + + private InsertRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/SortRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/SortRestrictionsProperties.java new file mode 100644 index 000000000..edbb2f8ae --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/SortRestrictionsProperties.java @@ -0,0 +1,23 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum SortRestrictionsProperties implements PropertyAccess { + + SORTABLE("Sortable"), + ASCENDING_ONLY_PROPERTIES("AscendingOnlyProperties"), + DESCENDING_ONLE_PROPERTIES("DescendingOnlyProperties"), + NON_SORTABLE_PROPERTIES("NonSortableProperties"); + + private final String property; + + private SortRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/Terms.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/Terms.java new file mode 100644 index 000000000..4462350dc --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/Terms.java @@ -0,0 +1,27 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.TermAccess; + +public enum Terms implements TermAccess { + + COUNT_RESTRICTIONS("CountRestrictions"), + DEEP_INSERT_SUPPORT("DeepInsertSupport"), + DEEP_UPDATE_SUPPORT("DeepUpdateSupport"), + DELETE_RESTRICTIONS("DeleteRestrictions"), + EXPAND_RESTRICTIONS("ExpandRestrictions"), + FILTER_RESTRICTIONS("FilterRestrictions"), + INSERT_RESTRICTIONS("InsertRestrictions"), + SORT_RESTRICTIONS("SortRestrictions"), + UPDATE_RESTRICTIONS("UpdateRestrictions"); + + private final String term; + + Terms(final String term) { + this.term = term; + } + + @Override + public String term() { + return term; + } +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/UpdateRestrictionsProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/UpdateRestrictionsProperties.java new file mode 100644 index 000000000..c4b7774ee --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/capabilities/terms/UpdateRestrictionsProperties.java @@ -0,0 +1,28 @@ +package com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum UpdateRestrictionsProperties implements PropertyAccess { + + UPDATEABLE("Updatable"), + UPSERTABLE("Upsertable"), + UPDATE_METHOD("UpdateMethod"), + NON_UPDATEABLE_PROPERTIES("NonUpdatableProperties"), + NON_UPDATEABLE_NAVIGATION_PROPERTIES("NonUpdatableNavigationProperties"), + REQUIRED_PROPERTIES("RequiredProperties"), + MAX_LEVELS("MaxLevels"), + DESCRIPTION("Description"), + LONG_DESCRIPTION("LongDescription"); + + private final String property; + + private UpdateRestrictionsProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Example.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Example.java new file mode 100644 index 000000000..91cf0e7b7 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Example.java @@ -0,0 +1,47 @@ +package com.sap.olingo.jpa.metadata.odata.v4.core.terms; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.Applicability; +import com.sap.olingo.jpa.metadata.odata.v4.general.Vocabulary; + +/** + * OData core annotation Example:
+ * Example for an instance of the annotated model element. The value of Core.Example is a record/object containing the + * example value and/or annotation examples.
+ * An example can have different flavors. It could be a PrimitiveExampleValue, a ComplexExampleValue, an + * EntityExampleValue or an ExternalExampleValue. Out of those only the last one is supported by this java annotation. + *

+ * Example:
+ * + * description = "External example" + * externalValue = "https://services.odata.org/TripPinRESTierService/(S(5fjoyrzpnvzrrvmxzzq25i4q))/Me" + * + *

+ * + * AppliesTo: EntityType, Property, NavigationProperty + * @author Oliver Grande + * + */ +@Retention(RUNTIME) +@Target({ TYPE, FIELD }) +@Vocabulary(alias = "Core", appliesTo = { Applicability.ENTITY_TYPE, Applicability.PROPERTY, + Applicability.NAVIGATION_PROPERTY }) +public @interface Example { + + /** + * Description of the example value + */ + String description() default ""; + + /** + * Url reference to the value in its literal format + */ + String externalValue(); + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/ExampleProperties.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/ExampleProperties.java new file mode 100644 index 000000000..898b4b3cf --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/ExampleProperties.java @@ -0,0 +1,20 @@ +package com.sap.olingo.jpa.metadata.odata.v4.core.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum ExampleProperties implements PropertyAccess { + DESCRIPTION("Description"), + EXTERNAL_VALUE("ExternalValue"); + + private final String property; + + ExampleProperties(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GenaralProperty.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GenaralProperty.java new file mode 100644 index 000000000..5105f6bbb --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GenaralProperty.java @@ -0,0 +1,19 @@ +package com.sap.olingo.jpa.metadata.odata.v4.core.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum GenaralProperty implements PropertyAccess { + VALUE("Value"); + + private final String property; + + GenaralProperty(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Terms.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Terms.java new file mode 100644 index 000000000..4d2241fe6 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/Terms.java @@ -0,0 +1,22 @@ +package com.sap.olingo.jpa.metadata.odata.v4.core.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.TermAccess; + +public enum Terms implements TermAccess { + + COMPUTED("Computed"), + COMPUTED_DEFAULT_VALUE("ComputedDefaultValue"), + IMMUTABLE("Immutable"), + EXAMPLE("Example"); + + private final String term; + + Terms(final String term) { + this.term = term; + } + + @Override + public String term() { + return term; + } +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/general/Aliases.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/general/Aliases.java new file mode 100644 index 000000000..8b908b7b7 --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/general/Aliases.java @@ -0,0 +1,21 @@ +package com.sap.olingo.jpa.metadata.odata.v4.general; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AliasAccess; + +public enum Aliases implements AliasAccess { + + CORE("Core"), + CAPABILITIES("Capabilities"); + + private final String alias; + + Aliases(final String alias) { + this.alias = alias; + } + + @Override + public String alias() { + return alias; + } + +} diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaAnnotationConverter.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaAnnotationConverter.java index ab213af41..7fb6d76d4 100644 --- a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaAnnotationConverter.java +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaAnnotationConverter.java @@ -2,7 +2,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -18,7 +17,6 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.provider.CsdlAnnotation; import org.apache.olingo.commons.api.edm.provider.CsdlComplexType; -import org.apache.olingo.commons.api.edm.provider.CsdlEnumMember; import org.apache.olingo.commons.api.edm.provider.CsdlEnumType; import org.apache.olingo.commons.api.edm.provider.CsdlNamed; import org.apache.olingo.commons.api.edm.provider.CsdlProperty; @@ -62,14 +60,14 @@ Optional convert(@Nonnull final JPAReferences references, private CsdlCollection asCollectionExpression(final JPAReferences references, final AnnotationType propertyType, final ODataAnnotatable annotatable, final Object[] values) { - final CsdlCollection collection = new CsdlCollection(); - final List collectionItems = collection.getItems(); - final FullQualifiedName fqn = propertyType.getFqn(); + final var collection = new CsdlCollection(); + final var collectionItems = collection.getItems(); + final var fqn = propertyType.getFqn(); for (final Object item : splitCollectionValues(propertyType, values)) { if (propertyType.isPrimitiveType()) collectionItems.add(asConstantExpression(propertyType, item)); else if (propertyType.isPathType()) { - final CsdlExpression expression = asPathExpression(annotatable, fqn, item); + final var expression = asPathExpression(annotatable, fqn, item); if (expression != null) collectionItems.add(asPathExpression(annotatable, fqn, item)); } else @@ -85,7 +83,7 @@ private CsdlExpression asConstantExpression(final AnnotationType annotation, fin } private CsdlPropertyValue asCsdlProperty(final CsdlProperty property, final CsdlExpression expression) { - final CsdlPropertyValue propertyValue = new CsdlPropertyValue(); + final var propertyValue = new CsdlPropertyValue(); propertyValue.setValue(expression); propertyValue.setProperty(property.getName()); return propertyValue; @@ -94,10 +92,10 @@ private CsdlPropertyValue asCsdlProperty(final CsdlProperty property, final Csdl private CsdlAnnotation asEdmAnnotation(final JPAReferences references, final Annotation annotation, final CsdlTerm term, final ODataAnnotatable annotatable) { - final String alias = annotation.annotationType().getAnnotation(Vocabulary.class).alias(); - final String namespace = references.convertAlias(alias); - final CsdlAnnotation edmAnnotation = new CsdlAnnotation(); - final AnnotationType annotationType = new AnnotationType(references, term); + final var alias = annotation.annotationType().getAnnotation(Vocabulary.class).alias(); + final var namespace = references.convertAlias(alias); + final var edmAnnotation = new CsdlAnnotation(); + final var annotationType = new AnnotationType(references, term); edmAnnotation.setTerm(new FullQualifiedName(namespace, term.getName()).getFullQualifiedNameAsString()); edmAnnotation.setExpression(buildExpression(references, annotation, annotationType, annotatable)); return edmAnnotation; @@ -106,32 +104,27 @@ private CsdlAnnotation asEdmAnnotation(final JPAReferences references, private Optional asEnumExpression(final Annotation annotation, final AnnotationType propertyType, final String propertyName) { - final Optional propertyValue = getPropertyValue(annotation, propertyName); + final var propertyValue = getPropertyValue(annotation, propertyName); return propertyValue.map(value -> asEnumExpression(propertyType, value)); } private CsdlExpression asEnumExpression(final AnnotationType propertyType, @Nonnull final Object propertyValue) { - final CsdlEnumType enumType = propertyType.asEnumeration(); - final CsdlEnumMember enumMember = enumType.getMember(propertyValue.toString()); + final var enumType = propertyType.asEnumeration(); + final var enumMember = enumType.getMember(propertyValue.toString()); return new CsdlConstantExpression(ConstantExpressionType.EnumMember, enumMember.getName()); } private ConstantExpressionType asExpressionType(final AnnotationType annotation) { - final FullQualifiedName fqn = annotation.getBaseType() != null ? annotation.getBaseType() : annotation.getFqn(); - switch (EdmPrimitiveTypeKind.valueOfFQN(fqn)) { - case Boolean: - return ConstantExpressionType.Bool; - case Int32: - return ConstantExpressionType.Int; - case String: - return ConstantExpressionType.String; - default: - break; - } - return null; + final var fqn = annotation.getBaseType() != null ? annotation.getBaseType() : annotation.getFqn(); + return switch (EdmPrimitiveTypeKind.valueOfFQN(fqn)) { + case Boolean -> ConstantExpressionType.Bool; + case Int32 -> ConstantExpressionType.Int; + case String -> ConstantExpressionType.String; + default -> null; + }; } @CheckForNull @@ -139,20 +132,21 @@ private CsdlExpression asPathExpression(final ODataAnnotatable annotatable, fina final Object item) { try { - switch (fqn.getName()) { - case "PropertyPath": + return switch (fqn.getName()) { + case "PropertyPath" -> { // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752659 - final CsdlPropertyPath path = new CsdlPropertyPath(); + final var path = new CsdlPropertyPath(); path.setValue(annotatable.convertStringToPath(item.toString()).getPathAsString()); - return path; - case "NavigationPropertyPath": + yield path; + } + case "NavigationPropertyPath" -> { // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752657 - final CsdlNavigationPropertyPath navigationPath = new CsdlNavigationPropertyPath(); + final var navigationPath = new CsdlNavigationPropertyPath(); navigationPath.setValue(annotatable.convertStringToNavigationPath(item.toString()).getPathAsString()); - return navigationPath; - default: - return null; - } + yield navigationPath; + } + default -> null; + }; } catch (final ODataPathNotFoundException e) { LOGGER.error("Error orrured when trying to convert path '" + item.toString() + "' of " + annotatable.getClass().getSimpleName(), e); @@ -163,12 +157,12 @@ private CsdlExpression asPathExpression(final ODataAnnotatable annotatable, fina private CsdlExpression asRecordExpression(final JPAReferences references, final Annotation annotation, final AnnotationType annotationType, final ODataAnnotatable annotatable) { - final CsdlRecord csdlRecord = new CsdlRecord(); + final var csdlRecord = new CsdlRecord(); final List recordProperties = new ArrayList<>(); csdlRecord.setType(annotationType.getFqn().getFullQualifiedNameAsString()); csdlRecord.setPropertyValues(recordProperties); for (final CsdlProperty property : annotationType.asComplexType().getProperties()) { - final AnnotationType propertyType = new AnnotationType(references, property); + final var propertyType = new AnnotationType(references, property); if (propertyType.isCollection()) { getPropertyValue(annotation, property.getName()) .map(Object[].class::cast) @@ -203,9 +197,9 @@ private CsdlExpression asRecordExpression(final JPAReferences references, final private CsdlExpression buildExpression(final JPAReferences references, final Annotation annotation, final AnnotationType annotationType, final ODataAnnotatable annotatable) { - final Optional value = getPropertyValue(annotation, "value"); + final var value = getPropertyValue(annotation, "value"); if (annotationType.isCollection) { - final CsdlCollection collection = new CsdlCollection(); + final var collection = new CsdlCollection(); if (value.isPresent()) { for (final Object item : (Object[]) value.get()) { collection.getItems().add(buildRowExpression(references, annotation, annotationType, annotatable, item)); @@ -230,14 +224,14 @@ private CsdlExpression buildRowExpression(final JPAReferences references, final } private String firstToLower(@Nonnull final String name) { - final char[] asArray = name.toCharArray(); + final var asArray = name.toCharArray(); asArray[0] = Character.toLowerCase(asArray[0]); return String.valueOf(asArray); } private Optional getPropertyValue(final Annotation annotation, final String propertyName) { try { - final Method method = annotation.getClass().getMethod(firstToLower(propertyName)); + final var method = annotation.getClass().getMethod(firstToLower(propertyName)); return Optional.ofNullable(method.invoke(annotation)); } catch (final NoSuchMethodException unSupported) { LOGGER.trace("Unsupported property of annotation: " + propertyName); @@ -255,7 +249,7 @@ private List splitCollectionValues(final AnnotationType propertyType, fi if (propertyType.isComplexType()) { items.add(value); } else { - final String[] itemElements = value.toString().split(","); + final var itemElements = value.toString().split(","); items.addAll(Arrays.asList(itemElements)); } } diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCapabilitiesAnnotationsProvider.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCapabilitiesAnnotationsProvider.java index 075c64b86..112f8aeee 100644 --- a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCapabilitiesAnnotationsProvider.java +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCapabilitiesAnnotationsProvider.java @@ -37,7 +37,7 @@ String getPath() { @Override URI getUri() throws URISyntaxException { return new URI( - "https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Capabilities.V1.xml");// NOSONAR + "https://raw.githubusercontent.com/oasis-tcs/odata-vocabularies/main/vocabularies/Org.OData.Capabilities.V1.xml");// NOSONAR } } diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCoreAnnotationsProvider.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCoreAnnotationsProvider.java index 992d4d5a7..ad8040a59 100644 --- a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCoreAnnotationsProvider.java +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/provider/JavaBasedCoreAnnotationsProvider.java @@ -37,7 +37,7 @@ String getPath() { @Override URI getUri() throws URISyntaxException { return new URI( - "https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.xml");// NOSONAR + "https://raw.githubusercontent.com/oasis-tcs/odata-vocabularies/main/vocabularies/Org.OData.Core.V1.xml");// NOSONAR } } diff --git a/jpa/odata-jpa-processor-cb/.project b/jpa/odata-jpa-processor-cb/.project new file mode 100644 index 000000000..a35a7eb96 --- /dev/null +++ b/jpa/odata-jpa-processor-cb/.project @@ -0,0 +1,34 @@ + + + jpa-processor-cb + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java index 36ede91b1..384b76fe2 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java @@ -139,10 +139,8 @@ public int hashCode() { @Override public boolean equals(final Object object) { - if (this == object) return true; - if (!super.equals(object)) return false; - if (!(object instanceof CollectionJoinImpl)) return false; - final CollectionJoinImpl other = (CollectionJoinImpl) object; - return Objects.equals(attribute, other.attribute); + if (object instanceof final CollectionJoinImpl other)// NOSONAR + return Objects.equals(attribute, other.attribute); + return false; } } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java index d2039aede..346a50df5 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java @@ -538,7 +538,7 @@ public Predicate gt(@Nonnull final Expression x, @Nonnull fina */ @Override public In in(final Expression expression) { - return new PredicateImpl.In<>((Path) expression); + return new PredicateImpl.In<>((Path) expression, parameter); } /** @@ -550,7 +550,7 @@ public In in(final Expression expression) { @Override public In>> in(final List>> paths, final Subquery>> subquery) { - return new PredicateImpl.In>>(paths).value(subquery); + return new PredicateImpl.In>>(paths, parameter).value(subquery); } /** @@ -561,7 +561,7 @@ public In>> in(final List>> paths, */ @Override public In in(final Path path) { - return new PredicateImpl.In<>(path); + return new PredicateImpl.In<>(path, parameter); } /** diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java index 23a51b8d3..e73d72271 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java @@ -50,6 +50,8 @@ class CriteriaQueryImpl implements ProcessorCriteriaQuery, SqlConvertible private Optional> orderList; private Optional>> groupBy; private Optional> having; + private Optional maxResults; + private Optional firstResult; private final CriteriaBuilder cb; CriteriaQueryImpl(final Class clazz, final JPAServiceDocument sd, final AliasBuilder ab, @@ -61,6 +63,8 @@ class CriteriaQueryImpl implements ProcessorCriteriaQuery, SqlConvertible this.orderList = Optional.empty(); this.groupBy = Optional.empty(); this.having = Optional.empty(); + this.firstResult = Optional.empty(); + this.maxResults = Optional.empty(); this.aliasBuilder = ab; this.cb = cb; this.selectAliasBuilder = new AliasBuilder("S"); @@ -107,6 +111,14 @@ public StringBuilder asSQL(final StringBuilder statement) { .append(" "); ((SqlConvertible) e).asSQL(statement); }); + maxResults.ifPresent(limit -> statement.append(" ") + .append(SqlPagingFunctions.LIMIT) + .append(" ") + .append(limit)); + firstResult.ifPresent(offset -> statement.append(" ") + .append(SqlPagingFunctions.OFFSET) + .append(" ") + .append(offset)); return statement; } @@ -444,4 +456,38 @@ private void addInheritanceWhere(final FromImpl from, final List) join, inheritanceWhere); } } + + /** + * The position of the first result the query object was set to + * retrieve. Returns 0 if setFirstResult was not applied to the + * query object. + * @return position of the first result + * @since 2.0 + */ + @Override + public int getFirstResult() { + return firstResult.orElse(0); + } + + @Override + public void setFirstResult(final int startPosition) { + firstResult = Optional.of(startPosition); + } + + /** + * The maximum number of results the query object was set to + * retrieve. Returns Integer.MAX_VALUE if setMaxResults was not + * applied to the query object. + * @return maximum number of results + * @since 2.0 + */ + @Override + public int getMaxResults() { + return maxResults.orElse(Integer.MAX_VALUE); + } + + @Override + public void setMaxResults(final int maxResult) { + this.maxResults = Optional.of(maxResult); + } } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java index a0b3106b4..01fd2e907 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java @@ -371,7 +371,7 @@ public StringBuilder asSQL(final StringBuilder statement) { @Override public String getName() { - return null; + return index.toString(); } @Override diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/JPAAttributeWrapper.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/JPAAttributeWrapper.java index ee50172ba..5824575c2 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/JPAAttributeWrapper.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/JPAAttributeWrapper.java @@ -95,31 +95,6 @@ public boolean isCollection() { return false; } - @Override - public boolean isComplex() { - return false; - } - - @Override - public boolean isEnum() { - return false; - } - - @Override - public boolean isEtag() { - return false; - } - - @Override - public boolean isKey() { - return false; - } - - @Override - public boolean isSearchable() { - return false; - } - @Override public boolean hasProtection() { return false; @@ -149,4 +124,10 @@ public Class getJavaType() { public CsdlAnnotation getAnnotation(final String alias, final String term) throws ODataJPAModelException { return null; } + + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + return null; + } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ParameterBuffer.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ParameterBuffer.java index cbf356f47..33b100607 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ParameterBuffer.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ParameterBuffer.java @@ -16,7 +16,7 @@ class ParameterBuffer { private static final Log LOG = LogFactory.getLog(ParameterBuffer.class); private int index = 1; - private final Map> parameterByHash; + private final Map> parameterByHash; ParameterBuffer() { super(); @@ -32,7 +32,7 @@ ParameterExpression addValue(@Nonnull final S value, final Expressi ParameterExpression param = new ParameterExpression<>(index, Objects.requireNonNull(value), expression); if (!parameterByHash.containsKey(param.hashCode())) { - parameterByHash.put(param.hashCode(), param); + parameterByHash.put(param.hashCode(), (ParameterExpression) param); index++; } else { // Hibernate does not allow provisioning of parameter that are not used in a query @@ -42,7 +42,7 @@ ParameterExpression addValue(@Nonnull final S value, final Expressi return param; } - Map> getParameter() { + Map> getParameters() { return parameterByHash; } } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImpl.java index 2b4f78346..6cc4f9834 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImpl.java @@ -1,5 +1,6 @@ package com.sap.olingo.jpa.processor.cb.impl; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -18,6 +19,7 @@ import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; import com.sap.olingo.jpa.processor.cb.joiner.SqlConvertible; +import com.sap.olingo.jpa.processor.cb.joiner.StringBuilderCollector; /** * @@ -334,43 +336,77 @@ public BooleanOperator getOperator() { } static class In extends PredicateImpl implements CriteriaBuilder.In { - private Expression expression; + private Optional> expression; + private Optional>> values; + private ParameterBuffer parameter; @SuppressWarnings("unchecked") In(final List> paths, final Subquery subquery) { - super(new CompoundPathImpl(paths.stream().map(path -> (Path>) path).toList())); - this.expression = (Expression) Objects.requireNonNull(subquery); + this(paths.stream().map(path -> (Path>) path).toList(), null); + this.expression = Optional.of((Expression) Objects.requireNonNull(subquery)); } - In(final List>> paths) { + In(final List>> paths, final ParameterBuffer parameter) { super(new CompoundPathImpl(paths)); + this.values = Optional.empty(); + this.expression = Optional.empty(); + this.parameter = parameter; } @SuppressWarnings("unchecked") - In(final Path path) { - super(new CompoundPathImpl(Collections.singletonList((Path>) path))); + In(final Path path, final ParameterBuffer parameter) { + this(Collections.singletonList((Path>) path), parameter); } @Override public StringBuilder asSQL(final StringBuilder statement) { + + return expression.map(exp -> asSubQuerySQL(exp, statement)) + .orElseGet(() -> values.map(list -> asFixValueSQL(list, statement)) + .orElseThrow()); + } + + private StringBuilder asFixValueSQL(final List> list, final StringBuilder statement) { + prepareInExpression(statement); + statement.append(list.stream() + .collect(new StringBuilderCollector.ExpressionCollector(statement, ", "))); + return statement.append(CLOSING_BRACKET); + } + + private StringBuilder asSubQuerySQL(final Expression exp, final StringBuilder statement) { + prepareInExpression(statement); + return ((SqlConvertible) Objects.requireNonNull(exp)).asSQL(statement).append(CLOSING_BRACKET); + } + + private void prepareInExpression(final StringBuilder statement) { expressions.get(0).asSQL(statement) .append(" ") .append(SqlKeyWords.IN) .append(" ") .append(OPENING_BRACKET); - return ((SqlConvertible) Objects.requireNonNull(expression)).asSQL(statement).append(CLOSING_BRACKET); } @Override public jakarta.persistence.criteria.CriteriaBuilder.In value(final X value) { - throw new NotImplementedException(); + if (this.expression.isPresent()) + throw new IllegalStateException("Do not add a fixed value if an expression is already present"); + values.ifPresentOrElse(list -> list.add(parameter.addValue(value)), () -> createValues(value)); + return this; + } + + private void createValues(final X value) { + // parameter.addValue(value) + values = Optional.of(new ArrayList<>()); + values.get().add(parameter.addValue(value)); } @Override public jakarta.persistence.criteria.CriteriaBuilder.In value(final Expression value) { - if (this.expression != null) + if (this.values.isPresent()) + throw new IllegalStateException("Do not add an expression if a fixed value is already present"); + if (this.expression.isPresent()) throw new NotImplementedException(); - this.expression = Objects.requireNonNull(value); + this.expression = Optional.of(Objects.requireNonNull(value)); return this; } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java new file mode 100644 index 000000000..4348a5627 --- /dev/null +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java @@ -0,0 +1,23 @@ +package com.sap.olingo.jpa.processor.cb.impl; + +enum SqlPagingFunctions { + LIMIT("LIMIT", Integer.MAX_VALUE), + OFFSET("OFFSET", 0); + + private final String keyWord; + private final int defaultValue; + + private SqlPagingFunctions(final String keyWord, final int defaultValue) { + this.keyWord = keyWord; + this.defaultValue = defaultValue; + } + + @Override + public String toString() { + return keyWord; + } + + int defaultValue() { + return defaultValue; + } +} diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TupleImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TupleImpl.java index cc5ea8f82..3eb308f37 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TupleImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TupleImpl.java @@ -30,11 +30,11 @@ class TupleImpl implements Tuple { this(new Object[] { value }, selection, selectionIndex); } - TupleImpl(final Object[] values, final List> selPath, + TupleImpl(final Object[] values, final List> selection, final Map selectionIndex) { super(); this.values = values; - this.selection = selPath; + this.selection = selection; this.selectionIndex = selectionIndex; this.tupleElements = Optional.empty(); } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImpl.java index 3253b80a9..5ee945545 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImpl.java @@ -4,12 +4,16 @@ import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.CheckForNull; + import jakarta.persistence.EntityManager; import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; @@ -34,88 +38,223 @@ class TypedQueryImpl implements TypedQuery { + private static final String SET_A_PARAMETER_EXCEPTION = + "Set a parameter is not supported. Parameter have to be forwarded from criteria query"; private final CriteriaQueryImpl parent; - private final Query query; private final ProcessorSelection selection; + private final ParameterBuffer parameterBuffer; + private final EntityManager em; + private Optional lockMode; + private final Map hints; + private Optional flushMode; TypedQueryImpl(final CriteriaQuery criteriaQuery, final EntityManager em, final ParameterBuffer parameterBuffer) { - final StringBuilder sql = new StringBuilder(); + this.parent = (CriteriaQueryImpl) criteriaQuery; this.parent.getResultType(); this.selection = (ProcessorSelection) parent.getSelection(); - this.query = em.createNativeQuery(parent.asSQL(sql).toString()); - copyParameter(parameterBuffer.getParameter()); + this.parameterBuffer = parameterBuffer; + this.em = em; + this.hints = new HashMap<>(); + this.lockMode = Optional.empty(); + this.flushMode = Optional.empty(); } @Override public int executeUpdate() { - return query.executeUpdate(); + return createNativeQuery().executeUpdate(); } @Override public int getFirstResult() { - return query.getFirstResult(); + return parent.getFirstResult(); } + @CheckForNull @Override public FlushModeType getFlushMode() { - return query.getFlushMode(); + return flushMode.orElse(null); } @Override public Map getHints() { - return query.getHints(); + return this.hints; } + @CheckForNull @Override public LockModeType getLockMode() { - return query.getLockMode(); + return lockMode.orElse(null); } @Override public int getMaxResults() { - return query.getMaxResults(); + return parent.getMaxResults(); } + /** + * Get the parameter object corresponding to the declared + * positional parameter with the given position. + * This method is not required to be supported for native + * queries. + * @param position position + * @return parameter object + * @throws IllegalArgumentException if the parameter with the + * specified position does not exist + * @throws IllegalStateException if invoked on a native + * query when the implementation does not support + * this use + * @since 2.0 + */ @Override public Parameter getParameter(final int position) { - return query.getParameter(position); + final Optional parameter = parameterBuffer + .getParameters() + .values() + .stream() + .filter(p -> p.getPosition().equals(position)) + .findFirst(); + return (Parameter) parameter + .orElseThrow(() -> new IllegalArgumentException("No parameter with index " + position)); } + /** + * Get the parameter object corresponding to the declared + * positional parameter with the given position and type. + * This method is not required to be supported by the provider. + * @param position position + * @param type type + * @return parameter object + * @throws IllegalArgumentException if the parameter with the + * specified position does not exist or is not assignable + * to the type + * @throws IllegalStateException if invoked on a native + * query or Jakarta Persistence query language query when + * the implementation does not support this use + * @since 2.0 + */ + @SuppressWarnings("unchecked") @Override public Parameter getParameter(final int position, final Class type) { - return query.getParameter(position, type); + final var parameter = getParameter(position); + if (parameter.getParameterType() != null && type != null && !type.isAssignableFrom(parameter.getParameterType())) + throw new IllegalArgumentException("Parameter at " + position + " has different type"); + return (Parameter) parameter; } + /** + * Get the parameter object corresponding to the declared + * parameter of the given name. + * This method is not required to be supported for native + * queries. + * @param name parameter name + * @return parameter object + * @throws IllegalArgumentException if the parameter of the + * specified name does not exist + * @throws IllegalStateException if invoked on a native + * query when the implementation does not support + * this use + * @since 2.0 + */ @Override public Parameter getParameter(final String name) { - return query.getParameter(name); + final var position = Integer.valueOf(name); + return getParameter(position); } + /** + * Get the parameter object corresponding to the declared + * parameter of the given name and type. + * This method is required to be supported for criteria queries + * only. + * @param name parameter name + * @param type type + * @return parameter object + * @throws IllegalArgumentException if the parameter of the + * specified name does not exist or is not assignable + * to the type + * @throws IllegalStateException if invoked on a native + * query or Jakarta Persistence query language query when + * the implementation does not support this use + * @since 2.0 + */ + @SuppressWarnings("unchecked") @Override public Parameter getParameter(final String name, final Class type) { - return query.getParameter(name, type); + final var parameter = getParameter(name); + if (parameter.getParameterType() != null && type != null && !type.isAssignableFrom(parameter.getParameterType())) + throw new IllegalArgumentException("Parameter with name " + name + " has different type"); + return (Parameter) parameter; } + /** + * Get the parameter objects corresponding to the declared + * parameters of the query. + * Returns empty set if the query has no parameters. + * This method is not required to be supported for native + * queries. + * @return set of the parameter objects + * @throws IllegalStateException if invoked on a native + * query when the implementation does not support + * this use + * @since 2.0 + */ @Override public Set> getParameters() { - return query.getParameters(); + return parameterBuffer.getParameters() + .values().stream().collect(Collectors.toSet()); } + /** + * Return the input value bound to the positional parameter. + * (Note that OUT parameters are unbound.) + * @param position position + * @return parameter value + * @throws IllegalStateException if the parameter has not been + * been bound + * @throws IllegalArgumentException if the parameter with the + * specified position does not exist + * @since 2.0 + */ @Override public Object getParameterValue(final int position) { - return query.getParameterValue(position); + final Optional parameter = parameterBuffer + .getParameters() + .values() + .stream() + .filter(p -> p.getPosition().equals(position)) + .findFirst(); + return parameter.orElseThrow(() -> new IllegalArgumentException("No parameter with index " + position)); } + /** + * Return the input value bound to the parameter. + * (Note that OUT parameters are unbound.) + * @param param parameter object + * @return parameter value + * @throws IllegalArgumentException if the parameter is not + * a parameter of the query + * @throws IllegalStateException if the parameter has not been + * been bound + * @since 2.0 + */ + @SuppressWarnings("unchecked") @Override public X getParameterValue(final Parameter param) { - return query.getParameterValue(param); + final Optional> parameter = parameterBuffer.getParameters().values() + .stream() + .filter(p -> p.equals(param)) + .findFirst(); + + return (X) parameter.map(ParameterExpression::getValue) + .orElseThrow(() -> new IllegalArgumentException("Parameter unknown " + param)); } @Override public Object getParameterValue(final String name) { - return query.getParameterValue(name); + final ParameterExpression parameter = (ParameterExpression) getParameter(name); + return parameter.getValue(); } /** @@ -141,8 +280,7 @@ public Object getParameterValue(final String name) { @SuppressWarnings("unchecked") @Override public List getResultList() { - - final List result = query.getResultList(); + final List result = createNativeQuery().getResultList(); if (parent.getResultType().isAssignableFrom(Tuple.class)) { if (result.isEmpty()) return Collections.emptyList(); @@ -192,98 +330,99 @@ public T getSingleResult() { @Override public boolean isBound(final Parameter param) { - return query.isBound(param); + return parameterBuffer.getParameters().containsValue(param); } @Override public TypedQuery setFirstResult(final int startPosition) { - - query.setFirstResult(startPosition); + parent.setFirstResult(startPosition); return this; } @Override public TypedQuery setFlushMode(final FlushModeType flushMode) { - query.setFlushMode(flushMode); + this.flushMode = Optional.ofNullable(flushMode); return this; } @Override public TypedQuery setHint(final String hintName, final Object value) { - query.setHint(hintName, value); + this.hints.put(hintName, value); return this; } @Override public TypedQuery setLockMode(final LockModeType lockMode) { - query.setLockMode(lockMode); + this.lockMode = Optional.ofNullable(lockMode); return this; } @Override public TypedQuery setMaxResults(final int maxResult) { - query.setMaxResults(maxResult); + this.parent.setMaxResults(maxResult); return this; } + /** + * Bind an instance of java.util.Calendar to a positional parameter. + * @throws IllegalStateException Setting parameter is not supported + */ @Override public TypedQuery setParameter(final int position, final Calendar value, final TemporalType temporalType) { - query.setParameter(position, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final int position, final Date value, final TemporalType temporalType) { - query.setParameter(position, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final int position, final Object value) { - query.setParameter(position, value); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final Parameter param, final Calendar value, final TemporalType temporalType) { - query.setParameter(param, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final Parameter param, final Date value, final TemporalType temporalType) { - query.setParameter(param, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final Parameter param, final X value) { - query.setParameter(param, value); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final String name, final Calendar value, final TemporalType temporalType) { - query.setParameter(name, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final String name, final Date value, final TemporalType temporalType) { - query.setParameter(name, value, temporalType); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } @Override public TypedQuery setParameter(final String name, final Object value) { - query.setParameter(name, value); - return this; + throw new IllegalStateException(SET_A_PARAMETER_EXCEPTION); } + @SuppressWarnings("unchecked") @Override public X unwrap(final Class clazz) { - return query.unwrap(clazz); + if (clazz.isAssignableFrom(this.getClass())) { + return (X) this; + } + if (clazz.isAssignableFrom(parent.getClass())) { + return (X) parent; + } + throw new PersistenceException("Unable to unwrap " + clazz.getName()); } private List> buildSelection() { @@ -297,9 +436,9 @@ private Map buildSelectionIndex(final List count[0]++)); } - private void copyParameter(final Map> map) { - map.entrySet().stream().forEach(es -> this.query.setParameter(es.getValue().getPosition(), es.getValue() - .getValue())); + private void copyParameter(final Query query, final Map> map) { + map.entrySet().stream().forEach(entry -> query.setParameter(entry.getValue().getPosition(), + entry.getValue().getValue())); } private List> toAttributeList(final List> selectionPath) { @@ -310,4 +449,11 @@ private List> toAttributeList(final List parameter : cut.getParameter().getParameter().values()) { + assertEquals(1, cut.getParameter().getParameters().size()); + for (final ParameterExpression parameter : cut.getParameter().getParameters().values()) { if (parameter.getPosition() == 1) assertEquals("NUTS2", parameter.getValue()); } @@ -297,7 +297,7 @@ void testBinaryNumericExpressionWithExpression(final Method m, final String exp) final Expression act = (Expression) m.invoke(cut, params); assertNotNull(act); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); - assertEquals(0, cut.getParameter().getParameter().size()); + assertEquals(0, cut.getParameter().getParameters().size()); } @ParameterizedTest @@ -311,8 +311,8 @@ void testBinaryNumericExpressionWithObject(final Method m, final String exp) thr assertNotNull(act); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); - assertEquals(1, cut.getParameter().getParameter().size()); - for (final ParameterExpression parameter : cut.getParameter().getParameter().values()) { + assertEquals(1, cut.getParameter().getParameters().size()); + for (final ParameterExpression parameter : cut.getParameter().getParameters().values()) { if (parameter.getPosition() == 1) assertEquals(1000, parameter.getValue()); } @@ -330,8 +330,8 @@ void testBinaryNumericExpressionWithObjectFirst(final Method m, final String exp assertNotNull(act); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); - assertEquals(1, cut.getParameter().getParameter().size()); - for (final ParameterExpression parameter : cut.getParameter().getParameter().values()) { + assertEquals(1, cut.getParameter().getParameters().size()); + for (final ParameterExpression parameter : cut.getParameter().getParameters().values()) { if (parameter.getPosition() == 1) assertEquals(1000, parameter.getValue()); } @@ -399,7 +399,7 @@ void testCreateGeExpressionWithExpression() { assertNotNull(act); assertEquals("(E0.\"Area\" >= E0.\"Population\")", ((SqlConvertible) act).asSQL(statement).toString()); - assertEquals(0, cut.getParameter().getParameter().size()); + assertEquals(0, cut.getParameter().getParameters().size()); } @Test @@ -494,7 +494,7 @@ void testLiteralExpressionReturnsParameter() { final Expression act = cut.literal(LocalDate.now()); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); assertNotNull(cut.getParameter()); - assertEquals(1, cut.getParameter().getParameter().size()); + assertEquals(1, cut.getParameter().getParameters().size()); } @Test @@ -777,7 +777,7 @@ void testCreateEqualWithValueAndConverter() { final AccessRights[] rights = { AccessRights.READ, AccessRights.DELETE }; final Expression act = cut.equal(person.get("accessRights"), rights); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); - for (final ParameterExpression parameter : cut.getParameter().getParameter().values()) { + for (final ParameterExpression parameter : cut.getParameter().getParameters().values()) { if (parameter.getPosition() == 1) assertEquals((short) 9, parameter.getValue()); } @@ -792,7 +792,7 @@ void testCreateEqualWithLiteralAndConverter() { final AccessRights[] rights = { AccessRights.READ, AccessRights.DELETE }; final Expression act = cut.equal(person.get("accessRights"), cut.literal(rights)); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); - for (final ParameterExpression parameter : cut.getParameter().getParameter().values()) { + for (final ParameterExpression parameter : cut.getParameter().getParameters().values()) { if (parameter.getPosition() == 1) assertEquals((short) 9, parameter.getValue()); } @@ -882,7 +882,7 @@ void assertConcatExpression(final String stringA, final String stringB, final Ex final StringBuilder builder = new StringBuilder(); assertTrue(act instanceof ConcatExpression); assertEquals("CONCAT(?1, ?2)", ((ExpressionImpl) act).asSQL(builder).toString()); - final Map> actMap = cut.getParameter().getParameter(); + final Map> actMap = cut.getParameter().getParameters(); assertEquals(2, actMap.size()); boolean aFound = false; boolean bFound = false; diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java index 0d147c38f..790ca5885 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java @@ -142,10 +142,11 @@ void testHaving() { cut.groupBy(act.get("name2")); cut.multiselect(act.get("aBCClass"), act.get("name2")); assertEquals( - "SELECT E0.\"ABCClass\" S0, E0.\"NameLine2\" S1 FROM \"OLINGO\".\"BusinessPartner\" E0 " - + "WHERE (E0.\"Type\" = ?2) " - + "GROUP BY E0.\"NameLine2\" " - + "HAVING (COUNT(E0.\"ID\") > ?1)", + """ + SELECT E0."ABCClass" S0, E0."NameLine2" S1 FROM "OLINGO"."BusinessPartner" E0 \ + WHERE (E0."Type" = ?2) \ + GROUP BY E0."NameLine2" \ + HAVING (COUNT(E0."ID") > ?1)""", cut.asSQL(statement).toString()); } @@ -165,4 +166,26 @@ void testFromRethrowsException() throws ODataJPAModelException { cut = new CriteriaQueryImpl<>(Object.class, serviceDocument, cb); assertThrows(InternalServerError.class, () -> cut.from(AdministrativeDivision.class)); } + + @Test + void testGetFirstResultWhenNotSet() { + assertEquals(0, cut.getFirstResult()); + } + + @Test + void testGetFirstResultReturnsSetValue() { + cut.setFirstResult(122); + assertEquals(122, cut.getFirstResult()); + } + + @Test + void testGetMaxResultsWhenNotSet() { + assertEquals(Integer.MAX_VALUE, cut.getMaxResults()); + } + + @Test + void testGetMaxResultsReturnsSetValue() { + cut.setMaxResults(122); + assertEquals(122, cut.getMaxResults()); + } } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/ParameterExpressionImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/ParameterExpressionImplTest.java index 5b7dd2082..2cea0991f 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/ParameterExpressionImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/ParameterExpressionImplTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.mockito.Mockito.mock; @@ -35,7 +34,7 @@ void setup() { @Test void testGetNameReturnsNull() { - assertNull(cut.getName()); + assertEquals("10", cut.getName()); } @Test diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PrerdicateImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PrerdicateImplTest.java index 5ecb63b0d..37e369c88 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PrerdicateImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PrerdicateImplTest.java @@ -44,18 +44,18 @@ class PredicateImplTest extends BuilderBaseTest { private PredicateImpl cut; private StringBuilder statement; - private CriteriaQuery q; + private CriteriaQuery query; static Stream notImplemented() throws NoSuchMethodException, SecurityException { - final Class c = PredicateImpl.class; + final Class clazz = PredicateImpl.class; return Stream.of( - arguments(c.getMethod("in", Expression.class)), - arguments(c.getMethod("in", Collection.class)), - arguments(c.getMethod("in", Expression[].class)), - arguments(c.getMethod("in", Object[].class)), - arguments(c.getMethod("getCompoundSelectionItems")), - arguments(c.getMethod("getJavaType")), - arguments(c.getMethod("as", Class.class))); + arguments(clazz.getMethod("in", Expression.class)), + arguments(clazz.getMethod("in", Collection.class)), + arguments(clazz.getMethod("in", Expression[].class)), + arguments(clazz.getMethod("in", Object[].class)), + arguments(clazz.getMethod("getCompoundSelectionItems")), + arguments(clazz.getMethod("getJavaType")), + arguments(clazz.getMethod("as", Class.class))); } @BeforeEach @@ -64,14 +64,14 @@ void setup() { final CriteriaBuilder cb = new CriteriaBuilderImpl(sd, new ParameterBuffer()); statement = new StringBuilder(); - q = cb.createTupleQuery(); + query = cb.createTupleQuery(); } @ParameterizedTest @MethodSource("notImplemented") - void testThrowsNotImplemented(final Method m) throws IllegalAccessException, IllegalArgumentException { + void testThrowsNotImplemented(final Method method) throws IllegalAccessException, IllegalArgumentException { - testNotImplemented(m, cut); + testNotImplemented(method, cut); } @Test @@ -95,16 +95,10 @@ void testInCreated() { assertNotNull(in); } - @Test - void testInCreatedRequiresSubquery() { - - assertThrows(NullPointerException.class, () -> new PredicateImpl.In<>(Collections.emptyList(), null)); - } - @Test void testInGetExpressionReturnsFirstPath() { - final Root adminDivision = q.from(AdministrativeDivision.class); + final Root adminDivision = query.from(AdministrativeDivision.class); final List> paths = Arrays.asList(adminDivision.get("codeID"), adminDivision.get("parentCodeID")); @SuppressWarnings("unchecked") final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); @@ -119,15 +113,13 @@ void testInValueNotImplemented() { final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); final In act = new PredicateImpl.In<>(Collections.emptyList(), subQuery); - - assertThrows(NotImplementedException.class, () -> act.value(Integer.valueOf(5))); assertThrows(NotImplementedException.class, () -> act.value(mock(Expression.class))); } @Test void testInAsSqlGeneratePath() { final String exp = ("(E0.\"CodeID\", E0.\"CodePublisher\") IN ()"); - final Root adminDivision = q.from(AdministrativeDivision.class); + final Root adminDivision = query.from(AdministrativeDivision.class); final List> paths = Arrays.asList(adminDivision.get("codeID"), adminDivision.get("codePublisher")); @SuppressWarnings("unchecked") final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); @@ -161,32 +153,78 @@ public StringBuilder answer(final InvocationOnMock invocation) throws Throwable final In act = new PredicateImpl.In<>(Collections.emptyList(), subQuery); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); } - + @Test void testInCreatedFromExpression() { @SuppressWarnings("unchecked") final Path path = mock(Path.class); - final Predicate in = new PredicateImpl.In<>(path); + final Predicate in = new PredicateImpl.In<>(path, null); assertNotNull(in); } - + @SuppressWarnings("unchecked") @Test - void testInAddValue() { + void testInAddExpressionValue() { final Path path = mock(Path.class); final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); - final In in = new PredicateImpl.In<>(path); + final In in = new PredicateImpl.In<>(path, null); assertNotNull(in.value(subQuery)); } - + + @SuppressWarnings("unchecked") + @Test + void testInThrowsExceptionOnMultipleAddExpressionValue() { + final Path path = mock(Path.class); + final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); + final In in = new PredicateImpl.In<>(path, null); + in.value(subQuery); + assertThrows(NotImplementedException.class, () -> in.value(subQuery)); + } + + @SuppressWarnings("unchecked") + @Test + void testInAddFixValue() { + final Path path = mock(Path.class); + final In in = new PredicateImpl.In<>(path, new ParameterBuffer()); + assertNotNull(in.value("Test")); + } + @SuppressWarnings("unchecked") @Test - void testInThrowsExceptionOnMultipleAddValue() { + void testInAddMultipleFixValue() { + final Path path = mock(Path.class); + final In in = new PredicateImpl.In<>(path, new ParameterBuffer()); + assertNotNull(in.value("Test1").value("Test2").value("Test3")); + } + + @SuppressWarnings("unchecked") + @Test + void testInThrowsExceptionOnAddExpressionIfFixValueExists() { final Path path = mock(Path.class); final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); - final In in = new PredicateImpl.In<>(path); + final In in = new PredicateImpl.In<>(path, new ParameterBuffer()); + in.value("Test1"); + assertThrows(IllegalStateException.class, () -> in.value(subQuery)); + } + + @SuppressWarnings("unchecked") + @Test + void testInThrowsExceptionOnAddFixValueIfExpressionExists() { + final Path path = mock(Path.class); + final Subquery subQuery = mock(Subquery.class, withSettings().extraInterfaces(SqlConvertible.class)); + final In in = new PredicateImpl.In<>(path, new ParameterBuffer()); in.value(subQuery); - assertThrows(NotImplementedException.class, () -> in.value(subQuery)); + assertThrows(IllegalStateException.class, () -> in.value("Test1")); + } + + @Test + void testInAddMultipleFixValueAsSQL() { + final String exp = ("() IN (?1, ?2, ?3)"); + final ParameterBuffer parameter = new ParameterBuffer(); + final In in = new PredicateImpl.In<>(Collections.emptyList(), parameter); + in.value("Test1").value("Test2").value("Test3"); + assertEquals(exp, ((PredicateImpl.In) in).asSQL(statement).toString()); + assertEquals(3, parameter.getParameters().size()); } @Test diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypeConverterTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypeConverterTest.java index 9e63984f0..60134baf9 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypeConverterTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypeConverterTest.java @@ -249,8 +249,8 @@ void testConvertTemporalThrowsExceptionOnUnsupported() { @Test void testConvertDurationThrowsExceptionOnUnsupported() { - - assertThrows(IllegalArgumentException.class, () -> convert(Integer.valueOf(10), Duration.class)); + final Integer i = 10; + assertThrows(IllegalArgumentException.class, () -> convert(i, Duration.class)); } @Test diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImplTest.java index f7c4486c4..76d47ecf9 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TypedQueryImplTest.java @@ -1,6 +1,9 @@ package com.sap.olingo.jpa.processor.cb.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -15,6 +18,7 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; +import jakarta.persistence.PersistenceException; import jakarta.persistence.Query; import jakarta.persistence.TemporalType; @@ -49,139 +53,166 @@ void testExecuteUpdate() { @Test void testGetFirstResult() { cut.getFirstResult(); - verify(q).getFirstResult(); + verify(cq).getFirstResult(); } @Test - void testGetFlushMode() { - cut.getFlushMode(); - verify(q).getFlushMode(); + void testGetParameterByPosition() { + final var exp = parameterBuffer.addValue("Test"); + final var act = cut.getParameter(1); + assertEquals(exp, act); } @Test - void testGetHints() { - cut.getHints(); - verify(q).getHints(); + void testGetParameterByPositionThrowsIfNoParameterAtPosition() { + parameterBuffer.addValue("Test"); + assertThrows(IllegalArgumentException.class, () -> cut.getParameter(2)); } @Test - void testGetLockMode() { - cut.getLockMode(); - verify(q).getLockMode(); + void testGetParameterByPositionType() { + final var exp = parameterBuffer.addValue(Integer.valueOf(100)); + final var act = cut.getParameter(1, Integer.class); + assertEquals(exp, act); } @Test - void testGetMaxResults() { - cut.getMaxResults(); - verify(q).getMaxResults(); + void testGetParameterByPositionTypeThrowsIfNoParameterAtPosition() { + parameterBuffer.addValue(Integer.valueOf(100)); + assertThrows(IllegalArgumentException.class, () -> cut.getParameter(2, Integer.class)); } @Test - void testGetParameterByPosition() { - cut.getParameter(1); - verify(q).getParameter(1); + void testGetParameterByPositionTypeThrowsWrongType() { + parameterBuffer.addValue(Integer.valueOf(100)); + assertThrows(IllegalArgumentException.class, () -> cut.getParameter(1, String.class)); } @Test - void testGetParameterByPositionType() { - cut.getParameter(1, Integer.class); - verify(q).getParameter(1, Integer.class); + void testGetParameterByName() { + final var exp = parameterBuffer.addValue(Integer.valueOf(100)); + assertEquals(exp, cut.getParameter("1")); } @Test - void testGetParameterByName() { - cut.getParameter("Test"); - verify(q).getParameter("Test"); + void testGetParameterByNameThrowsIfNameNotANumber() { + parameterBuffer.addValue(Integer.valueOf(100)); + assertThrows(IllegalArgumentException.class, () -> cut.getParameter("1.2")); } @Test void testGetParameterByNameType() { - cut.getParameter("Test", String.class); - verify(q).getParameter("Test", String.class); + final var exp = parameterBuffer.addValue(Integer.valueOf(100)); + assertEquals(exp, cut.getParameter("1", Integer.class)); + } + + @Test + void testGetParameterByNameTypeThrowsWrongType() { + parameterBuffer.addValue(Integer.valueOf(100)); + assertThrows(IllegalArgumentException.class, () -> cut.getParameter("1", String.class)); } @Test void testGetParameters() { - cut.getParameters(); - verify(q).getParameters(); + final var exp = parameterBuffer.addValue(Integer.valueOf(100)); + final var act = cut.getParameters(); + assertEquals(1, act.size()); + assertTrue(act.contains(exp)); } @Test void testGetParameterValueByPosition() { - cut.getParameterValue(1); - verify(q).getParameterValue(1); + final var exp = parameterBuffer.addValue("Test"); + final var act = cut.getParameterValue(1); + assertEquals(exp, act); + } + + @Test + void testGetParameterValueByPositionFails() { + parameterBuffer.addValue("Test"); + assertThrows(IllegalArgumentException.class, () -> cut.getParameterValue(2)); } @Test void testGetParameterValueByParameter() { - @SuppressWarnings("unchecked") - final Parameter param = mock(Parameter.class); - cut.getParameterValue(param); - verify(q).getParameterValue(param); + final var exp = parameterBuffer.addValue("Test"); + parameterBuffer.addValue(Integer.valueOf(100)); + assertEquals("Test", cut.getParameterValue(exp)); + } + + @SuppressWarnings("unchecked") + @Test + void testGetParameterValueByParameterThrowsIfParameterNotExists() { + final var dummy = mock(Parameter.class); + parameterBuffer.addValue(Integer.valueOf(100)); + assertThrows(IllegalArgumentException.class, () -> cut.getParameterValue(dummy)); } @Test void testGetParameterValueByName() { - cut.getParameterValue("Test"); - verify(q).getParameterValue("Test"); + parameterBuffer.addValue(Integer.valueOf(100)); + assertEquals(100, cut.getParameterValue("1")); } @Test void testIsBound() { - @SuppressWarnings("unchecked") - final Parameter param = mock(Parameter.class); - cut.isBound(param); - verify(q).isBound(param); + final var act = parameterBuffer.addValue(Integer.valueOf(10)); + assertTrue(cut.isBound(act)); } @Test void testSetFirstResult() { assertEquals(cut, cut.setFirstResult(1)); - verify(q).setFirstResult(1); + verify(cq).setFirstResult(1); } @Test - void testSetFlushMode() { + void testSetGetFlushMode() { assertEquals(cut, cut.setFlushMode(FlushModeType.AUTO)); - verify(q).setFlushMode(FlushModeType.AUTO); + assertEquals(FlushModeType.AUTO, cut.getFlushMode()); } @Test - void testSetHint() { + void testSetGetHint() { assertEquals(cut, cut.setHint("Test", "Test")); - verify(q).setHint("Test", "Test"); + final var act = cut.getHints(); + assertEquals(1, act.size()); + assertEquals("Test", act.get("Test")); } @Test - void testSetLockMode() { + void testSetGetLockMode() { assertEquals(cut, cut.setLockMode(LockModeType.OPTIMISTIC)); - verify(q).setLockMode(LockModeType.OPTIMISTIC); + assertEquals(LockModeType.OPTIMISTIC, cut.getLockMode()); } @Test void testSetMaxResults() { assertEquals(cut, cut.setMaxResults(1)); - verify(q).setMaxResults(1); + verify(cq).setMaxResults(1); + } + + @Test + void testGetMaxResults() { + cut.getMaxResults(); + verify(cq).getMaxResults(); } @Test void testSetParameterTemporalByPosition() { final Calendar value = new GregorianCalendar(); - assertEquals(cut, cut.setParameter(1, value, TemporalType.TIMESTAMP)); - verify(q).setParameter(1, value, TemporalType.TIMESTAMP); + assertThrows(IllegalStateException.class, () -> cut.setParameter(1, value, TemporalType.TIMESTAMP)); } @Test void testSetParameterTemporalDateByPosition() { final Date value = Date.valueOf(LocalDate.now()); - assertEquals(cut, cut.setParameter(2, value, TemporalType.DATE)); - verify(q).setParameter(2, value, TemporalType.DATE); + assertThrows(IllegalStateException.class, () -> cut.setParameter(2, value, TemporalType.DATE)); } @Test void testSetParameterPosition() { - assertEquals(cut, cut.setParameter(1, "Test")); - verify(q).setParameter(1, "Test"); + assertThrows(IllegalStateException.class, () -> cut.setParameter(1, "Test")); } @Test @@ -189,8 +220,7 @@ void testSetParameterByTemporalByParameter() { @SuppressWarnings("unchecked") final Parameter param = mock(Parameter.class); final Calendar value = new GregorianCalendar(); - assertEquals(cut, cut.setParameter(param, value, TemporalType.TIMESTAMP)); - verify(q).setParameter(param, value, TemporalType.TIMESTAMP); + assertThrows(IllegalStateException.class, () -> cut.setParameter(param, value, TemporalType.TIMESTAMP)); } @Test @@ -198,41 +228,40 @@ void testSetParameterByTemporalDateByParameter() { @SuppressWarnings("unchecked") final Parameter param = mock(Parameter.class); final Date value = Date.valueOf(LocalDate.now()); - assertEquals(cut, cut.setParameter(param, value, TemporalType.DATE)); - verify(q).setParameter(param, value, TemporalType.DATE); + assertThrows(IllegalStateException.class, () -> cut.setParameter(param, value, TemporalType.DATE)); } @Test void testSetParameterByValue() { @SuppressWarnings("unchecked") final Parameter param = mock(Parameter.class); - assertEquals(cut, cut.setParameter(param, 1)); - verify(q).setParameter(param, 1); + assertThrows(IllegalStateException.class, () -> cut.setParameter(param, 1)); } @Test void testSetParameterByTemporalCalendarByName() { final Calendar value = new GregorianCalendar(); - assertEquals(cut, cut.setParameter("Test", value, TemporalType.TIME)); - verify(q).setParameter("Test", value, TemporalType.TIME); + assertThrows(IllegalStateException.class, () -> cut.setParameter("Test", value, TemporalType.TIME)); } @Test void testSetParameterByTemporalDateByName() { final Date value = Date.valueOf(LocalDate.now()); - assertEquals(cut, cut.setParameter("Test", value, TemporalType.DATE)); - verify(q).setParameter("Test", value, TemporalType.DATE); + assertThrows(IllegalStateException.class, () -> cut.setParameter("Test", value, TemporalType.DATE)); } @Test void testSetParameterByName() { - assertEquals(cut, cut.setParameter("Test", "Test")); - verify(q).setParameter("Test", "Test"); + assertThrows(IllegalStateException.class, () -> cut.setParameter("Test", "Test")); + } + + @Test + void testUnwrapThrowsException() { + assertThrows(PersistenceException.class, () -> cut.unwrap(String.class)); } @Test void testUnwrap() { - cut.unwrap(String.class); - verify(q).unwrap(String.class); + assertNotNull(cut.unwrap(Query.class)); } } diff --git a/jpa/odata-jpa-processor-ext/.project b/jpa/odata-jpa-processor-ext/.project new file mode 100644 index 000000000..6187274cb --- /dev/null +++ b/jpa/odata-jpa-processor-ext/.project @@ -0,0 +1,47 @@ + + + jpa-processor-ext + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-processor-ext/pom.xml b/jpa/odata-jpa-processor-ext/pom.xml index e999c8395..67e448201 100644 --- a/jpa/odata-jpa-processor-ext/pom.xml +++ b/jpa/odata-jpa-processor-ext/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.sap.olingo @@ -13,7 +13,6 @@ com.sap.olingo odata-jpa-metadata - com.sap.olingo odata-jpa-test diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorCriteriaQuery.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorCriteriaQuery.java index d487aae92..ff5c07eac 100644 --- a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorCriteriaQuery.java +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorCriteriaQuery.java @@ -16,4 +16,39 @@ public interface ProcessorCriteriaQuery extends CriteriaQuery, ProcessorSu */ Root from(final ProcessorSubquery subquery); + /** + * The position of the first result the query object was set to + * retrieve. Returns 0 if setFirstResult was not applied to the + * query object. + * @return position of the first result + * @since 2.0 + */ + int getFirstResult(); + + /** + * Set the position of the first result to retrieve. + * @param startPosition position of the first result, + * numbered from 0 + * @return the same query instance + * @throws IllegalArgumentException if the argument is negative + */ + void setFirstResult(int startPosition); + + /** + * The maximum number of results the query object was set to + * retrieve. Returns Integer.MAX_VALUE if setMaxResults was not + * applied to the query object. + * @return maximum number of results + * @since 2.0 + */ + int getMaxResults(); + + /** + * Set the maximum number of results to retrieve. + * @param maxResult maximum number of results to retrieve + * @return the same query instance + * @throws IllegalArgumentException if the argument is negative + */ + void setMaxResults(int maxResult); + } diff --git a/jpa/odata-jpa-processor-parallel/.project b/jpa/odata-jpa-processor-parallel/.project new file mode 100644 index 000000000..144b812ee --- /dev/null +++ b/jpa/odata-jpa-processor-parallel/.project @@ -0,0 +1,34 @@ + + + jpa-processor-parallel + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-processor-parallel/README.md b/jpa/odata-jpa-processor-parallel/README.md index 9e1737783..0ac160d45 100644 --- a/jpa/odata-jpa-processor-parallel/README.md +++ b/jpa/odata-jpa-processor-parallel/README.md @@ -8,4 +8,4 @@ This project contains JPA Processor enhancements to process OData requests in pa .setBatchProcessorFactory(new JPAODataParallelBatchProcessorFactory()) ``` -It shall be mentioned that the OData specification would allow a parallel processing only if the clients sends a `continue-on-error` header, see +It shall be mentioned that the OData specification would allow a parallel processing only if the clients sends a `continue-on-error` header, see \ No newline at end of file diff --git a/jpa/odata-jpa-processor-parallel/pom.xml b/jpa/odata-jpa-processor-parallel/pom.xml index 6cbb787b1..b6860146c 100644 --- a/jpa/odata-jpa-processor-parallel/pom.xml +++ b/jpa/odata-jpa-processor-parallel/pom.xml @@ -37,10 +37,5 @@ odata-jpa-processor-cb test - - javax.servlet - javax.servlet-api - test - \ No newline at end of file diff --git a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/IntegrationTestHelper.java b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/IntegrationTestHelper.java index 5717a6708..2b8708832 100644 --- a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/IntegrationTestHelper.java +++ b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/IntegrationTestHelper.java @@ -34,8 +34,6 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestProcessor; import com.sap.olingo.jpa.processor.core.api.JPAODataSessionContextAccess; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaRequestMapper; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaResponseMapper; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; import com.sap.olingo.jpa.processor.core.util.HttpRequestHeaderDouble; @@ -71,7 +69,7 @@ public IntegrationTestHelper(final EntityManagerFactory emf, final String urlPat sessionContext); handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); handler.register(new JPAODataBatchProcessor(sessionContext, requestContext)); - handler.process(new JakartaRequestMapper(req), new JakartaResponseMapper(resp)); + handler.process(req, resp); } public List getRawBatchResult() throws IOException { diff --git a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaRequestMapper.java b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaRequestMapper.java deleted file mode 100644 index e9866f17a..000000000 --- a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaRequestMapper.java +++ /dev/null @@ -1,380 +0,0 @@ -package com.sap.olingo.jpa.processor.test.util; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.Principal; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Map; - -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.Part; - -import jakarta.servlet.http.HttpServletRequest; - -public class JakartaRequestMapper implements javax.servlet.http.HttpServletRequest { - - private final HttpServletRequest jakartaRequest; - - public JakartaRequestMapper(final HttpServletRequest jakartaRequest) { - super(); - this.jakartaRequest = jakartaRequest; - } - - @Override - public Object getAttribute(final String name) { - return jakartaRequest.getAttribute(name); - } - - @Override - public Enumeration getAttributeNames() { - return jakartaRequest.getAttributeNames(); - } - - @Override - public String getCharacterEncoding() { - return jakartaRequest.changeSessionId(); - } - - @Override - public void setCharacterEncoding(final String env) throws UnsupportedEncodingException { - jakartaRequest.setCharacterEncoding(env); - } - - @Override - public int getContentLength() { - return jakartaRequest.getContentLength(); - } - - @Override - public String getContentType() { - return jakartaRequest.getContentType(); - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return new JakartaServletInputStream(jakartaRequest.getInputStream()); - } - - @Override - public String getParameter(final String name) { - return jakartaRequest.getParameter(name); - } - - @Override - public Enumeration getParameterNames() { - return jakartaRequest.getParameterNames(); - } - - @Override - public String[] getParameterValues(final String name) { - return jakartaRequest.getParameterValues(name); - } - - @Override - public Map getParameterMap() { - return jakartaRequest.getParameterMap(); - } - - @Override - public String getProtocol() { - return jakartaRequest.getProtocol(); - } - - @Override - public String getScheme() { - return jakartaRequest.getScheme(); - } - - @Override - public String getServerName() { - return jakartaRequest.getServerName(); - } - - @Override - public int getServerPort() { - return getServerPort(); - } - - @Override - public BufferedReader getReader() throws IOException { - return jakartaRequest.getReader(); - } - - @Override - public String getRemoteAddr() { - return jakartaRequest.getRemoteAddr(); - } - - @Override - public String getRemoteHost() { - return jakartaRequest.getRemoteHost(); - } - - @Override - public void setAttribute(final String name, final Object o) { - jakartaRequest.setAttribute(name, o); - } - - @Override - public void removeAttribute(final String name) { - jakartaRequest.removeAttribute(name); - } - - @Override - public Locale getLocale() { - return jakartaRequest.getLocale(); - } - - @Override - public Enumeration getLocales() { - return jakartaRequest.getLocales(); - } - - @Override - public boolean isSecure() { - return jakartaRequest.isSecure(); - } - - @Override - public RequestDispatcher getRequestDispatcher(final String path) { - throw new IllegalAccessError(); - } - - @Override - public String getRealPath(final String path) { - throw new IllegalAccessError(); - } - - @Override - public int getRemotePort() { - return 0; - } - - @Override - public String getLocalName() { - return jakartaRequest.getLocalName(); - } - - @Override - public String getLocalAddr() { - - return jakartaRequest.getLocalAddr(); - } - - @Override - public int getLocalPort() { - return 0; - } - - @Override - public ServletContext getServletContext() { - throw new IllegalAccessError(); - } - - @Override - public AsyncContext startAsync() throws IllegalStateException { - throw new IllegalAccessError(); - } - - @Override - public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse) - throws IllegalStateException { - throw new IllegalAccessError(); - } - - @Override - public boolean isAsyncStarted() { - return jakartaRequest.isAsyncStarted(); - } - - @Override - public boolean isAsyncSupported() { - return jakartaRequest.isAsyncSupported(); - } - - @Override - public AsyncContext getAsyncContext() { - throw new IllegalAccessError(); - } - - @Override - public DispatcherType getDispatcherType() { - throw new IllegalAccessError(); - } - - @Override - public String getAuthType() { - return jakartaRequest.getAuthType(); - } - - @Override - public Cookie[] getCookies() { - final Cookie[] cookies = new Cookie[jakartaRequest.getCookies().length]; - for (int i = 0; i < jakartaRequest.getCookies().length; i++) - cookies[i] = new Cookie(jakartaRequest.getCookies()[i].getName(), jakartaRequest.getCookies()[i].getValue()); - return cookies; - } - - @Override - public long getDateHeader(final String name) { - return jakartaRequest.getDateHeader(name); - } - - @Override - public String getHeader(final String name) { - return jakartaRequest.getHeader(name); - } - - @Override - public Enumeration getHeaders(final String name) { - return jakartaRequest.getHeaders(name); - } - - @Override - public Enumeration getHeaderNames() { - return jakartaRequest.getHeaderNames(); - } - - @Override - public int getIntHeader(final String name) { - return jakartaRequest.getIntHeader(name); - } - - @Override - public String getMethod() { - return jakartaRequest.getMethod(); - } - - @Override - public String getPathInfo() { - return jakartaRequest.getPathInfo(); - } - - @Override - public String getPathTranslated() { - return jakartaRequest.getPathTranslated(); - } - - @Override - public String getContextPath() { - return jakartaRequest.getContextPath(); - } - - @Override - public String getQueryString() { - return jakartaRequest.getQueryString(); - } - - @Override - public String getRemoteUser() { - return jakartaRequest.getRemoteUser(); - } - - @Override - public boolean isUserInRole(final String role) { - return jakartaRequest.isUserInRole(role); - } - - @Override - public Principal getUserPrincipal() { - return jakartaRequest.getUserPrincipal(); - } - - @Override - public String getRequestedSessionId() { - return jakartaRequest.getRequestedSessionId(); - } - - @Override - public String getRequestURI() { - return jakartaRequest.getRequestURI(); - } - - @Override - public StringBuffer getRequestURL() { - return jakartaRequest.getRequestURL(); - } - - @Override - public String getServletPath() { - return jakartaRequest.getServletPath(); - } - - @Override - public HttpSession getSession(final boolean create) { - throw new IllegalAccessError(); - } - - @Override - public HttpSession getSession() { - throw new IllegalAccessError(); - } - - @Override - public boolean isRequestedSessionIdValid() { - return jakartaRequest.isRequestedSessionIdValid(); - } - - @Override - public boolean isRequestedSessionIdFromCookie() { - return jakartaRequest.isRequestedSessionIdFromCookie(); - } - - @Override - public boolean isRequestedSessionIdFromURL() { - return jakartaRequest.isRequestedSessionIdFromURL(); - } - - @Override - public boolean isRequestedSessionIdFromUrl() { - return jakartaRequest.isRequestedSessionIdFromURL(); - } - - @Override - public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException { - - return false; - } - - @Override - public void login(final String userName, final String password) throws ServletException { - try { - jakartaRequest.login(userName, password); - } catch (final jakarta.servlet.ServletException e) { - throw new ServletException(e); - } - } - - @Override - public void logout() throws ServletException { - try { - jakartaRequest.logout(); - } catch (final jakarta.servlet.ServletException e) { - throw new ServletException(e); - } - } - - @Override - public Collection getParts() throws IOException, ServletException { - throw new IllegalAccessError(); - } - - @Override - public Part getPart(final String name) throws IOException, ServletException { - throw new IllegalAccessError(); - } - -} diff --git a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaResponseMapper.java b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaResponseMapper.java deleted file mode 100644 index 24f855ce2..000000000 --- a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaResponseMapper.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.sap.olingo.jpa.processor.test.util; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collection; -import java.util.Locale; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - -public class JakartaResponseMapper implements HttpServletResponse { - - private final jakarta.servlet.http.HttpServletResponse jakartaResponse; - - public JakartaResponseMapper(final jakarta.servlet.http.HttpServletResponse response) { - this.jakartaResponse = response; - } - - @Override - public String getCharacterEncoding() { - return jakartaResponse.getCharacterEncoding(); - } - - @Override - public String getContentType() { - return jakartaResponse.getContentType(); - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - return new JakartaServletOutputStream(jakartaResponse.getOutputStream()); - } - - @Override - public PrintWriter getWriter() throws IOException { - return jakartaResponse.getWriter(); - } - - @Override - public void setCharacterEncoding(final String charset) { - jakartaResponse.setCharacterEncoding(charset); - } - - @Override - public void setContentLength(final int len) { - jakartaResponse.setContentLength(len); - } - - @Override - public void setContentType(final String type) { - jakartaResponse.setContentType(type); - } - - @Override - public void setBufferSize(final int size) { - jakartaResponse.setBufferSize(size); - } - - @Override - public int getBufferSize() { - return jakartaResponse.getBufferSize(); - } - - @Override - public void flushBuffer() throws IOException { - jakartaResponse.flushBuffer(); - } - - @Override - public void resetBuffer() { - jakartaResponse.resetBuffer(); - } - - @Override - public boolean isCommitted() { - return jakartaResponse.isCommitted(); - } - - @Override - public void reset() { - jakartaResponse.reset(); - } - - @Override - public void setLocale(final Locale loc) { - jakartaResponse.setLocale(loc); - } - - @Override - public Locale getLocale() { - return jakartaResponse.getLocale(); - } - - @Override - public void addCookie(final Cookie cookie) { - jakartaResponse.addCookie(new jakarta.servlet.http.Cookie(cookie.getName(), cookie.getValue())); - } - - @Override - public boolean containsHeader(final String name) { - return jakartaResponse.containsHeader(name); - } - - @Override - public String encodeURL(final String url) { - return jakartaResponse.encodeURL(url); - } - - @Override - public String encodeRedirectURL(final String url) { - return jakartaResponse.encodeRedirectURL(url); - } - - @Override - public String encodeUrl(final String url) { - return jakartaResponse.encodeURL(url); - } - - @Override - public String encodeRedirectUrl(final String url) { - return jakartaResponse.encodeRedirectURL(url); - } - - @Override - public void sendError(final int sc, final String msg) throws IOException { - jakartaResponse.sendError(sc, msg); - } - - @Override - public void sendError(final int sc) throws IOException { - jakartaResponse.sendError(sc); - } - - @Override - public void sendRedirect(final String location) throws IOException { - jakartaResponse.sendRedirect(location); - } - - @Override - public void setDateHeader(final String name, final long date) { - jakartaResponse.setDateHeader(name, date); - } - - @Override - public void addDateHeader(final String name, final long date) { - jakartaResponse.addDateHeader(name, date); - } - - @Override - public void setHeader(final String name, final String value) { - jakartaResponse.setHeader(name, value); - } - - @Override - public void addHeader(final String name, final String value) { - jakartaResponse.addHeader(name, value); - } - - @Override - public void setIntHeader(final String name, final int value) { - jakartaResponse.setIntHeader(name, value); - } - - @Override - public void addIntHeader(final String name, final int value) { - jakartaResponse.addIntHeader(name, value); - } - - @Override - public void setStatus(final int sc) { - jakartaResponse.setStatus(sc); - } - - @Override - public void setStatus(final int sc, final String sm) { - jakartaResponse.setBufferSize(sc); - } - - @Override - public int getStatus() { - return getStatus(); - } - - @Override - public String getHeader(final String name) { - return jakartaResponse.getHeader(name); - } - - @Override - public Collection getHeaders(final String name) { - return jakartaResponse.getHeaders(name); - } - - @Override - public Collection getHeaderNames() { - return jakartaResponse.getHeaderNames(); - } - -} diff --git a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletInputStream.java b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletInputStream.java deleted file mode 100644 index 2ca9f95e9..000000000 --- a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletInputStream.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.olingo.jpa.processor.test.util; - -import java.io.IOException; - -import javax.servlet.ServletInputStream; - -public class JakartaServletInputStream extends ServletInputStream { - private final jakarta.servlet.ServletInputStream inputStream; - - public JakartaServletInputStream(final jakarta.servlet.ServletInputStream inputStream) { - this.inputStream = inputStream; - } - - @Override - public int read() throws IOException { - return inputStream.read(); - } - - @Override - public int readLine(final byte[] b, final int off, final int len) throws IOException { - return inputStream.readLine(b, off, len); - } - -} diff --git a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletOutputStream.java b/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletOutputStream.java deleted file mode 100644 index 97980dae7..000000000 --- a/jpa/odata-jpa-processor-parallel/src/test/java/com/sap/olingo/jpa/processor/test/util/JakartaServletOutputStream.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.olingo.jpa.processor.test.util; - -import java.io.IOException; - -import javax.servlet.ServletOutputStream; - -public class JakartaServletOutputStream extends ServletOutputStream { - - private final jakarta.servlet.ServletOutputStream jakartaOutputStream; - - public JakartaServletOutputStream(final jakarta.servlet.ServletOutputStream outputStream) { - this.jakartaOutputStream = outputStream; - } - - @Override - public void write(final int b) throws IOException { - jakartaOutputStream.write(b); - } - - @Override - public void print(final String s) throws IOException { - jakartaOutputStream.print(s); - } -} diff --git a/jpa/odata-jpa-processor/.project b/jpa/odata-jpa-processor/.project index db102a681..644577220 100644 --- a/jpa/odata-jpa-processor/.project +++ b/jpa/odata-jpa-processor/.project @@ -10,26 +10,6 @@ - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.wst.validation.validationbuilder - - - - - org.whitesource.eclipse.plugin.WSbuilder - - - - - org.whitesource.eclipse.plugin.WSbuilder - - - org.eclipse.m2e.core.maven2Builder @@ -37,6 +17,7 @@ + org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature org.eclipse.wst.common.project.facet.core.nature diff --git a/jpa/odata-jpa-processor/.settings/org.eclipse.jdt.core.prefs b/jpa/odata-jpa-processor/.settings/org.eclipse.jdt.core.prefs index 741734fcb..94ac14d17 100644 --- a/jpa/odata-jpa-processor/.settings/org.eclipse.jdt.core.prefs +++ b/jpa/odata-jpa-processor/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,12 @@ eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled org.eclipse.jdt.core.compiler.source=17 diff --git a/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml b/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml index a28c2b88f..d55740c7d 100644 --- a/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -1,3 +1,5 @@ + + diff --git a/jpa/odata-jpa-processor/pom.xml b/jpa/odata-jpa-processor/pom.xml index d2de8d6c6..b4271bbe6 100644 --- a/jpa/odata-jpa-processor/pom.xml +++ b/jpa/odata-jpa-processor/pom.xml @@ -5,9 +5,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - com.sap.olingo - odata-jpa - 2.1.0-SNAPSHOT + com.sap.olingo + odata-jpa + 2.1.0-SNAPSHOT odata-jpa-processor @@ -45,10 +45,6 @@ jakarta.servlet jakarta.servlet-api - - javax.servlet - javax.servlet-api - jakarta.transaction jakarta.transaction-api @@ -76,7 +72,7 @@ com.sap.olingo odata-jpa-processor-cb - test + test diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProvider.java new file mode 100644 index 000000000..33a170ad4 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProvider.java @@ -0,0 +1,33 @@ +package com.sap.olingo.jpa.processor.core.api; + +import java.util.Optional; + +import jakarta.persistence.EntityManager; + +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriInfo; + +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; +import com.sap.olingo.jpa.processor.core.query.JPACountQuery; + +class JPADefaultPagingProvider implements JPAODataPagingProvider { + + @Override + public Optional getNextPage(final String skipToken, final OData odata, + final ServiceMetadata serviceMetadata, final JPARequestParameterMap requestParameter, final EntityManager em) { + return Optional.empty(); + } + + @Override + public Optional getFirstPage(final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, final UriInfo uriInfo, final Integer preferredPageSize, + final JPACountQuery countQuery, final EntityManager em) throws ODataApplicationException { + + final var skipValue = uriInfo.getSkipOption() != null ? uriInfo.getSkipOption().getValue() : 0; + final var topValue = uriInfo.getTopOption() != null ? uriInfo.getTopOption().getValue() : Integer.MAX_VALUE; + return Optional.of(new JPAODataPage(uriInfo, skipValue, topValue, null)); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPagingProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPagingProvider.java index b6ba58dc0..54e3da397 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPagingProvider.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPagingProvider.java @@ -1,33 +1,87 @@ package com.sap.olingo.jpa.processor.core.api; +import java.util.Optional; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import jakarta.persistence.EntityManager; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.uri.UriInfo; +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; import com.sap.olingo.jpa.processor.core.query.JPACountQuery; /** - * + * Supporting + * + * Server Drive Paging (Part1 11.2.5.7) * @author Oliver Grande * */ public interface JPAODataPagingProvider { /** * Returns the page related to a given skiptoken. - * If the skiptoken is not known the method returns null. - * @param skiptoken + * If the skiptoken is not known the method must return null. + * @param skipToken + * @return Next page. If the page is null, an exception + */ + @Deprecated(since = "2.1.0", forRemoval = true) + default JPAODataPage getNextPage(@Nonnull final String skipToken) { + return getNextPage(skipToken, null, null, null, null).orElse(null); + } + + /** + * Returns the page related to a given skiptoken. + * If the skiptoken is not known the method must return an empty optional. + * @param skipToken + * @param odata + * @param serviceMetadata + * @param requestParameter * @return */ - JPAODataPage getNextPage(final String skiptoken); + default Optional getNextPage(@Nonnull final String skipToken, final OData odata, + final ServiceMetadata serviceMetadata, final JPARequestParameterMap requestParameter, final EntityManager em) { + return Optional.ofNullable(getNextPage(skipToken)); // NOSONAR + } /** * Based on the query the provider decides if a paging is required and return the first page. * @param uriInfo + * @param preferredPageSize Value from odata.maxpagesize preference header + * @param countQuery A query that can be used to determine the maximum number of results that can be expected. Only if + * the number of expected results is bigger then the page size a next link + * @param em * @return * @throws ODataApplicationException */ - JPAODataPage getFirstPage(final UriInfo uriInfo, final Integer preferredPageSize, final JPACountQuery countQuery, - final EntityManager em) throws ODataApplicationException; + @Deprecated(since = "2.1.0", forRemoval = true) + default JPAODataPage getFirstPage(final UriInfo uriInfo, @Nullable final Integer preferredPageSize, + final JPACountQuery countQuery, final EntityManager em) throws ODataApplicationException { + return getFirstPage(null, null, uriInfo, preferredPageSize, countQuery, em).orElse(null); + } + + /** + * Based on the query the provider decides if a paging is required and return the first page. + * @param requestParameter The parameter from the request context + * @param pathInformation Request URI split info different segments like it is expected by the Olingo URI parser + * @param uriInfo + * @param preferredPageSize Value of the odata.maxpagesize preference header + * @param countQuery A query that can be used to determine the maximum number of results that can be + * expected. Only if the number of expected results is bigger then the page size a next link + * @param em An instenace of the entity manager + * @return An optional of the page that shall be read. In case the optional is empty, all records are read from the + * database. + * @throws ODataApplicationException + */ + default Optional getFirstPage(final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, final UriInfo uriInfo, @Nullable final Integer preferredPageSize, + final JPACountQuery countQuery, final EntityManager em) throws ODataApplicationException { + return Optional.ofNullable(getFirstPage(uriInfo, preferredPageSize, countQuery, em));// NOSONAR + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPathInformation.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPathInformation.java new file mode 100644 index 000000000..4d3e51032 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataPathInformation.java @@ -0,0 +1,16 @@ +package com.sap.olingo.jpa.processor.core.api; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +import org.apache.olingo.server.api.ODataRequest; + +public record JPAODataPathInformation(String baseUri, String oDataPath, @Nullable String queryPath, + @Nullable String fragments) implements Serializable { + + public JPAODataPathInformation(final ODataRequest request) { + this(request.getRawBaseUri(), request.getRawODataPath(), request.getRawQueryPath(), null); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectives.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectives.java new file mode 100644 index 000000000..6b66f0c6e --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectives.java @@ -0,0 +1,42 @@ +package com.sap.olingo.jpa.processor.core.api; + +import com.sap.olingo.jpa.processor.core.api.JPAODataServiceContext.Builder; + +public sealed interface JPAODataQueryDirectives { + + public static JPAODataQueryDirectivesBuilder with(final Builder builder) { + return new JPAODataQueryDirectivesBuilderImpl(builder); + } + + int getMaxValuesInInClause(); + + static record JPAODataQueryDirectivesImpl(int maxValuesInInClause) implements JPAODataQueryDirectives { + + @Override + public int getMaxValuesInInClause() { + return maxValuesInInClause; + } + + } + + static class JPAODataQueryDirectivesBuilderImpl implements JPAODataQueryDirectivesBuilder { + + private final Builder parent; + private int maxValuesInInClause = 0; + + JPAODataQueryDirectivesBuilderImpl(final Builder builder) { + this.parent = builder; + } + + @Override + public JPAODataQueryDirectivesBuilder maxValuesInInClause(final int maxValues) { + this.maxValuesInInClause = maxValues; + return this; + } + + @Override + public JPAODataServiceContextBuilder build() { + return parent.setQueryDirectives(new JPAODataQueryDirectivesImpl(maxValuesInInClause)); + } + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesBuilder.java new file mode 100644 index 000000000..674e3496a --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesBuilder.java @@ -0,0 +1,9 @@ +package com.sap.olingo.jpa.processor.core.api; + +public interface JPAODataQueryDirectivesBuilder { + + JPAODataQueryDirectivesBuilder maxValuesInInClause(int i); + + JPAODataServiceContextBuilder build(); + +} \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java index 81a495cd4..8374a6b93 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestContextAccess.java @@ -69,4 +69,6 @@ public Optional getQueryEnhancement(@Nonnull final JP * @return list of locale provided for this request */ public List getProvidedLocale(); + + JPAODataQueryDirectives getQueryDirectives(); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java index 7db6b456d..b84f0e336 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandler.java @@ -13,8 +13,6 @@ import org.apache.olingo.server.api.ODataHttpHandler; import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaRequestMapper; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaResponseMapper; import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; public class JPAODataRequestHandler { @@ -86,16 +84,16 @@ private void processInternal(final HttpServletRequest request, final HttpServlet handler.register(serviceContext.getEdmProvider().getServiceDocument()); handler.register(serviceContext.getErrorProcessor()); handler.register(new JPAODataServiceDocumentProcessor(serviceContext)); - handler.process(new JakartaRequestMapper(mappedRequest), new JakartaResponseMapper(response)); + handler.process(mappedRequest, response); } - private HttpServletRequest prepareRequestMapping(final HttpServletRequest req, final String requestPath) { + private HttpServletRequest prepareRequestMapping(final HttpServletRequest request, final String requestPath) { if (requestPath != null && !requestPath.isEmpty()) { - final HttpServletRequestWrapper request = new HttpServletRequestWrapper(req); - request.setAttribute(REQUEST_MAPPING_ATTRIBUTE, requestPath); - return request; + final HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request); + wrappedRequest.setAttribute(REQUEST_MAPPING_ATTRIBUTE, requestPath); + return wrappedRequest; } else { - return req; + return request; } } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestProcessor.java index 147b206ab..568042373 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestProcessor.java @@ -174,7 +174,7 @@ public void readComplex(final ODataRequest request, final ODataResponse response try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -190,7 +190,7 @@ public void readComplexCollection(final ODataRequest request, final ODataRespons final ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -207,7 +207,7 @@ public void readEntity(final ODataRequest request, final ODataResponse response, try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -224,7 +224,7 @@ public void readEntityCollection(final ODataRequest request, final ODataResponse try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -240,7 +240,7 @@ public void readPrimitiveCollection(final ODataRequest request, final ODataRespo final ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -257,7 +257,7 @@ public void readPrimitive(final ODataRequest request, final ODataResponse respon try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -274,7 +274,7 @@ public void readPrimitiveValue(final ODataRequest request, final ODataResponse r try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -292,7 +292,7 @@ public void readMediaEntity(final ODataRequest request, final ODataResponse resp try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, responseFormat, request.getAllHeaders(), - requestContext); + requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, responseFormat); } catch (ODataApplicationException | ODataLibraryException e) { requestContext.getDebugger().debug(this, e.getMessage()); @@ -439,7 +439,7 @@ private void performCountRequest(final ODataRequest request, final ODataResponse throws ODataApplicationException { try { final JPARequestProcessor processor = factory.createProcessor(uriInfo, ContentType.TEXT_PLAIN, request - .getAllHeaders(), requestContext); + .getAllHeaders(), requestContext, new JPAODataPathInformation(request)); processor.retrieveData(request, response, ContentType.TEXT_PLAIN); } catch (final ODataApplicationException e) { throw e; diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java index 41ce4577f..2b40ab4ed 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java @@ -29,6 +29,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.core.api.JPAODataQueryDirectives.JPAODataQueryDirectivesImpl; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseProcessorFactory; @@ -53,8 +54,9 @@ public final class JPAODataServiceContext implements JPAODataSessionContextAcces private final JPAODataBatchProcessorFactory batchProcessorFactory; private final boolean useAbsoluteContextURL; private final List annotationProvider; + private final JPAODataQueryDirectives queryDirectives; - public static Builder with() { + public static JPAODataServiceContextBuilder with() { return new Builder(); } @@ -75,6 +77,7 @@ private JPAODataServiceContext(final Builder builder) { batchProcessorFactory = (JPAODataBatchProcessorFactory) builder.batchProcessorFactory; useAbsoluteContextURL = builder.useAbsoluteContextURL; annotationProvider = Arrays.asList(builder.annotationProvider); + queryDirectives = builder.queryDirectives; } @Override @@ -145,7 +148,12 @@ public List getAnnotationProvider() { return annotationProvider; } - public static class Builder { + @Override + public JPAODataQueryDirectives getQueryDirectives() { + return queryDirectives; + } + + static class Builder implements JPAODataServiceContextBuilder { private String namespace; private List references = new ArrayList<>(); @@ -163,11 +171,13 @@ public static class Builder { private JPAODataBatchProcessorFactory batchProcessorFactory; private boolean useAbsoluteContextURL = false; private AnnotationProvider[] annotationProvider; + private JPAODataQueryDirectivesImpl queryDirectives; private Builder() { super(); } + @Override public JPAODataSessionContextAccess build() throws ODataException { try { if (nameBuilder == null) { @@ -194,6 +204,10 @@ public JPAODataSessionContextAccess build() throws ODataException { LOGGER.trace("No batch-processor-factory provided, use default factory to create one"); batchProcessorFactory = new JPADefaultBatchProcessorFactory(); } + if (pagingProvider == null) + pagingProvider = new JPADefaultPagingProvider(); + if (queryDirectives == null) + useQueryDirectives().build(); } catch (SQLException | PersistenceException e) { throw new ODataJPAFilterException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } @@ -208,7 +222,8 @@ public JPAODataSessionContextAccess build() throws ODataException { * @param databaseProcessor * @return */ - public Builder setDatabaseProcessor(final JPAODataDatabaseProcessor databaseProcessor) { + @Override + public JPAODataServiceContextBuilder setDatabaseProcessor(final JPAODataDatabaseProcessor databaseProcessor) { this.databaseProcessor = databaseProcessor; return this; } @@ -221,7 +236,8 @@ public Builder setDatabaseProcessor(final JPAODataDatabaseProcessor databaseProc * @param ds * @return */ - public Builder setDataSource(final DataSource ds) { + @Override + public JPAODataServiceContextBuilder setDataSource(final DataSource ds) { this.ds = ds; return this; } @@ -238,7 +254,8 @@ public Builder setDataSource(final DataSource ds) { * Error Response. * @param errorProcessor */ - public Builder setErrorProcessor(final ErrorProcessor errorProcessor) { + @Override + public JPAODataServiceContextBuilder setErrorProcessor(final ErrorProcessor errorProcessor) { this.errorProcessor = errorProcessor; return this; } @@ -248,7 +265,8 @@ public Builder setErrorProcessor(final ErrorProcessor errorProcessor) { * @param postProcessor * @return */ - public Builder setMetadataPostProcessor(final JPAEdmMetadataPostProcessor postProcessor) { + @Override + public JPAODataServiceContextBuilder setMetadataPostProcessor(final JPAEdmMetadataPostProcessor postProcessor) { this.postProcessor = postProcessor; return this; } @@ -258,7 +276,8 @@ public Builder setMetadataPostProcessor(final JPAEdmMetadataPostProcessor postPr * @param jpaOperationConverter * @return */ - public Builder setOperationConverter(final JPAODataDatabaseOperations jpaOperationConverter) { + @Override + public JPAODataServiceContextBuilder setOperationConverter(final JPAODataDatabaseOperations jpaOperationConverter) { this.operationConverter = jpaOperationConverter; return this; } @@ -268,7 +287,8 @@ public Builder setOperationConverter(final JPAODataDatabaseOperations jpaOperati * the requested results as well as a $skiptoken. * @param provider */ - public Builder setPagingProvider(final JPAODataPagingProvider provider) { + @Override + public JPAODataServiceContextBuilder setPagingProvider(final JPAODataPagingProvider provider) { this.pagingProvider = provider; return this; } @@ -280,7 +300,8 @@ public Builder setPagingProvider(final JPAODataPagingProvider provider) { * @param pUnit * @return */ - public Builder setPUnit(final String pUnit) { + @Override + public JPAODataServiceContextBuilder setPUnit(final String pUnit) { this.namespace = pUnit; return this; } @@ -290,7 +311,8 @@ public Builder setPUnit(final String pUnit) { * @param references * @return */ - public Builder setReferences(final List references) { + @Override + public JPAODataServiceContextBuilder setReferences(final List references) { this.references = references; return this; } @@ -303,12 +325,14 @@ public Builder setReferences(final List references) { * * @param packageName */ - public Builder setTypePackage(final String... packageName) { + @Override + public JPAODataServiceContextBuilder setTypePackage(final String... packageName) { this.packageName = packageName; return this; } - public Builder setRequestMappingPath(final String mappingPath) { + @Override + public JPAODataServiceContextBuilder setRequestMappingPath(final String mappingPath) { this.mappingPath = mappingPath; return this; } @@ -319,7 +343,8 @@ public Builder setRequestMappingPath(final String mappingPath) { * @param emf * @return */ - public Builder setEntityManagerFactory(final EntityManagerFactory emf) { + @Override + public JPAODataServiceContextBuilder setEntityManagerFactory(final EntityManagerFactory emf) { this.emf = Optional.of(emf); return this; } @@ -331,12 +356,14 @@ public Builder setEntityManagerFactory(final EntityManagerFactory emf) { * @param nameBuilder * @return */ - public Builder setEdmNameBuilder(final JPAEdmNameBuilder nameBuilder) { + @Override + public JPAODataServiceContextBuilder setEdmNameBuilder(final JPAEdmNameBuilder nameBuilder) { this.nameBuilder = nameBuilder; return this; } - public Builder setBatchProcessorFactory( + @Override + public JPAODataServiceContextBuilder setBatchProcessorFactory( final JPAODataBatchProcessorFactory batchProcessorFactory) { this.batchProcessorFactory = batchProcessorFactory; return this; @@ -349,12 +376,14 @@ public Builder setBatchProcessorFactory( * @param useAbsoluteContextURL * @return */ - public Builder setUseAbsoluteContextURL(final boolean useAbsoluteContextURL) { + @Override + public JPAODataServiceContextBuilder setUseAbsoluteContextURL(final boolean useAbsoluteContextURL) { this.useAbsoluteContextURL = useAbsoluteContextURL; return this; } - public Builder setAnnotationProvider(final AnnotationProvider... annotationProvider) { + @Override + public JPAODataServiceContextBuilder setAnnotationProvider(final AnnotationProvider... annotationProvider) { this.annotationProvider = annotationProvider; return this; } @@ -382,5 +411,16 @@ private void createEmfWrapper() { } } } + + @Override + public JPAODataQueryDirectivesBuilder useQueryDirectives() { + return JPAODataQueryDirectives.with(this); + } + + JPAODataServiceContextBuilder setQueryDirectives(final JPAODataQueryDirectivesImpl queryDirectives) { + this.queryDirectives = queryDirectives; + return this; + } } + } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java new file mode 100644 index 000000000..5a1b602ab --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java @@ -0,0 +1,145 @@ +package com.sap.olingo.jpa.processor.core.api; + +import java.util.List; + +import javax.sql.DataSource; + +import jakarta.persistence.EntityManagerFactory; + +import org.apache.olingo.commons.api.edmx.EdmxReference; +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.processor.ErrorProcessor; + +import com.sap.olingo.jpa.metadata.api.JPAEdmMetadataPostProcessor; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; +import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.core.api.JPAODataServiceContext.Builder; +import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; +import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; + +public interface JPAODataServiceContextBuilder { + + JPAODataSessionContextAccess build() throws ODataException; + + /** + * A database processor allows database specific implementations for search and odata function with function import + * that are implemented as database functions.
+ * In case no database processor is provided and non could be determined via an data source + * {@link JPADefaultDatabaseProcessor} is used. + * @param databaseProcessor + * @return + */ + JPAODataServiceContextBuilder setDatabaseProcessor(JPAODataDatabaseProcessor databaseProcessor); + + /** + * The data source is used to create an entity manager factory if not provided, see + * {@link Builder#setEntityManagerFactory(EntityManagerFactory)}, and to determine the type of + * database used to select an integrated database processor, in case the database processor was not set via + * {@link Builder#setDatabaseProcessor(JPAODataDatabaseProcessor)}}. + * @param ds + * @return + */ + JPAODataServiceContextBuilder setDataSource(DataSource ds); + + /** + * Allows to provide an Olingo error processor. The error processor allows to enrich an error response. See + * JSON Error Response or + * Atom + * Error Response. + * @param errorProcessor + */ + JPAODataServiceContextBuilder setErrorProcessor(ErrorProcessor errorProcessor); + + /** + * + * @param postProcessor + * @return + */ + JPAODataServiceContextBuilder setMetadataPostProcessor(JPAEdmMetadataPostProcessor postProcessor); + + /** + * + * @param jpaOperationConverter + * @return + */ + JPAODataServiceContextBuilder setOperationConverter(JPAODataDatabaseOperations jpaOperationConverter); + + /** + * Register a provider that is able to decides based on a given query if the server like to return only a sub set of + * the requested results as well as a $skiptoken. + * @param provider + */ + JPAODataServiceContextBuilder setPagingProvider(JPAODataPagingProvider provider); + + /** + * The name of the persistence-unit to be used. It is taken to create a entity manager factory + * ({@link Builder#setEntityManagerFactory(EntityManagerFactory)}), if not provided and + * as namespace of the OData service, in case the default name builder shall be used. + * @param pUnit + * @return + */ + JPAODataServiceContextBuilder setPUnit(String pUnit); + + /** + * + * @param references + * @return + */ + JPAODataServiceContextBuilder setReferences(List references); + + /** + * Name of the top level package to look for + *
    + *
  • Enumeration Types + *
  • Java class based Functions + *
+ * @param packageName + */ + JPAODataServiceContextBuilder setTypePackage(String... packageName); + + JPAODataServiceContextBuilder setRequestMappingPath(String mappingPath); + + /** + * Set an externally created entity manager factory.
+ * This is necessary e.g. in case a spring based service shall run without a persistance.xml. + * @param emf + * @return + */ + JPAODataServiceContextBuilder setEntityManagerFactory(EntityManagerFactory emf); + + /** + * Set a custom EDM name builder {@link JPAEdmNameBuilder}. If non is provided {@link JPADefaultEdmNameBuilder} is + * used, which uses the provided persistence-unit name ({@link JPAODataServiceContext.Builder#setPUnit}) as + * namespace. + * @param nameBuilder + * @return + */ + JPAODataServiceContextBuilder setEdmNameBuilder(JPAEdmNameBuilder nameBuilder); + + JPAODataServiceContextBuilder setBatchProcessorFactory( + JPAODataBatchProcessorFactory batchProcessorFactory); + + /** + * Some clients, like Excel, require context url's with an absolute path. The default generation of relative paths + * can be overruled.
+ * @see Issue OLINGO-787 + * @param useAbsoluteContextURL + * @return + */ + JPAODataServiceContextBuilder setUseAbsoluteContextURL(boolean useAbsoluteContextURL); + + JPAODataServiceContextBuilder setAnnotationProvider(AnnotationProvider... annotationProvider); + + /** + * + * @return + */ + JPAODataQueryDirectivesBuilder useQueryDirectives(); + +} \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java index e383b14c2..eaa73ff2d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java @@ -65,4 +65,6 @@ public default boolean useAbsoluteContextURL() { } public List getAnnotationProvider(); + + public JPAODataQueryDirectives getQueryDirectives(); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java index 295c4eb9c..28b791cc0 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataTransactionFactory.java @@ -7,6 +7,8 @@ /** * A wrapper to abstract from various transaction APIs provided by JAVA or e.g. Spring like * jakarta.persistence.EntityTransaction, javax.transaction.UserTransaction, javax.transaction.Transaction or + * A wrapper to abstract from various transaction APIs provided by JAVA or e.g. Spring like + * javax.persistence.EntityTransaction, javax.transaction.UserTransaction, javax.transaction.Transaction or * org.springframework.transaction.jta.JtaTransactionManager. *

* diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAServiceDebugger.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAServiceDebugger.java index dbc34b0f1..0414ec95a 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAServiceDebugger.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAServiceDebugger.java @@ -19,5 +19,7 @@ public default void debug(final Object instance, final String log) {} public static interface JPARuntimeMeasurement extends AutoCloseable { @Override void close(); + + long getMemoryConsumption(); } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java index 093ceec73..3c1ebd87b 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java @@ -1,120 +1,132 @@ package com.sap.olingo.jpa.processor.core.api.example; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import jakarta.persistence.EntityManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.uri.UriInfo; -import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceCount; import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; import com.sap.olingo.jpa.processor.core.api.JPAODataPagingProvider; +import com.sap.olingo.jpa.processor.core.api.JPAODataPathInformation; import com.sap.olingo.jpa.processor.core.query.JPACountQuery; public class JPAExamplePagingProvider implements JPAODataPagingProvider { + private static final Log LOGGER = LogFactory.getLog(JPAExamplePagingProvider.class); + + private static final Object lock = new Object(); private static final int DEFAULT_BUFFER_SIZE = 100; private final Map maxPageSizes; private final Map pageCache; - private final int cacheSize; private final Queue index; + private final int cacheSize; public JPAExamplePagingProvider(final Map pageSizes) { this(pageSizes, DEFAULT_BUFFER_SIZE); } public JPAExamplePagingProvider(final Map pageSizes, final int bufferSize) { - maxPageSizes = pageSizes; + maxPageSizes = Collections.unmodifiableMap(pageSizes); pageCache = new HashMap<>(bufferSize); cacheSize = bufferSize; index = new LinkedList<>(); } @Override - public JPAODataPage getNextPage(final String skipToken) { - final CacheEntry previousPage = pageCache.get(skipToken.replace("'", "")); + public Optional getNextPage(@Nonnull final String skipToken, final OData odata, + final ServiceMetadata serviceMetadata, final JPARequestParameterMap requestParameter, final EntityManager em) { + final var previousPage = pageCache.get(skipToken.replace("'", "")); if (previousPage != null) { // Calculate next page - final Integer skip = previousPage.getPage().skip() + previousPage.getPage().top(); + final Integer skip = previousPage.page().skip() + previousPage.page().top(); // Create a new skip token if next page is not the last one String nextToken = null; - if (skip + previousPage.getPage().top() < previousPage.getMaxTop()) + if (skip + previousPage.page().top() < previousPage.maxTop()) nextToken = UUID.randomUUID().toString(); - final int top = (int) ((skip + previousPage.getPage().top()) < previousPage.getMaxTop() ? previousPage - .getPage().top() : previousPage.getMaxTop() - skip); - final JPAODataPage page = new JPAODataPage(previousPage.getPage().uriInfo(), - skip, top, nextToken); + final var top = (int) ((skip + previousPage.page().top()) < previousPage.maxTop() ? previousPage + .page().top() : previousPage.maxTop() - skip); + final var page = new JPAODataPage(previousPage.page().uriInfo(), skip, top, nextToken); if (nextToken != null) - addToCache(page, previousPage.getMaxTop()); - return page; + addToCache(page, previousPage.maxTop()); + return Optional.of(page); } - // skip token not found => let JPA Processor handle this - return null; + // skip token not found => let JPA Processor handle this by return http.gone + return Optional.empty(); } @Override - public JPAODataPage getFirstPage(final UriInfo uriInfo, final Integer preferredPageSize, + public Optional getFirstPage(final JPARequestParameterMap requestParameter, + final JPAODataPathInformation pathInformation, final UriInfo uriInfo, @Nullable final Integer preferredPageSize, final JPACountQuery countQuery, final EntityManager em) throws ODataApplicationException { - final UriResource root = uriInfo.getUriResourceParts().get(0); + final var resourceParts = uriInfo.getUriResourceParts(); + + final var root = resourceParts.get(0); + final var leaf = resourceParts.get(resourceParts.size() - 1); // Paging will only be done for Entity Sets - if (root instanceof final UriResourceEntitySet entitySet) { + if (root instanceof final UriResourceEntitySet entitySet + && !(leaf instanceof UriResourceCount)) { + // Not last is count!! // Check if Entity Set shall be packaged - final Integer maxSize = maxPageSizes.get(entitySet.getEntitySet().getName()); + final var maxSize = maxPageSizes.get(entitySet.getEntitySet().getName()); if (maxSize != null) { // Read $top and $skip final Integer skipValue = uriInfo.getSkipOption() != null ? uriInfo.getSkipOption().getValue() : 0; - final Integer topValue = uriInfo.getTopOption() != null ? uriInfo.getTopOption().getValue() : null; - // Determine end of list - final Long count = topValue != null ? (topValue + skipValue) : countQuery.countResults(); + final var topValue = uriInfo.getTopOption() != null ? uriInfo.getTopOption().getValue() : null; // Determine page size - final Integer size = preferredPageSize != null && preferredPageSize < maxSize ? preferredPageSize : maxSize; + final var pageSize = preferredPageSize != null && preferredPageSize < maxSize ? preferredPageSize : maxSize; + if (topValue != null && topValue <= pageSize) + return Optional.of(new JPAODataPage(uriInfo, skipValue, topValue, null)); + // Determine end of list + final var maxResults = countQuery.countResults(); + final Long count = topValue != null && (topValue + skipValue) < maxResults + ? topValue.longValue() : maxResults - skipValue; + final Long last = topValue != null && (topValue + skipValue) < maxResults + ? (topValue + skipValue) : maxResults; // Create a unique skip token if needed String skipToken = null; - if (size < count) + if (pageSize < count) skipToken = UUID.randomUUID().toString(); // Create page information - final JPAODataPage page = new JPAODataPage(uriInfo, skipValue, topValue != null && topValue < size ? topValue - : size, skipToken); + final var page = new JPAODataPage(uriInfo, skipValue, pageSize, skipToken); // Cache page to be able to fulfill next link based request if (skipToken != null) - addToCache(page, count); - return page; + addToCache(page, last); + return Optional.of(page); } } - return null; + return Optional.empty(); } private void addToCache(final JPAODataPage page, final Long count) { - if (pageCache.size() == cacheSize) - pageCache.remove(index.poll()); - pageCache.put((String) page.skipToken(), new CacheEntry(count, page)); - index.add((String) page.skipToken()); + synchronized (lock) { + if (pageCache.size() == cacheSize) + pageCache.remove(index.poll()); + LOGGER.info("Cache size: " + pageCache.size()); + pageCache.put((String) page.skipToken(), new CacheEntry(count, page)); + index.add((String) page.skipToken()); + } } - private static class CacheEntry { - private final Long maxTop; - private final JPAODataPage page; - - CacheEntry(final Long count, final JPAODataPage page) { - super(); - this.maxTop = count; - this.page = page; - } + private static record CacheEntry(Long maxTop, JPAODataPage page) {} - public Long getMaxTop() { - return maxTop; - } - - public JPAODataPage getPage() { - return page; - } - } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapper.java deleted file mode 100644 index 0404d73b2..000000000 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapper.java +++ /dev/null @@ -1,395 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.Principal; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; - -import javax.annotation.Nonnull; -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.Part; - -import jakarta.servlet.http.HttpServletRequest; - -public class JakartaRequestMapper implements javax.servlet.http.HttpServletRequest { - - private final HttpServletRequest jakartaRequest; - - public JakartaRequestMapper(@Nonnull final HttpServletRequest jakartaRequest) { - super(); - this.jakartaRequest = Objects.requireNonNull(jakartaRequest); - } - - @Override - public Object getAttribute(final String name) { - return jakartaRequest.getAttribute(name); - } - - @Override - public Enumeration getAttributeNames() { - return jakartaRequest.getAttributeNames(); - } - - @Override - public String getCharacterEncoding() { - return jakartaRequest.getCharacterEncoding(); - } - - @Override - public void setCharacterEncoding(final String env) throws UnsupportedEncodingException { - jakartaRequest.setCharacterEncoding(env); - } - - @Override - public int getContentLength() { - return jakartaRequest.getContentLength(); - } - - @Override - public String getContentType() { - return jakartaRequest.getContentType(); - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return new JakartaServletInputStream(jakartaRequest.getInputStream()); - } - - @Override - public String getParameter(final String name) { - return jakartaRequest.getParameter(name); - } - - @Override - public Enumeration getParameterNames() { - return jakartaRequest.getParameterNames(); - } - - @Override - public String[] getParameterValues(final String name) { - return jakartaRequest.getParameterValues(name); - } - - @Override - public Map getParameterMap() { - return jakartaRequest.getParameterMap(); - } - - @Override - public String getProtocol() { - return jakartaRequest.getProtocol(); - } - - @Override - public String getScheme() { - return jakartaRequest.getScheme(); - } - - @Override - public String getServerName() { - return jakartaRequest.getServerName(); - } - - @Override - public int getServerPort() { - return jakartaRequest.getServerPort(); - } - - @Override - public BufferedReader getReader() throws IOException { - return jakartaRequest.getReader(); - } - - @Override - public String getRemoteAddr() { - return jakartaRequest.getRemoteAddr(); - } - - @Override - public String getRemoteHost() { - return jakartaRequest.getRemoteHost(); - } - - @Override - public void setAttribute(final String name, final Object o) { - jakartaRequest.setAttribute(name, o); - } - - @Override - public void removeAttribute(final String name) { - jakartaRequest.removeAttribute(name); - } - - @Override - public Locale getLocale() { - return jakartaRequest.getLocale(); - } - - @Override - public Enumeration getLocales() { - return jakartaRequest.getLocales(); - } - - @Override - public boolean isSecure() { - return jakartaRequest.isSecure(); - } - - @Override - public RequestDispatcher getRequestDispatcher(final String path) { - throw new IllegalAccessError(); - } - - @Override - public String getRealPath(final String path) { - throw new IllegalAccessError(); - } - - @Override - public int getRemotePort() { - return jakartaRequest.getRemotePort(); - } - - @Override - public String getLocalName() { - return jakartaRequest.getLocalName(); - } - - @Override - public String getLocalAddr() { - - return jakartaRequest.getLocalAddr(); - } - - @Override - public int getLocalPort() { - return jakartaRequest.getLocalPort(); - } - - @Override - public ServletContext getServletContext() { - throw new IllegalAccessError(); - } - - @Override - public AsyncContext startAsync() throws IllegalStateException { - throw new IllegalAccessError(); - } - - @Override - public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse) - throws IllegalStateException { - throw new IllegalAccessError(); - } - - @Override - public boolean isAsyncStarted() { - return jakartaRequest.isAsyncStarted(); - } - - @Override - public boolean isAsyncSupported() { - return jakartaRequest.isAsyncSupported(); - } - - @Override - public AsyncContext getAsyncContext() { - throw new IllegalAccessError(); - } - - @Override - public DispatcherType getDispatcherType() { - throw new IllegalAccessError(); - } - - @Override - public String getAuthType() { - return jakartaRequest.getAuthType(); - } - - @Override - public Cookie[] getCookies() { - final Cookie[] cookies = new Cookie[jakartaRequest.getCookies().length]; - for (int i = 0; i < jakartaRequest.getCookies().length; i++) { - final Cookie cookie = new Cookie(jakartaRequest.getCookies()[i].getName(), jakartaRequest.getCookies()[i] - .getValue()); - cookie.setHttpOnly(jakartaRequest.getCookies()[i].isHttpOnly()); - cookie.setSecure(jakartaRequest.getCookies()[i].getSecure()); - cookie.setComment(jakartaRequest.getCookies()[i].getComment()); - cookie.setDomain(jakartaRequest.getCookies()[i].getDomain()); - cookie.setMaxAge(jakartaRequest.getCookies()[i].getMaxAge()); - cookie.setPath(jakartaRequest.getCookies()[i].getPath()); - cookie.setVersion(jakartaRequest.getCookies()[i].getVersion()); - cookies[i] = cookie; - } - return cookies; - } - - @Override - public long getDateHeader(final String name) { - return jakartaRequest.getDateHeader(name); - } - - @Override - public String getHeader(final String name) { - return jakartaRequest.getHeader(name); - } - - @Override - public Enumeration getHeaders(final String name) { - return jakartaRequest.getHeaders(name); - } - - @Override - public Enumeration getHeaderNames() { - return jakartaRequest.getHeaderNames(); - } - - @Override - public int getIntHeader(final String name) { - return jakartaRequest.getIntHeader(name); - } - - @Override - public String getMethod() { - return jakartaRequest.getMethod(); - } - - @Override - public String getPathInfo() { - return jakartaRequest.getPathInfo(); - } - - @Override - public String getPathTranslated() { - return jakartaRequest.getPathTranslated(); - } - - @Override - public String getContextPath() { - return jakartaRequest.getContextPath(); - } - - @Override - public String getQueryString() { - return jakartaRequest.getQueryString(); - } - - @Override - public String getRemoteUser() { - return jakartaRequest.getRemoteUser(); - } - - @Override - public boolean isUserInRole(final String role) { - return jakartaRequest.isUserInRole(role); - } - - @Override - public Principal getUserPrincipal() { - return jakartaRequest.getUserPrincipal(); - } - - @Override - public String getRequestedSessionId() { - // Fix sonar issue. OData does not care about session handling - return null; - } - - @Override - public String getRequestURI() { - return jakartaRequest.getRequestURI(); - } - - @Override - public StringBuffer getRequestURL() { - return jakartaRequest.getRequestURL(); - } - - @Override - public String getServletPath() { - return jakartaRequest.getServletPath(); - } - - @Override - public HttpSession getSession(final boolean create) { - throw new IllegalAccessError(); - } - - @Override - public HttpSession getSession() { - throw new IllegalAccessError(); - } - - @Override - public boolean isRequestedSessionIdValid() { - return jakartaRequest.isRequestedSessionIdValid(); - } - - @Override - public boolean isRequestedSessionIdFromCookie() { - return jakartaRequest.isRequestedSessionIdFromCookie(); - } - - @Override - public boolean isRequestedSessionIdFromURL() { - return jakartaRequest.isRequestedSessionIdFromURL(); - } - - @Override - public boolean isRequestedSessionIdFromUrl() { - return jakartaRequest.isRequestedSessionIdFromURL(); - } - - @Override - public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException { - return false; - } - - @Override - public void login(final String userName, final String password) throws ServletException { - try { - jakartaRequest.login(userName, password); - } catch (final jakarta.servlet.ServletException e) { - throw new ServletException(e); - } - } - - @Override - public void logout() throws ServletException { - try { - jakartaRequest.logout(); - } catch (final jakarta.servlet.ServletException e) { - throw new ServletException(e); - } - } - - @Override - public Collection getParts() throws IOException, ServletException { - throw new IllegalAccessError(); - } - - @Override - public Part getPart(final String name) throws IOException, ServletException { - throw new IllegalAccessError(); - } - - public HttpServletRequest getWrapped() { - return jakartaRequest; - } -} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapper.java deleted file mode 100644 index 3c9af8dc5..000000000 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapper.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collection; -import java.util.Locale; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - -public class JakartaResponseMapper implements HttpServletResponse { - - private final jakarta.servlet.http.HttpServletResponse jakartaResponse; - - public JakartaResponseMapper(final jakarta.servlet.http.HttpServletResponse response) { - this.jakartaResponse = response; - } - - @Override - public String getCharacterEncoding() { - return jakartaResponse.getCharacterEncoding(); - } - - @Override - public String getContentType() { - return jakartaResponse.getContentType(); - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - return new JakartaServletOutputStream(jakartaResponse.getOutputStream()); - } - - @Override - public PrintWriter getWriter() throws IOException { - return jakartaResponse.getWriter(); - } - - @Override - public void setCharacterEncoding(final String charset) { - jakartaResponse.setCharacterEncoding(charset); - } - - @Override - public void setContentLength(final int len) { - jakartaResponse.setContentLength(len); - } - - @Override - public void setContentType(final String type) { - jakartaResponse.setContentType(type); - } - - @Override - public void setBufferSize(final int size) { - jakartaResponse.setBufferSize(size); - } - - @Override - public int getBufferSize() { - return jakartaResponse.getBufferSize(); - } - - @Override - public void flushBuffer() throws IOException { - jakartaResponse.flushBuffer(); - } - - @Override - public void resetBuffer() { - jakartaResponse.resetBuffer(); - } - - @Override - public boolean isCommitted() { - return jakartaResponse.isCommitted(); - } - - @Override - public void reset() { - jakartaResponse.reset(); - } - - @Override - public void setLocale(final Locale loc) { - jakartaResponse.setLocale(loc); - } - - @Override - public Locale getLocale() { - return jakartaResponse.getLocale(); - } - - @SuppressWarnings("removal") - @Override - public void addCookie(final Cookie cookie) { - final jakarta.servlet.http.Cookie newCookie = - new jakarta.servlet.http.Cookie(cookie.getName(), cookie.getValue()); - - newCookie.setHttpOnly(cookie.isHttpOnly()); - newCookie.setSecure(cookie.getSecure()); - newCookie.setComment(cookie.getComment()); - newCookie.setDomain(cookie.getDomain()); - newCookie.setMaxAge(cookie.getMaxAge()); - newCookie.setPath(cookie.getPath()); - newCookie.setVersion(cookie.getVersion()); - jakartaResponse.addCookie(newCookie); - } - - @Override - public boolean containsHeader(final String name) { - return jakartaResponse.containsHeader(name); - } - - @Override - public String encodeURL(final String url) { - return jakartaResponse.encodeURL(url); - } - - @Override - public String encodeRedirectURL(final String url) { - return jakartaResponse.encodeRedirectURL(url); - } - - @Override - public String encodeUrl(final String url) { - return jakartaResponse.encodeURL(url); - } - - @Override - public String encodeRedirectUrl(final String url) { - return jakartaResponse.encodeRedirectURL(url); - } - - @Override - public void sendError(final int sc, final String msg) throws IOException { - jakartaResponse.sendError(sc, msg); - } - - @Override - public void sendError(final int sc) throws IOException { - jakartaResponse.sendError(sc); - } - - @Override - public void sendRedirect(final String location) throws IOException { - jakartaResponse.sendRedirect(location); - } - - @Override - public void setDateHeader(final String name, final long date) { - jakartaResponse.setDateHeader(name, date); - } - - @Override - public void addDateHeader(final String name, final long date) { - jakartaResponse.addDateHeader(name, date); - } - - @Override - public void setHeader(final String name, final String value) { - jakartaResponse.setHeader(name, value); - } - - @Override - public void addHeader(final String name, final String value) { - jakartaResponse.addHeader(name, value); - } - - @Override - public void setIntHeader(final String name, final int value) { - jakartaResponse.setIntHeader(name, value); - } - - @Override - public void addIntHeader(final String name, final int value) { - jakartaResponse.addIntHeader(name, value); - } - - @Override - public void setStatus(final int status) { - jakartaResponse.setStatus(status); - } - - @Override - public void setStatus(final int status, final String message) { - jakartaResponse.setStatus(status); - } - - @Override - public int getStatus() { - return jakartaResponse.getStatus(); - } - - @Override - public String getHeader(final String name) { - return jakartaResponse.getHeader(name); - } - - @Override - public Collection getHeaders(final String name) { - return jakartaResponse.getHeaders(name); - } - - @Override - public Collection getHeaderNames() { - return jakartaResponse.getHeaderNames(); - } - -} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletInputStream.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletInputStream.java deleted file mode 100644 index ed214afbb..000000000 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletInputStream.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import java.io.IOException; - -import javax.servlet.ServletInputStream; - -public class JakartaServletInputStream extends ServletInputStream { - private final jakarta.servlet.ServletInputStream inputStream; - - public JakartaServletInputStream(final jakarta.servlet.ServletInputStream inputStream) { - this.inputStream = inputStream; - } - - @Override - public int read() throws IOException { - return inputStream.read(); - } - - @Override - public int readLine(final byte[] b, final int off, final int len) throws IOException { - return inputStream.readLine(b, off, len); - } - -} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletOutputStream.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletOutputStream.java deleted file mode 100644 index 169090ca6..000000000 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaServletOutputStream.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import java.io.IOException; - -import javax.servlet.ServletOutputStream; - -public class JakartaServletOutputStream extends ServletOutputStream { - - private final jakarta.servlet.ServletOutputStream jakartaOutputStream; - - public JakartaServletOutputStream(final jakarta.servlet.ServletOutputStream outputStream) { - this.jakartaOutputStream = outputStream; - } - - @Override - public void write(final int b) throws IOException { - jakartaOutputStream.write(b); - } - - @Override - public void print(final String s) throws IOException { - jakartaOutputStream.print(s); - } -} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java index 422253cb1..3e61a4530 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAQueryException.java @@ -36,6 +36,7 @@ public enum MessageKeys implements ODataJPAMessageKey { QUERY_PREPARATION_ORDER_BY_TRANSIENT, QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING, + QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED, NOT_SUPPORTED_RESOURCE_TYPE, MISSING_CLAIMS_PROVIDER, MISSING_CLAIM, diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorImp.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorImp.java index 6a7bf8a9f..1425c3587 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorImp.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorImp.java @@ -77,8 +77,8 @@ public Expression getLeft(final CriteriaBuilder cb) throws ODataApplicat /* * (non-Javadoc) * - * @see com.sap.olingo.jpa.processor.core.filter.JPAArithmeticOperator#getRightAsNumber(jakarta.persistence.criteria. - * CriteriaBuilder) + * @see com.sap.olingo.jpa.processor.core.filter.JPAArithmeticOperator#getRightAsNumber(javax.persistence.criteria. + * CriteriaBuilder) */ @Override public Number getRightAsNumber(final CriteriaBuilder cb) throws ODataApplicationException { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAComparisonOperator.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAComparisonOperator.java index 3003ec42e..587407b62 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAComparisonOperator.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAComparisonOperator.java @@ -3,6 +3,7 @@ import jakarta.persistence.criteria.Expression; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; public interface JPAComparisonOperator> extends JPAExpressionOperator { @@ -14,4 +15,7 @@ public interface JPAComparisonOperator> extends JPAExpre Expression getRightAsExpression() throws ODataApplicationException; + @SuppressWarnings("unchecked") + @Override + BinaryOperatorKind getOperator(); } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterComplier.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterComplier.java index 56e2b70f9..7a6f037b6 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterComplier.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterComplier.java @@ -1,6 +1,7 @@ package com.sap.olingo.jpa.processor.core.filter; import java.util.List; +import java.util.Optional; import jakarta.persistence.criteria.Expression; @@ -19,4 +20,5 @@ public interface JPAFilterComplier { */ List getMember() throws ODataApplicationException; + Optional getWatchDog(); } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDog.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDog.java index 0c8ecd238..ca53ba80a 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDog.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDog.java @@ -47,8 +47,10 @@ public class JPAFilterRestrictionsWatchDog extends AbstractWatchDog { private final Map properties; private final String externalName; private final Set requiredPropertyPath; + private boolean singleEntityRequested; - public JPAFilterRestrictionsWatchDog(final JPAAnnotatable annotatable) throws ODataJPAQueryException { + public JPAFilterRestrictionsWatchDog(final JPAAnnotatable annotatable, final boolean singleEntityRequested) + throws ODataJPAQueryException { try { if (annotatable != null) { externalName = annotatable.getExternalName(); @@ -61,6 +63,7 @@ public JPAFilterRestrictionsWatchDog(final JPAAnnotatable annotatable) throws OD properties = Collections.emptyMap(); requiredPropertyPath = Collections.emptySet(); } + this.singleEntityRequested = singleEntityRequested; } catch (final ODataJPAModelException e) { throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } @@ -101,18 +104,19 @@ private Set extractRequiredPropertyPath(final CsdlExpression expression) private boolean filteringIsAllowed() { final CsdlExpression filterable = properties.get(FILTERABLE); - return filterable != null - && !Boolean.valueOf(filterable.asConstant().getValue()); + return filterable == null + || (Boolean.valueOf(filterable.asConstant().getValue())); } private boolean filterIsRequired() { final CsdlExpression requiresFilter = properties.get(REQUIRES_FILTER); return requiresFilter != null - && Boolean.valueOf(requiresFilter.asConstant().getValue()); + && Boolean.valueOf(requiresFilter.asConstant().getValue()) + && !singleEntityRequested; } private void watchFilterable(final Expression filter) throws ODataJPAFilterException { - if (filteringIsAllowed() + if (!filteringIsAllowed() && filter != null) throw new ODataJPAFilterException(FILTERING_NOT_SUPPORTED, HttpStatusCode.BAD_REQUEST, externalName); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperator.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperator.java new file mode 100644 index 000000000..837d20fc0 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperator.java @@ -0,0 +1,19 @@ +package com.sap.olingo.jpa.processor.core.filter; + +import java.util.List; + +import jakarta.persistence.criteria.Expression; + +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; + +public interface JPAInOperator extends JPAExpressionOperator { + + @SuppressWarnings("unchecked") + @Override + BinaryOperatorKind getOperator(); + + List getFixValues(); + + Expression getLeft() throws ODataApplicationException; +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperatorImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperatorImpl.java new file mode 100644 index 000000000..d5a4de40b --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAInOperatorImpl.java @@ -0,0 +1,49 @@ +package com.sap.olingo.jpa.processor.core.filter; + +import java.util.List; + +import jakarta.persistence.criteria.Expression; + +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; + +public class JPAInOperatorImpl implements JPAInOperator { + + private final JPAOperationConverter converter; + private final JPAOperator left; + private final List right; + + public JPAInOperatorImpl(final JPAOperationConverter converter, + final JPAOperator left, final List right) { + this.converter = converter; + this.left = left; + this.right = right; + } + + @Override + public Expression get() throws ODataApplicationException { + return converter.convert(this); + } + + @Override + public String getName() { + return getOperator().name(); + } + + @Override + public BinaryOperatorKind getOperator() { + return BinaryOperatorKind.IN; + } + + @Override + public List getFixValues() { + return right; + } + + @SuppressWarnings("unchecked") + @Override + public Expression getLeft() throws ODataApplicationException { + return (Expression) left.get(); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverter.java index 743eda551..a6a19d236 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverter.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverter.java @@ -4,12 +4,16 @@ import java.util.function.Function; import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaBuilder.In; import jakarta.persistence.criteria.Expression; +import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAFilterException; public class JPAOperationConverter { @@ -79,22 +83,19 @@ public final Expression convert(final JPABooleanOperatorImp jpaOperator } @SuppressWarnings({ "unchecked" }) - public final Expression convert(@SuppressWarnings("rawtypes") final JPAComparisonOperatorImp jpaOperator) + public final Expression convert(@SuppressWarnings("rawtypes") final JPAComparisonOperator jpaOperator) throws ODataApplicationException { switch (jpaOperator.getOperator()) { case EQ: return equalExpression((left, right) -> (cb.equal(left, right)), (left, right) -> (cb.equal(left, right)), - left -> (cb.isNull(left)), - jpaOperator); + left -> (cb.isNull(left)), jpaOperator); case NE: return equalExpression((left, right) -> (cb.notEqual(left, right)), (left, right) -> (cb.notEqual(left, right)), - left -> (cb.isNotNull(left)), - jpaOperator); + left -> (cb.isNotNull(left)), jpaOperator); case GE: return comparisonExpression((left, right) -> (cb.greaterThanOrEqualTo(left, right)), (left, right) -> (cb - .greaterThanOrEqualTo(left, - right)), jpaOperator); + .greaterThanOrEqualTo(left, right)), jpaOperator); case GT: return comparisonExpression((left, right) -> (cb.greaterThan(left, right)), (left, right) -> (cb.greaterThan( left, right)), jpaOperator); @@ -103,14 +104,28 @@ public final Expression convert(@SuppressWarnings("rawtypes") final JPA right)), jpaOperator); case LE: return comparisonExpression((left, right) -> (cb.lessThanOrEqualTo(left, right)), (left, right) -> (cb - .lessThanOrEqualTo(left, right)), - jpaOperator); + .lessThanOrEqualTo(left, right)), jpaOperator); default: return dbConverter.convert(jpaOperator); } } + @SuppressWarnings("unchecked") + public final Expression convert(final JPAInOperator jpaOperator) + throws ODataApplicationException { + + if (BinaryOperatorKind.IN == jpaOperator.getOperator()) { + final In in = cb.in(jpaOperator.getLeft()); + for (final JPAOperator value : jpaOperator.getFixValues()) { + in.value((T) value.get()); + } + return in; + } + throw new ODataJPAFilterException(ODataJPAFilterException.MessageKeys.NOT_SUPPORTED_OPERATOR, + HttpStatusCode.NOT_IMPLEMENTED, jpaOperator.getName()); + } + @SuppressWarnings("unchecked") public Expression convert(final JPAMethodCall jpaFunction) throws ODataApplicationException { switch (jpaFunction.getFunction()) { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitor.java index 9f3df8ddb..d832aa7e3 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitor.java @@ -122,8 +122,11 @@ public JPAOperator visitBinaryOperator(final BinaryOperatorKind operator, final @Override public JPAOperator visitBinaryOperator(final BinaryOperatorKind operator, final JPAOperator left, - final List right) - throws ExpressionVisitException, ODataApplicationException { + final List right) throws ExpressionVisitException, ODataApplicationException { + + if (operator == BinaryOperatorKind.IN) + return new JPAInOperatorImpl<>(this.jpaComplier.getConverter(), left, right); + throw new ODataJPAFilterException(NOT_SUPPORTED_OPERATOR, NOT_IMPLEMENTED, operator.name()); } @@ -173,7 +176,7 @@ public JPAOperator visitLiteral(final Literal literal) throws ExpressionVisitExc public JPAOperator visitMember(final Member member) throws ExpressionVisitException, ODataApplicationException { try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "visitMember")) { - final JPAPath attributePath = determineAttributePath(this.jpaComplier.getJpaEntityType(), + final JPAPath attributePath = determineAttributePath(this.jpaComplier.getJpaEntityType(), this.jpaComplier.getParent().getJpaEntity(), member, jpaComplier.getAssociation()); checkTransient(attributePath); if (getLambdaType(member.getResourcePath()) == UriResourceKind.lambdaAny) { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAMapBaseResult.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAMapBaseResult.java index 9e120489b..a149d2701 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAMapBaseResult.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAMapBaseResult.java @@ -43,6 +43,6 @@ protected String determineLocale(final Map descGetterMap, final return (String) value; } else { return determineLocale((Map) value, localeAttribute, index + 1); - } - } + } + } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java index e0112dfdf..140e4cb6c 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java @@ -1,7 +1,7 @@ package com.sap.olingo.jpa.processor.core.processor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -61,7 +61,7 @@ public void trace(final Object instance, final String pattern, final Object... a } public boolean hasMemoryInformation() { - return memoryReader.isSAPJvm; + return memoryReader.memoryConsumptionAvailable; } private Object[] composeArguments(final Long threadID, final Object... arguments) { @@ -80,78 +80,68 @@ private String composeLog(final String pattern, final Object... arguments) { private static class Measurement extends RuntimeMeasurement implements JPARuntimeMeasurement { private final MemoryReader memoryReader; + private boolean closed; + private long usedMemory; public Measurement(final Object instance, final String methodName, final MemoryReader memoryReader) { this.setTimeStarted(System.nanoTime()); this.setClassName(instance.getClass().getCanonicalName()); this.setMethodName(methodName); this.memoryReader = memoryReader; + this.closed = false; + this.usedMemory = memoryReader.getCurrentThreadMemoryConsumption() / 1000; } @Override public void close() { this.setTimeStopped(System.nanoTime()); + this.closed = true; final long threadID = Thread.currentThread().getId(); final long runtime = (this.getTimeStopped() - this.getTimeStarted()) / 1000; final Long memory = memoryReader.getCurrentThreadMemoryConsumption() / 1000; + usedMemory = memory - usedMemory; LogFactory.getLog(this.getClassName()) - .debug(String.format("thread: %d, method: %s, runtime [µs]: %d; memory [kb]: %d", + .debug(String.format( + "thread: %d, method: %s, runtime [µs]: %d; over all memory [kb]: %d; additional memory [kb]: %d", threadID, this.getMethodName(), runtime, - memory)); + memory, + usedMemory)); + } + + @Override + public long getMemoryConsumption() { + assert closed; + return usedMemory; } } private static class MemoryReader { - private Object[] memoryInfoReader; - private boolean isSAPJvm = true; + private boolean memoryConsumptionAvailable = true; public MemoryReader() { super(); - try { - memoryInfoReader = getMemoryInformation(); - } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException | InstantiationException e) { - memoryInfoReader = null; - isSAPJvm = false; - } } long getCurrentThreadMemoryConsumption() { - long result = 0; - if (!isSAPJvm) { - return result; + if (!memoryConsumptionAvailable) { + return 0L; } try { - result = getMemoryConsumption(); - } catch (NoClassDefFoundError | Exception e) { - isSAPJvm = false; + return getMemoryConsumption(); + } catch (final Exception e) { + memoryConsumptionAvailable = false; } - return result; + return 0L; } private long getMemoryConsumption() { - - try { - - final Object memoryInfo = ((Method) memoryInfoReader[1]).invoke(memoryInfoReader[0], Thread.currentThread()); - final Method getMemoryConsumption = memoryInfo.getClass().getMethod("getMemoryConsumption"); - return (long) getMemoryConsumption.invoke(memoryInfo); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException - | SecurityException e) { - return 0; + final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + if (threadMXBean instanceof final com.sun.management.ThreadMXBean sunMXBean) { + return sunMXBean.getThreadAllocatedBytes(Thread.currentThread().getId()); } - } - - private Object[] getMemoryInformation() throws ClassNotFoundException, NoSuchMethodException, - IllegalAccessException, InvocationTargetException, InstantiationException { - - final Class info = Class.forName("com.sap.jvm.monitor.vm.VmInfo"); - final Object vmInfo = info.getConstructor().newInstance(); - final Method getMemoryInfo = info.getMethod("getThreadMemoryInfo", Thread.class); - - return new Object[] { vmInfo, getMemoryInfo }; + return 0L; } } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java index 84edb29a5..c9e748cf2 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACountRequestProcessor.java @@ -7,14 +7,13 @@ import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; -import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.UriResourceSingleton; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; -import com.sap.olingo.jpa.processor.core.query.JPAJoinQuery; +import com.sap.olingo.jpa.processor.core.query.JPAJoinCountQuery; /** * resourceParts = uriInfo.getUriResourceParts(); + final var resourceParts = uriInfo.getUriResourceParts(); this.lastItem = resourceParts.get(resourceParts.size() - 1); this.page = requestContext.getPage(); } @@ -77,7 +71,7 @@ public JPANavigationRequestProcessor(final OData odata, final ServiceMetadata se public > void retrieveData(final ODataRequest request, final ODataResponse response, final ContentType responseFormat) throws ODataException { - try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "retrieveData")) { + try (var measurement = debugger.newMeasurement(this, "retrieveData")) { checkRequestSupported(); // Create a JPQL Query and execute it @@ -88,17 +82,17 @@ public > void retrieveData(final ODataRequest request, f throw new ODataJPAProcessorException(QUERY_PREPARATION_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR, e); } - final JPAConvertibleResult result = query.execute(); + final var result = query.execute(); // Read Expand and Collection - final Optional keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(), + final var keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(), page); - final JPAExpandWatchDog watchDog = new JPAExpandWatchDog(determineTargetEntitySet(requestContext)); + final var watchDog = new JPAExpandWatchDog(determineTargetEntitySet(requestContext)); watchDog.watch(uriInfo.getExpandOption(), uriInfo.getUriResourceParts()); result.putChildren(readExpandEntities(request.getAllHeaders(), query.getNavigationInfo(), uriInfo, keyBoundary, watchDog)); // Convert tuple result into an OData Result EntityCollection entityCollection; - try (JPARuntimeMeasurement converterMeasurement = debugger.newMeasurement(this, "convertResult")) { + try (var converterMeasurement = debugger.newMeasurement(this, "convertResult")) { entityCollection = result.asEntityCollection(new JPATupleChildConverter(sd, odata.createUriHelper(), serviceMetadata, requestContext)).get(ROOT_RESULT_KEY); } catch (final ODataApplicationException e) { @@ -107,14 +101,14 @@ public > void retrieveData(final ODataRequest request, f // Set Next Link entityCollection.setNext(buildNextLink(page)); // Count results if requested - final CountOption countOption = uriInfo.getCountOption(); + final var countOption = uriInfo.getCountOption(); if (countOption != null && countOption.getValue()) - entityCollection.setCount(new JPAJoinQuery(odata, requestContext) + entityCollection.setCount(new JPAJoinCountQuery(odata, requestContext) .countResults().intValue()); /* * See part 1: - * -9.1.1 Response Code 200 OK: A request that does not create a resource returns 200 OK if it is completed + * - 9.1.1 Response Code 200 OK: A request that does not create a resource returns 200 OK if it is completed * successfully and the value of the resource is not null. In this case, the response body MUST contain the value * of the resource specified in the request URL. * - 9.2.1 Response Code 404 Not Found: 404 Not Found indicates that the resource specified by the request URL @@ -144,8 +138,8 @@ else if (doesNotExists(entityCollection.getEntities())) response.setStatusCode(HttpStatusCode.NOT_FOUND.getStatusCode()); // 200 OK indicates that either a result was found or that the a Entity Collection query had no result else if (entityCollection.getEntities() != null) { - try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "serialize")) { - final SerializerResult serializerResult = serializer.serialize(request, entityCollection); + try (var serializerMeasurement = debugger.newMeasurement(this, "serialize")) { + final var serializerResult = serializer.serialize(request, entityCollection); createSuccessResponse(response, responseFormat, serializerResult); } } else { @@ -182,7 +176,7 @@ private boolean complexHasNoContent(final List entities) { if (entities.isEmpty()) return false; name = Utility.determineStartNavigationPath(uriInfo.getUriResourceParts()).getProperty().getName(); - final Property property = entities.get(0).getProperty(name); + final var property = entities.get(0).getProperty(name); if (property != null) { for (final Property p : ((ComplexValue) property.getValue()).getValue()) { if (p.getValue() != null) { @@ -232,7 +226,7 @@ private boolean primitiveHasNoContent(final List entities) { if (entities.isEmpty()) return false; name = Utility.determineStartNavigationPath(uriInfo.getUriResourceParts()).getProperty().getName(); - final Property property = entities.get(0).getProperty(name); + final var property = entities.get(0).getProperty(name); return (property != null && property.getValue() == null); } @@ -273,9 +267,9 @@ private Map readExpandEntities(final Map parentHops, final UriInfoResource uriResourceInfo, final Optional keyBoundary, final JPAExpandWatchDog watchDog) throws ODataException { - try (JPARuntimeMeasurement expandMeasurement = debugger.newMeasurement(this, "readExpandEntities")) { + try (var expandMeasurement = debugger.newMeasurement(this, "readExpandEntities")) { - final JPAExpandQueryFactory factory = new JPAExpandQueryFactory(odata, requestContext, cb); + final var factory = new JPAExpandQueryFactory(odata, requestContext, cb); final Map allExpResults = new HashMap<>(); if (watchDog.getRemainingLevels() > 0) { // x/a?$expand=b/c($expand=d,e/f)&$filter=...&$top=3&$orderBy=... @@ -289,11 +283,11 @@ private Map readExpandEntities(final Map itemInfoList = new JPAExpandItemInfoFactory() + final var itemInfoList = new JPAExpandItemInfoFactory() .buildExpandItemInfo(sd, uriResourceInfo, parentHops); for (final JPAExpandItemInfo item : watchDog.filter(itemInfoList)) { - final JPAAbstractExpandQuery expandQuery = factory.createQuery(item, keyBoundary); - final JPAExpandQueryResult expandResult = expandQuery.execute(); + final var expandQuery = factory.createQuery(item, keyBoundary); + final var expandResult = expandQuery.execute(); if (expandResult.getNoResults() > 0) // Only go to the next hop if the current one has a result expandResult.putChildren(readExpandEntities(headers, item.getHops(), item.getUriInfo(), keyBoundary, @@ -303,10 +297,10 @@ private Map readExpandEntities(final Map collectionInfoList = new JPAExpandItemInfoFactory() + final var collectionInfoList = new JPAExpandItemInfoFactory() .buildCollectionItemInfo(sd, uriResourceInfo, parentHops, requestContext.getGroupsProvider()); for (final JPACollectionItemInfo item : collectionInfoList) { - final JPACollectionJoinQuery collectionQuery = new JPACollectionJoinQuery(odata, item, + final var collectionQuery = new JPACollectionJoinQuery(odata, item, new JPAODataInternalRequestContext(item.getUriInfo(), requestContext, headers), keyBoundary); final JPAExpandResult expandResult = collectionQuery.execute(); allExpResults.put(item.getExpandAssociation(), expandResult); @@ -318,7 +312,7 @@ private Map readExpandEntities(final Map determineTargetEntitySet(final JPAODataRequestContextAccess requestContext) throws ODataException { - final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo() + final var bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo() .getUriResourceParts()); if (bindingTarget instanceof EdmEntitySet) return requestContext.getEdmProvider().getServiceDocument().getEntitySet(bindingTarget.getName()) diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java index 2f0769329..65fe065d4 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContext.java @@ -34,6 +34,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataDefaultTransactionFactory; import com.sap.olingo.jpa.processor.core.api.JPAODataGroupProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; +import com.sap.olingo.jpa.processor.core.api.JPAODataQueryDirectives; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAODataServiceContext; @@ -66,6 +67,7 @@ public final class JPAODataInternalRequestContext implements JPAODataRequestCont private JPAODataDatabaseProcessor dbProcessor; private Optional edmProvider; private JPAODataDatabaseOperations operationConverter; + private JPAODataQueryDirectives queryDirectives; public JPAODataInternalRequestContext(@Nonnull final JPAODataRequestContext requestContext, @Nonnull final JPAODataSessionContextAccess sessionContext) { @@ -254,6 +256,11 @@ public JPAODataDatabaseOperations getOperationConverter() { return operationConverter; } + @Override + public JPAODataQueryDirectives getQueryDirectives() { + return queryDirectives; + } + private void copyContextValues(final JPAODataRequestContextAccess context) throws ODataJPAProcessorException { this.em = context.getEntityManager(); @@ -267,6 +274,7 @@ private void copyContextValues(final JPAODataRequestContextAccess context) this.dbProcessor = context.getDatabaseProcessor(); this.edmProvider = Optional.ofNullable(context.getEdmProvider()); this.operationConverter = context.getOperationConverter(); + this.queryDirectives = context.getQueryDirectives(); } private void copyRequestContext(@Nonnull final JPAODataRequestContext requestContext, @@ -285,6 +293,7 @@ private void copyRequestContext(@Nonnull final JPAODataRequestContext requestCon dbProcessor = sessionContext.getDatabaseProcessor(); operationConverter = sessionContext.getOperationConverter(); edmProvider = determineEdmProvider(sessionContext, em); + queryDirectives = sessionContext.getQueryDirectives(); } private Optional determineEdmProvider(final JPAODataSessionContextAccess sessionContext, diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java index 92696746a..4b16cce07 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAProcessorFactory.java @@ -23,13 +23,14 @@ import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; +import com.sap.olingo.jpa.processor.core.api.JPAODataPathInformation; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAODataSessionContextAccess; import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.modify.JPAConversionHelper; import com.sap.olingo.jpa.processor.core.query.JPACountQuery; -import com.sap.olingo.jpa.processor.core.query.JPAJoinQuery; +import com.sap.olingo.jpa.processor.core.query.JPAJoinCountQuery; import com.sap.olingo.jpa.processor.core.serializer.JPASerializerFactory; public final class JPAProcessorFactory { @@ -77,33 +78,35 @@ public JPAActionRequestProcessor createActionProcessor(final UriInfo uriInfo, fi } public JPARequestProcessor createProcessor(final UriInfo uriInfo, final ContentType responseFormat, - final Map> header, final JPAODataRequestContextAccess context) throws ODataException { + final Map> header, final JPAODataRequestContextAccess context, + final JPAODataPathInformation pathInformation) throws ODataException { + + final var resourceParts = uriInfo.getUriResourceParts(); + final var lastItem = resourceParts.get(resourceParts.size() - 1); + final var page = getPage(header, uriInfo, context, pathInformation); - final List resourceParts = uriInfo.getUriResourceParts(); - final UriResource lastItem = resourceParts.get(resourceParts.size() - 1); - final JPAODataPage page = getPage(header, uriInfo, context); - JPAODataRequestContextAccess requestContext; try { - requestContext = new JPAODataInternalRequestContext(page, serializerFactory + final JPAODataRequestContextAccess requestContext = new JPAODataInternalRequestContext(page, serializerFactory .createSerializer(responseFormat, page.uriInfo(), Optional.ofNullable(header.get( HttpHeader.ODATA_MAX_VERSION))), context, header); + + return switch (lastItem.getKind()) { + case count -> new JPACountRequestProcessor(odata, requestContext); + case function -> { + checkFunctionPathSupported(resourceParts); + yield new JPAFunctionRequestProcessor(odata, requestContext); + } + case complexProperty, primitiveProperty, navigationProperty, entitySet, singleton, value -> { + checkNavigationPathSupported(resourceParts); + yield new JPANavigationRequestProcessor(odata, serviceMetadata, requestContext); + } + default -> throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE, + HttpStatusCode.NOT_IMPLEMENTED, lastItem.getKind().toString()); + }; } catch (final ODataJPAIllegalAccessException e) { throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } - switch (lastItem.getKind()) { - case count: - return new JPACountRequestProcessor(odata, requestContext); - case function: - checkFunctionPathSupported(resourceParts); - return new JPAFunctionRequestProcessor(odata, requestContext); - case complexProperty, primitiveProperty, navigationProperty, entitySet, singleton, value: - checkNavigationPathSupported(resourceParts); - return new JPANavigationRequestProcessor(odata, serviceMetadata, requestContext); - default: - throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE, - HttpStatusCode.NOT_IMPLEMENTED, lastItem.getKind().toString()); - } } private void checkFunctionPathSupported(final List resourceParts) throws ODataApplicationException { @@ -126,23 +129,27 @@ private void checkNavigationPathSupported(final List resourceParts) } private JPAODataPage getPage(final Map> headers, final UriInfo uriInfo, - final JPAODataRequestContextAccess requestContext) throws ODataException { + final JPAODataRequestContextAccess requestContext, final JPAODataPathInformation pathInformation) + throws ODataException { - JPAODataPage page = new JPAODataPage(uriInfo, 0, Integer.MAX_VALUE, null); + final var page = new JPAODataPage(uriInfo, 0, Integer.MAX_VALUE, null); // Server-Driven-Paging if (serverDrivenPaging(uriInfo)) { - final String skipToken = skipToken(uriInfo); + final var skipToken = skipToken(uriInfo); if (skipToken != null && !skipToken.isEmpty()) { - page = sessionContext.getPagingProvider().getNextPage(skipToken); - if (page == null) - throw new ODataJPAProcessorException(QUERY_SERVER_DRIVEN_PAGING_GONE, HttpStatusCode.GONE, skipToken); + return sessionContext.getPagingProvider().getNextPage(skipToken, odata, + odata.createServiceMetadata(sessionContext.getEdmProvider(), sessionContext.getEdmProvider() + .getReferences()), requestContext.getRequestParameter(), requestContext.getEntityManager()) + .orElseThrow(() -> new ODataJPAProcessorException(QUERY_SERVER_DRIVEN_PAGING_GONE, HttpStatusCode.GONE, + skipToken)); } else { - final JPACountQuery countQuery = new JPAJoinQuery(odata, new JPAODataInternalRequestContext(uriInfo, + final JPACountQuery countQuery = new JPAJoinCountQuery(odata, new JPAODataInternalRequestContext(uriInfo, requestContext, headers)); - final Integer preferredPageSize = getPreferredPageSize(headers); - final JPAODataPage firstPage = sessionContext.getPagingProvider().getFirstPage(uriInfo, preferredPageSize, - countQuery, requestContext.getEntityManager()); - page = firstPage != null ? firstPage : page; + final var preferredPageSize = getPreferredPageSize(headers); + return sessionContext.getPagingProvider().getFirstPage( + requestContext.getRequestParameter(), pathInformation, uriInfo, preferredPageSize, countQuery, + requestContext.getEntityManager()) + .orElse(page); } } return page; @@ -150,7 +157,7 @@ private JPAODataPage getPage(final Map> headers, final UriI private Integer getPreferredPageSize(final Map> headers) throws ODataJPAProcessorException { - final List preferredHeaders = getHeader("Prefer", headers); + final var preferredHeaders = getHeader("Prefer", headers); if (preferredHeaders != null) { for (final String header : preferredHeaders) { if (header.startsWith("odata.maxpagesize")) { @@ -173,7 +180,7 @@ private boolean serverDrivenPaging(final UriInfo uriInfo) throws ODataJPAProcess throw new ODataJPAProcessorException(QUERY_SERVER_DRIVEN_PAGING_NOT_IMPLEMENTED, HttpStatusCode.NOT_IMPLEMENTED); } - final List resourceParts = uriInfo.getUriResourceParts(); + final var resourceParts = uriInfo.getUriResourceParts(); return sessionContext.getPagingProvider() != null && resourceParts.get(resourceParts.size() - 1).getKind() != UriResourceKind.function; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java index fd6f85e14..557d9825e 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java @@ -114,7 +114,6 @@ public static List>> convertToCriteriaPaths(final From return jpaPaths.stream() .map(jpaPath -> ExpressionUtility.> convertToCriteriaPath(from, jpaPath.getPath())) .toList(); - // .collect(Collectors.toList()); } public static Object convertValueOnAttribute(final OData odata, final JPAAttribute attribute, final String value) diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java index 3d88bb8d3..ada042989 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractJoinQuery.java @@ -25,6 +25,7 @@ import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.AbstractQuery; import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Path; @@ -66,6 +67,7 @@ import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.filter.JPAFilterComplier; import com.sap.olingo.jpa.processor.core.filter.JPAFilterCrossComplier; import com.sap.olingo.jpa.processor.core.filter.JPAFilterRestrictionsWatchDog; import com.sap.olingo.jpa.processor.core.filter.JPAOperationConverter; @@ -321,15 +323,21 @@ protected jakarta.persistence.criteria.Expression createWhere(final Uri final List navigationInfo) throws ODataApplicationException { try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "createWhere")) { - jakarta.persistence.criteria.Expression whereCondition = null; + jakarta.persistence.criteria.Expression whereCondition = null; // Given keys: Organizations('1')/Roles(...) whereCondition = createKeyWhere(navigationInfo); // http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398301 // http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398094 // https://tools.oasis-open.org/version-control/browse/wsvn/odata/trunk/spec/ABNF/odata-abnf-construction-rules.txt try { - whereCondition = addWhereClause(whereCondition, navigationInfo.get(navigationInfo.size() - 1) - .getFilterCompiler().compile()); + final JPAFilterComplier compiler = navigationInfo.get(navigationInfo.size() - 1).getFilterCompiler(); + final Expression filter = compiler.compile(); + final Optional watchDog = compiler.getWatchDog(); + if (watchDog.isPresent()) { + watchDog.get().watch(filter); + } + whereCondition = addWhereClause(whereCondition, filter); + } catch (final ExpressionVisitException e) { throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_FILTER_ERROR, HttpStatusCode.BAD_REQUEST, e); @@ -698,13 +706,15 @@ && isCollectionPropertyQuery()) { final JPAOperationConverter converter = new JPAOperationConverter(cb, requestContext.getOperationConverter()); final JPAODataRequestContextAccess subContext = new JPAODataInternalRequestContext(uriResource, requestContext); final JPAFilterRestrictionsWatchDog watchDog = new JPAFilterRestrictionsWatchDog( - ((JPAAssociationAttribute) element)); + ((JPAAssociationAttribute) element), !lastInfo.getKeyPredicates().isEmpty()); lastInfo.setFilterCompiler(new JPAFilterCrossComplier(odata, sd, targetEt, converter, this, path, lastInfo.getAssociationPath(), subContext, watchDog)); } else { + lastInfo.getKeyPredicates(); final JPAOperationConverter converter = new JPAOperationConverter(cb, requestContext.getOperationConverter()); final JPAODataRequestContextAccess subContext = new JPAODataInternalRequestContext(uriResource, requestContext); - final JPAFilterRestrictionsWatchDog watchDog = new JPAFilterRestrictionsWatchDog(entitySet.orElse(null)); + final JPAFilterRestrictionsWatchDog watchDog = new JPAFilterRestrictionsWatchDog(entitySet.orElse(null), + !lastInfo.getKeyPredicates().isEmpty()); lastInfo.setFilterCompiler(new JPAFilterCrossComplier(odata, sd, jpaEntity, converter, this, lastInfo .getAssociationPath(), subContext, watchDog)); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQuery.java index ef00b037d..6db23cd1d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQuery.java @@ -27,6 +27,7 @@ import org.apache.olingo.server.api.uri.queryoption.expression.VisitableExpression; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; @@ -54,6 +55,7 @@ public abstract class JPAAbstractSubQuery extends JPAAbstractQuery { protected final From from; protected final JPAAssociationPath association; protected JPAFilterElementComplier filterComplier; + protected final boolean isCollectionProperty; JPAAbstractSubQuery(final OData odata, final JPAServiceDocument sd, final JPAEntityType jpaEntity, final EntityManager em, final JPAAbstractQuery parent, final From from, @@ -63,6 +65,7 @@ public abstract class JPAAbstractSubQuery extends JPAAbstractQuery { this.parentQuery = parent; this.from = from; this.association = association; + this.isCollectionProperty = toCollectionProperty(); } JPAAbstractSubQuery(final OData odata, final JPAServiceDocument sd, final JPAEntityType jpaEntity, @@ -101,7 +104,12 @@ JPAODataRequestContextAccess getContext() { protected void createRoots(final JPAAssociationPath association) throws ODataJPAQueryException { if (association != null && association.hasJoinTable()) { - if (association.getJoinTable().getEntityType() != null) { + if (isCollectionProperty) { + if (association.getTargetType() != null) + this.queryRoot = subQuery.from(association.getTargetType().getTypeClass()); + else + this.queryRoot = createCollectionRoot(); + } else if (association.getJoinTable().getEntityType() != null) { createJoinTableRoot(association); } else { throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NOT_IMPLEMENTED, @@ -112,6 +120,17 @@ protected void createRoots(final JPAAssociationPath association) throws ODataJPA } } + private From createCollectionRoot() { + From join = subQuery.from(association.getSourceType().getTypeClass()); + for (final JPAElement element : association.getPath()) { + join = join.join(element.getInternalName()); + if (element instanceof JPACollectionAttribute) { + break; + } + } + return join; + } + void createJoinTableRoot(final JPAAssociationPath association) { // At least for EclipseLink the order is of importance. First the join table has to be mentioned. It becomes // the "leading" table. Otherwise EclipseLink replaces the join table within the WHERE clause. @@ -228,9 +247,7 @@ protected void handleAggregation(final Subquery subQuery, final From gr final List> groupByList = new ArrayList<>(); if (filterComplier != null && this.aggregationType != null) { for (final JPAPath onItem : groupByPath) { - Path subPath = groupByRoot; - for (final JPAElement jpaPathElement : onItem.getPath()) - subPath = subPath.get(jpaPathElement.getInternalName()); + final var subPath = ExpressionUtility.convertToCriteriaPath(groupByRoot, onItem.getPath()); groupByList.add(subPath); } subQuery.groupBy(groupByList); @@ -300,4 +317,9 @@ protected List determineAggregationLeftColumns() throws ODataJPAQueryEx } public abstract List>> getLeftPaths() throws ODataJPAIllegalAccessException; + + final boolean toCollectionProperty() { + final var path = association.getPath(); + return path.get(path.size() - 1) instanceof JPACollectionAttribute; + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java index 369a96dc6..d6a1da072 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java @@ -41,7 +41,6 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorSubquery; -import com.sap.olingo.jpa.processor.core.api.JPAODataPage; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; @@ -118,8 +117,8 @@ public JPAExpandFilterQuery(final OData odata, final JPAODataRequestContextAcces @SuppressWarnings("unchecked") @Nonnull @Override - public Subquery getSubQuery(@Nullable final Subquery childQuery, - final VisitableExpression expression, final List>> inPath) throws ODataApplicationException { + public Subquery getSubQuery(@Nullable final Subquery childQuery, + final VisitableExpression expression, final List>> inPath) throws ODataApplicationException { // Last childQuery == null try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createSubQuery")) { final ProcessorSubquery nextQuery = (ProcessorSubquery) this.subQuery; @@ -146,7 +145,8 @@ protected final JPAFilterCrossComplier addFilterCompiler(final JPANavigationProp final JPAODataRequestContextAccess subContext = new JPAODataInternalRequestContext(navigationInfo.getUriInfo(), requestContext); - final JPAFilterRestrictionsWatchDog watchDog = new JPAFilterRestrictionsWatchDog(this.association.getLeaf()); + final JPAFilterRestrictionsWatchDog watchDog = new JPAFilterRestrictionsWatchDog(this.association.getLeaf(), + !navigationInfo.getKeyPredicates().isEmpty()); return new JPAFilterCrossComplier(odata, sd, navigationInfo.getEntityType(), converter, this, navigationInfo.getFromClause(), null, subContext, watchDog); } @@ -207,7 +207,7 @@ private List> createGroupBy(final Subquery childQuery, private List createOrderBy(final Subquery childQuery) throws ODataApplicationException { if (!hasRowLimit(childQuery)) { final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, queryRoot, cb, groups); - return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), (JPAODataPage) null); + return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage()); } return emptyList(); } @@ -218,8 +218,8 @@ JPAQueryPair createQueries(@Nullable final Subquery childQuery) throws ODataA JPARowNumberFilterQuery rowNumberQuery; try { - rowNumberQuery = new JPARowNumberFilterQuery(odata, requestContext, navigationInfo, parentQuery, - childAssociation, jpaEntity.getKeyPath()); + rowNumberQuery = new JPARowNumberFilterQuery(odata, requestContext, navigationInfo, parentQuery, + childAssociation, jpaEntity.getKeyPath()); } catch (final ODataException e) { throw new ODataJPAQueryException(e, INTERNAL_SERVER_ERROR); } @@ -233,9 +233,9 @@ JPAQueryPair createQueries(@Nullable final Subquery childQuery) throws ODataA void createRoots(final Subquery childQuery, final JPAQueryPair queries, final ProcessorSubquery nextQuery) throws ODataApplicationException { - if (hasRowLimit(childQuery)) + if (hasRowLimit(childQuery)) this.queryRoot = nextQuery.from((ProcessorSubquery) ((JPARowNumberFilterQuery) queries.inner()) - .getSubQuery(childQuery, null, Collections.emptyList())); + .getSubQuery(childQuery, null, Collections.emptyList())); else this.queryRoot = subQuery.from(this.jpaEntity.getTypeClass()); navigationInfo.setFromClause(queryRoot); @@ -268,17 +268,19 @@ Expression createWhereSubQuery(@Nullable final Subquery childQuery, } private Integer getSkipValue(@Nullable final Subquery childQuery) { + if (navigationInfo.getPage() != null) + return navigationInfo.getPage().skip(); if (navigationInfo.getUriInfo().getSkipOption() != null && childQuery == null) return navigationInfo.getUriInfo().getSkipOption().getValue(); - else - return null; + return null; } private Integer getTopValue(@Nullable final Subquery childQuery) { + if (navigationInfo.getPage() != null) + return navigationInfo.getPage().top(); if (navigationInfo.getUriInfo().getTopOption() != null && childQuery == null) return navigationInfo.getUriInfo().getTopOption().getValue(); - else - return null; + return null; } private boolean hasRowLimit(@Nullable final Subquery childQuery) { @@ -310,8 +312,8 @@ private List selectionPathIn() throws ODataJPAQueryException { ? association.getJoinTable().getJoinColumns() : association.getJoinColumnsList(); return columns.stream() - .map(JPAOnConditionItem::getLeftPath) - .toList(); + .map(JPAOnConditionItem::getLeftPath) + .toList(); } catch (final ODataJPAModelException e) { if (e.getId().equals(NO_JOIN_TABLE_TYPE.getKey())) { throw new ODataJPAQueryException(QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING, INTERNAL_SERVER_ERROR, @@ -319,10 +321,10 @@ private List selectionPathIn() throws ODataJPAQueryException { } throw new ODataJPAQueryException(QUERY_PREPARATION_ERROR, INTERNAL_SERVER_ERROR, e); } - } + } @Override public List>> getLeftPaths() throws ODataJPAIllegalAccessException { return Collections.emptyList(); - } + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java index f74a029cb..b51c4c3dc 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java @@ -169,8 +169,8 @@ final Map count() throws ODataApplicationException { Subquery linkSubQueries(final LinkedList hops) throws ODataApplicationException { Subquery subQuery = null; while (!hops.isEmpty() && hops.getFirst() instanceof JPAAbstractSubQuery) { - final JPAAbstractSubQuery hop = (JPAAbstractSubQuery) hops.pop(); - subQuery = hop.getSubQuery(subQuery, null, Collections.emptyList()); + final JPAAbstractSubQuery hop = (JPAAbstractSubQuery) hops.pop(); + subQuery = hop.getSubQuery(subQuery, null, Collections.emptyList()); } return subQuery; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java new file mode 100644 index 000000000..b36f07762 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java @@ -0,0 +1,63 @@ +package com.sap.olingo.jpa.processor.core.query; + +import java.util.Collections; + +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.AbstractQuery; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable; +import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; + +public class JPAJoinCountQuery extends JPAJoinQuery implements JPACountQuery { + + public JPAJoinCountQuery(final OData odata, final JPAODataRequestContextAccess requestContext) + throws ODataException { + super(odata, requestContext); + } + + /** + * Fulfill $count requests. For details see + * OData Version 4.0 Part 1 - 11.2.5.5 System Query Option $count + * @return + * @throws ODataApplicationException + */ + @Override + public Long countResults() throws ODataApplicationException { + /* + * URL example: + * .../Organizations?$count=true + * .../Organizations/$count + * .../Organizations('3')/Roles/$count + */ + try (var measurement = debugger.newMeasurement(this, "countResults")) { + + new JPACountWatchDog(entitySet.map(JPAAnnotatable.class::cast)).watch(this.uriResource); + createFromClause(Collections.emptyList(), Collections.emptyList(), cq, lastInfo); + final var whereClause = createWhere(); + if (whereClause != null) + cq.where(whereClause); + cq.multiselect(cb.count(target)); + final var result = em.createQuery(cq).getSingleResult(); + return (Long) result.get(0); + } catch (final JPANoSelectionException e) { + return 0L; + } + } + + @Override + public JPAConvertibleResult execute() throws ODataApplicationException { + // Pre-process URI parameter, so they can be used at different places + return null; + } + + @Override + public AbstractQuery getQuery() { + return cq; + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java index 61f290782..64d1e8f1b 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java @@ -8,7 +8,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; @@ -16,31 +15,24 @@ import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.AbstractQuery; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.From; -import org.apache.olingo.commons.api.edm.EdmBindingTarget; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriInfoResource; -import org.apache.olingo.server.api.uri.UriResource; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; -import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; -public class JPAJoinQuery extends JPAAbstractJoinQuery implements JPACountQuery { +public class JPAJoinQuery extends JPAAbstractJoinQuery { private static List determineNavigationInfo( - final JPAServiceDocument sd, final UriInfoResource uriResource) throws ODataException { + final JPAServiceDocument sd, final UriInfoResource uriResource) + throws ODataException { return Utility.determineNavigationPath(sd, uriResource.getUriResourceParts(), uriResource); } @@ -48,8 +40,8 @@ private static List determineNavigationInfo( private static JPAEntityType determineTargetEntityType(final JPAODataRequestContextAccess requestContext) throws ODataException { - final List resources = requestContext.getUriInfo().getUriResourceParts(); - final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(resources); + final var resources = requestContext.getUriInfo().getUriResourceParts(); + final var bindingTarget = Utility.determineBindingTarget(resources); if (bindingTarget instanceof EdmBoundCast) return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getEntityType()); return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getName()); @@ -59,9 +51,9 @@ private static JPAEntityType determineTargetEntityType(final JPAODataRequestCont private static JPAEntityType determineODataTargetEntityType(final JPAODataRequestContextAccess requestContext) throws ODataApplicationException { - final List resources = requestContext.getUriInfo().getUriResourceParts(); + final var resources = requestContext.getUriInfo().getUriResourceParts(); try { - final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(resources); + final var bindingTarget = Utility.determineBindingTarget(resources); return Optional.ofNullable(requestContext.getEdmProvider().getServiceDocument() // NOSONAR .getEntity(bindingTarget.getEntityType())) .orElseThrow(() -> new ODataJPAQueryException(QUERY_PREPARATION_ENTITY_UNKNOWN, INTERNAL_SERVER_ERROR, @@ -78,52 +70,23 @@ public JPAJoinQuery(final OData odata, final JPAODataRequestContextAccess reques requestContext, determineNavigationInfo(requestContext.getEdmProvider().getServiceDocument(), requestContext .getUriInfo())); entitySet = determineTargetEntitySet(requestContext); - } - - /** - * Fulfill $count requests. For details see - * OData Version 4.0 Part 1 - 11.2.5.5 System Query Option $count - * @return - * @throws ODataApplicationException - */ - @Override - public Long countResults() throws ODataApplicationException { - /* - * URL example: - * .../Organizations?$count=true - * .../Organizations/$count - * .../Organizations('3')/Roles/$count - */ - try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "countResults")) { - new JPACountWatchDog(entitySet.map(JPAAnnotatable.class::cast)).watch(this.uriResource); - final CriteriaQuery countQuery = cb.createQuery(Number.class); - createFromClause(Collections.emptyList(), Collections.emptyList(), countQuery, lastInfo); - final jakarta.persistence.criteria.Expression whereClause = createWhere(); - if (whereClause != null) - countQuery.where(whereClause); - countQuery.select(cb.count(target)); - return em.createQuery(countQuery).getSingleResult().longValue(); - } catch (final JPANoSelectionException e) { - return 0L; - } + lastInfo.setPage(requestContext.getPage()); } @Override public JPAConvertibleResult execute() throws ODataApplicationException { // Pre-process URI parameter, so they can be used at different places - final SelectionPathInfo selectionPath = buildSelectionPathList(this.uriResource); - try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "execute")) { - final List orderByNavigationAttributes = extractOrderByNavigationAttributes(uriResource + final var selectionPath = buildSelectionPathList(this.uriResource); + try (var measurement = debugger.newMeasurement(this, "execute")) { + final var orderByNavigationAttributes = extractOrderByNavigationAttributes(uriResource .getOrderByOption()); - final Map> joinTables = createFromClause(orderByNavigationAttributes, + final var joinTables = createFromClause(orderByNavigationAttributes, selectionPath.joinedPersistent(), cq, lastInfo); cq.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), target, groups)) .distinct(determineDistinct()); - final jakarta.persistence.criteria.Expression whereClause = createWhere(); + final var whereClause = createWhere(); if (whereClause != null) cq.where(whereClause); @@ -135,9 +98,9 @@ public JPAConvertibleResult execute() throws ODataApplicationException { final TypedQuery typedQuery = em.createQuery(cq); addTopSkip(typedQuery); - final HashMap> result = new HashMap<>(1); + final var result = new HashMap>(1); List intermediateResult; - try (JPARuntimeMeasurement resultMeasurement = debugger.newMeasurement(this, "getResultList")) { + try (var resultMeasurement = debugger.newMeasurement(this, "getResultList")) { intermediateResult = typedQuery.getResultList(); } result.put(ROOT_RESULT_KEY, intermediateResult); @@ -163,9 +126,9 @@ public AbstractQuery getQuery() { return cq; } - private jakarta.persistence.criteria.Expression createWhere() throws ODataApplicationException { + jakarta.persistence.criteria.Expression createWhere() throws ODataApplicationException { - final Expression filter = super.createWhere(uriResource, navigationInfo); + final var filter = super.createWhere(uriResource, navigationInfo); return addWhereClause(filter, createProtectionWhere(claimsProvider)); } @@ -187,7 +150,7 @@ private JPAConvertibleResult returnEmptyResult(final Collection selecti private JPAConvertibleResult returnResult(@Nonnull final Collection selectionPath, final HashMap> result) throws ODataApplicationException { - final JPAEntityType odataEntityType = determineODataTargetEntityType(requestContext); + final var odataEntityType = determineODataTargetEntityType(requestContext); if (lastInfo.getAssociationPath() != null && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) return new JPACollectionQueryResult(result, null, odataEntityType, lastInfo.getAssociationPath(), selectionPath); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForExistsQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForExistsQuery.java index 376e43027..6803b0080 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForExistsQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForExistsQuery.java @@ -14,7 +14,6 @@ import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriParameter; -import org.apache.olingo.server.api.uri.UriResourceKind; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; @@ -32,17 +31,55 @@ public class JPANavigationCountForExistsQuery extends JPANavigationCountQuery im final List keyPredicates) throws ODataApplicationException { super(odata, sd, type, em, parent, from, association, claimsProvider, keyPredicates); - this.aggregationType = UriResourceKind.count; } /** + * Only in case there a more than one join columns + * + *
+   * SELECT E0."Number" S0, E0."ID" S1
+   *     FROM "OLINGO"."Collections" E0
+   *     WHERE (EXISTS(
+   *         SELECT E2."ID" S0
+  *              FROM "OLINGO"."Collections" E1
+  *              INNER JOIN "OLINGO"."NestedComplex" E2
+  *                ON ((E1."ID" = E2."ID")
+  *                AND (E1."Number" = E2."Number"))
+  *              WHERE ((E2."ID" = E0."ID")
+  *              AND (E2."Number" = E0."Number"))
+  *              GROUP BY E2."ID", E2."Number"
+  *              HAVING (COUNT(E2."ID") = 1))) *
+   * 
+ */ + @Override + protected void createSubQueryCollectionProperty() throws ODataApplicationException { + try { + final List left = association + .getJoinTable() + .getJoinColumns(); // Collections --> + createSelectClauseAggregation(subQuery, queryRoot, left, false); + Expression whereCondition = createWhereByAssociation(from, queryRoot, left); + whereCondition = addWhereClause(whereCondition, + createProtectionWhereForEntityType(claimsProvider, (JPAEntityType) association.getSourceType(), queryRoot)); + + subQuery.where(applyAdditionalFilter(whereCondition)); + handleAggregation(subQuery, queryRoot, determineAggregationLeftColumns()); + } catch (final ODataJPAModelException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + } + + /** + *
    * SELECT E2."CodePublisher" S0
-   * FROM "OLINGO"."AdministrativeDivision" E2
-   * WHERE (((E2."ParentDivisionCode" = E0."DivisionCode")
-   * AND (E2."ParentCodeID" = E0."CodeID"))
-   * AND (E2."CodePublisher" = E0."CodePublisher"))
-   * GROUP BY E2."CodePublisher", E2."ParentCodeID", E2."ParentDivisionCode"
-   * HAVING (COUNT(E2."CodePublisher") = 2)
+   *     FROM "OLINGO"."AdministrativeDivision" E2
+   *     WHERE (((E2."ParentDivisionCode" = E0."DivisionCode")
+   *     AND (E2."ParentCodeID" = E0."CodeID"))
+   *     AND (E2."CodePublisher" = E0."CodePublisher"))
+   *     GROUP BY E2."CodePublisher", E2."ParentCodeID", E2."ParentDivisionCode"
+   *     HAVING (COUNT(E2."CodePublisher") = 2)
+   * 
+ * * @param * @param childQuery * @param query @@ -62,18 +99,22 @@ protected void createSubQueryAggregation(final Subquery query) handleAggregation(query, queryRoot, determineAggregationRightColumns()); } + /** + *
+   * select distinct E0."SourceKey" S0, E0."Number" S1
+   *     from "OLINGO"."JoinSource" E0
+   *     where exists ( select E2."SourceID" S0
+   *         from  "OLINGO"."JoinRelation" E2
+   *         inner join "OLINGO"."JoinTarget" E3
+   *           on (E2."TargetID" = E3."TargetKey")
+   *         where (E2."SourceID" = E0."SourceKey")
+   *         group by E2."SourceID"
+   *         having (COUNT(E3."TargetKey") = 2))
+   * 
+ */ @Override protected void createSubQueryJoinTableAggregation() throws ODataApplicationException { -// select distinct E0."SourceKey" S0, E0."Number" S1 -// from "OLINGO"."JoinSource" E0 -// where exists ( select E2."SourceID" S0 -// from "OLINGO"."JoinRelation" E2 -// inner join "OLINGO"."JoinTarget" E3 -// on (E2."TargetID" = E3."TargetKey") -// where (E2."SourceID" = E0."SourceKey") -// group by E2."SourceID" -// having (COUNT(E3."TargetKey") = 2)) try { final List left = association .getJoinTable() diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForInQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForInQuery.java index f4ee41213..8f3c936b4 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForInQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountForInQuery.java @@ -17,11 +17,13 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider; import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.filter.JPAInvertibleVisitableExpression; public class JPANavigationCountForInQuery extends JPANavigationCountQuery implements InExpressionValue { private Optional>>> leftPaths; @@ -35,6 +37,62 @@ public class JPANavigationCountForInQuery extends JPANavigationCountQuery implem leftPaths = Optional.empty(); } + @Override + protected void createSubQueryCollectionProperty() throws ODataApplicationException { + + if (association.getTargetType() == null) { + createSubQueryCollectionNoTargetType(); + } else { + createSubQueryCollectionWithTargetType(); + } + } + + /** + *
+   * WHERE t0."ID" IN (
+   *    SELECT t5."ParentID"
+   *       FROM "OLINGO"."InhouseAddress" t5
+   *       WHERE (t5."ParentID" IS NOT NULL)
+   *       GROUP BY t5."ParentID"
+   *       HAVING (COUNT(t5."ParentID") = 2))
+   * 
+ */ + private void createSubQueryCollectionWithTargetType() throws ODataApplicationException { + try { + final var right = association.getJoinTable().getInverseJoinColumns(); + createSelectClauseAggregation(subQuery, queryRoot, right, true); + + leftPaths = Optional.of(ExpressionUtility.convertToCriteriaPaths(from, determineAggregationLeftColumns())); + Expression whereCondition = createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot); + whereCondition = addWhereClause(whereCondition, createNullCheckRight(queryRoot, right)); + whereCondition = applyAdditionalFilter(whereCondition); + if (whereCondition != null) + subQuery.where(whereCondition); + createGroupBy(subQuery, queryRoot, right); + createHaving(subQuery); + } catch (final ODataJPAModelException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + } + + private void createSubQueryCollectionNoTargetType() throws ODataApplicationException { + try { + final var left = association.getLeftColumnsList(); + createSelectClauseJoin(subQuery, queryRoot, left, true); + + leftPaths = Optional.of(ExpressionUtility.convertToCriteriaPaths(from, left)); + Expression whereCondition = createProtectionWhereForEntityType(claimsProvider, + (JPAEntityType) association.getSourceType(), queryRoot); + whereCondition = addWhereClause(whereCondition, createNullCheck(queryRoot, left)); + whereCondition = applyAdditionalFilter(whereCondition); + if (whereCondition != null) + subQuery.where(whereCondition); + handleAggregation(subQuery, queryRoot, left); + } catch (final ODataJPAModelException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + } + /** *
    * WHERE (t0."CodePublisher", t0."CodeID", t0."DivisionCode") IN (
@@ -47,13 +105,15 @@ public class JPANavigationCountForInQuery extends JPANavigationCountQuery implem
   @Override
   protected  void createSubQueryAggregation(final Subquery query) throws ODataApplicationException {
 
-    createSelectClauseJoin(query, queryRoot, determineAggregationRightColumns(), true);
+    final var aggregationColumns = determineAggregationRightColumns();
+    createSelectClauseJoin(query, queryRoot, aggregationColumns, true);
     leftPaths = Optional.of(ExpressionUtility.convertToCriteriaPaths(from, determineAggregationLeftColumns()));
     Expression whereCondition = createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot);
+    whereCondition = addWhereClause(whereCondition, createNullCheck(queryRoot, aggregationColumns));
     whereCondition = applyAdditionalFilter(whereCondition);
     if (whereCondition != null)
       query.where(whereCondition);
-    handleAggregation(query, queryRoot, determineAggregationRightColumns());
+    handleAggregation(query, queryRoot, aggregationColumns);
   }
 
   /**
@@ -84,6 +144,7 @@ protected void createSubQueryJoinTableAggregation() throws ODataApplicationExcep
       Expression whereCondition = createWhereByAssociation(queryJoinTable, queryRoot, right);
       whereCondition = addWhereClause(whereCondition,
           createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot));
+      whereCondition = addWhereClause(whereCondition, createNullCheckRight(queryJoinTable, left));
       whereCondition = applyAdditionalFilter(whereCondition);
       if (whereCondition != null)
         subQuery.where(whereCondition);
@@ -94,6 +155,31 @@ protected void createSubQueryJoinTableAggregation() throws ODataApplicationExcep
     }
   }
 
+  private Expression createNullCheckRight(final From joinTable,
+      final List conditionItems) {
+
+    Expression result = null;
+    for (final JPAOnConditionItem onCondition : conditionItems) {
+      final var subPath = ExpressionUtility.convertToCriteriaPath(joinTable, onCondition.getRightPath().getPath());
+      result = addWhereClause(result, cb.isNotNull(subPath));
+    }
+    return result;
+  }
+
+  private Expression createNullCheck(final From queryRoot, final List aggregationColumns) {
+
+    Expression result = null;
+    if (filterComplier.getExpressionMember() instanceof final JPAInvertibleVisitableExpression visitableExpression
+        && visitableExpression.isInversionRequired()) {
+
+      for (final var column : aggregationColumns) {
+        final var subPath = ExpressionUtility.convertToCriteriaPath(queryRoot, column.getPath());
+        result = addWhereClause(result, cb.isNotNull(subPath));
+      }
+    }
+    return result;
+  }
+
   private List>> buildLeftPath(final From from,
       final List conditionItems) {
     return conditionItems.stream()
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQuery.java
index 7440365fd..e2184d93b 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQuery.java
@@ -48,7 +48,10 @@ public  Subquery getSubQuery(final Subquery childQuery,
       throw new ODataJPAQueryException(QUERY_PREPARATION_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR);
     final Subquery query = (Subquery) this.subQuery;
     if (this.association.getJoinTable() != null) {
-      createSubQueryJoinTableAggregation();
+      if (isCollectionProperty)
+        createSubQueryCollectionProperty();
+      else
+        createSubQueryJoinTableAggregation();
     } else {
       createSubQueryAggregation(query);
     }
@@ -59,6 +62,8 @@ public  Subquery getSubQuery(final Subquery childQuery,
 
   protected abstract void createSubQueryJoinTableAggregation() throws ODataApplicationException;
 
+  protected abstract void createSubQueryCollectionProperty() throws ODataApplicationException;
+
   protected void createHaving(final Subquery subQuery) throws ODataApplicationException {
     try {
       subQuery.having(this.filterComplier.compile());
diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQuery.java
index 6463d4843..9cf06be67 100644
--- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQuery.java
+++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQuery.java
@@ -24,6 +24,7 @@
 import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException;
 import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider;
 import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException;
+import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys;
 
 public class JPANavigationFilterQuery extends JPANavigationSubQuery implements ExistsExpressionValue {
 
@@ -55,21 +56,74 @@ public  Subquery getSubQuery(final Subquery childQuery,
       final VisitableExpression expression, final List>> inPath) throws ODataApplicationException {
 
     if (this.association.getJoinTable() != null) {
-      createSubQueryJoinTable();
+      if (isCollectionProperty)
+        createSubQueryCollectionProperty(childQuery, expression, inPath);
+      else
+        createSubQueryJoinTable();
     } else {
       createSubQuery(childQuery, expression, inPath);
     }
     return (Subquery) this.subQuery;
   }
 
+  protected  void createSubQueryCollectionProperty(final Subquery childQuery,
+      @Nullable final VisitableExpression expression, final List>> inPath)
+      throws ODataApplicationException {
+
+    if (association.getTargetType() == null) {
+      createSubQueryCollectionNoTargetType();
+    } else {
+      createSubQueryCollectionWithTargetType(childQuery, expression, inPath);
+    }
+  }
+
+  private void createSubQueryCollectionNoTargetType() throws ODataApplicationException {
+    debugger.debug(this,
+        "Collection property without entity type: queries using such properties in the filter are not supported");
+    throw new ODataJPAQueryException(
+        MessageKeys.QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED, HttpStatusCode.BAD_REQUEST);
+  }
+
+  /**
+   * 
+   * SELECT E0."ID" S0, E0."ETag" S1
+   *     FROM "OLINGO"."BusinessPartner" E0
+   *     WHERE EXISTS (
+   *         SELECT E2."BusinessPartnerID" S0
+   *             FROM "OLINGO"."Comment" E2
+   *             WHERE (E2."BusinessPartnerID" = E0."ID"
+   *             AND (E2."Text" LIKE '%just%')))
+   * 
+ * + * @throws ODataApplicationException + */ + @SuppressWarnings("unchecked") + private void createSubQueryCollectionWithTargetType(final Subquery childQuery, + @Nullable final VisitableExpression expression, final List>> inPath) + throws ODataApplicationException { + + createSelectClauseJoin(subQuery, queryRoot, determineAggregationRightColumns(), false); + Expression whereCondition = addWhereClause( + createWhereByAssociation(from, queryRoot, determineJoinColumns()), + createWhereByKey(queryRoot, this.keyPredicates, jpaEntity)); + if (childQuery != null) { + whereCondition = cb.and(whereCondition, + ExpressionUtility.createSubQueryBasedExpression((Subquery>>) childQuery, inPath, cb, + expression)); + } + whereCondition = addWhereClause(whereCondition, + createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot)); + + subQuery.where(applyAdditionalFilter(whereCondition)); + } + @SuppressWarnings("unchecked") protected void createSubQuery(final Subquery childQuery, @Nullable final VisitableExpression expression, final List>> inPath) throws ODataApplicationException { createSelectClauseJoin(subQuery, queryRoot, determineAggregationRightColumns(), false); - Expression whereCondition = null; - whereCondition = addWhereClause( + Expression whereCondition = addWhereClause( createWhereByAssociation(from, queryRoot, determineJoinColumns()), createWhereByKey(queryRoot, this.keyPredicates, jpaEntity)); if (childQuery != null) { @@ -79,6 +133,7 @@ protected void createSubQuery(final Subquery childQuery, } whereCondition = addWhereClause(whereCondition, createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot)); + subQuery.where(applyAdditionalFilter(whereCondition)); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilder.java index 799ef9057..3792ac267 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilder.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilder.java @@ -152,7 +152,7 @@ public JPANavigationFilterQueryBuilder setGroups(final List groups) { boolean asInQuery() throws ODataJPAFilterException { try { return (cb instanceof ProcessorCriteriaBuilder - || association.getJoinColumnsList().size() == 1) + || association.getLeftColumnsList().size() == 1) && getAggregationType(expression) != null; } catch (final ODataJPAModelException e) { throw new ODataJPAFilterException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQuery.java index 355089130..a777a12c0 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQuery.java @@ -117,7 +117,7 @@ private void createSubQueryNull(final Subquery query) *
*/ private void createSubQueryJoinTableNull() throws ODataApplicationException { - try { + try { final List left = association .getJoinTable() .getJoinColumns(); // Person --> diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationPropertyInfo.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationPropertyInfo.java index f64e18acc..ffd2c9639 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationPropertyInfo.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationPropertyInfo.java @@ -19,9 +19,10 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.api.JPAODataPage; import com.sap.olingo.jpa.processor.core.filter.JPAFilterComplier; - -public final class JPANavigationPropertyInfo implements JPANavigationPropertyInfoAccess { + +public final class JPANavigationPropertyInfo implements JPANavigationPropertyInfoAccess { private static final Log LOGGER = LogFactory.getLog(JPANavigationPropertyInfo.class); private final JPAServiceDocument sd; private final UriResourcePartTyped navigationTarget; @@ -31,6 +32,7 @@ public final class JPANavigationPropertyInfo implements JPANavigationPropertyInf private final UriInfoResource uriInfo; private JPAEntityType et = null; private JPAFilterComplier filterCompiler = null; + private JPAODataPage page = null; /** * @@ -46,6 +48,7 @@ public JPANavigationPropertyInfo(final JPANavigationPropertyInfo original) { this.uriInfo = original.getUriInfo(); this.sd = original.getServiceDocument(); this.et = this.uriInfo instanceof JPAExpandItem ? ((JPAExpandItem) uriInfo).getEntityType() : null; + this.page = original.getPage(); } public JPANavigationPropertyInfo(final JPAServiceDocument sd, final JPAAssociationPath associationPath, @@ -165,6 +168,14 @@ void setFromClause(final From from) { fromClause = from; } + JPAODataPage getPage() { + return page; + } + + void setPage(final JPAODataPage page) { + this.page = page; + } + private JPAServiceDocument getServiceDocument() { return sd; } @@ -180,4 +191,5 @@ public String toString() { return super.toString(); } } + } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationSubQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationSubQuery.java index 876142a63..a1d4a877c 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationSubQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPANavigationSubQuery.java @@ -22,7 +22,6 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADescriptionAttribute; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; @@ -42,13 +41,23 @@ public abstract class JPANavigationSubQuery extends JPAAbstractSubQuery { final EntityManager em, final JPAAbstractQuery parent, final From from, final JPAAssociationPath association, final Optional claimsProvider, final List keyPredicates) throws ODataApplicationException { + super(odata, sd, jpaEntity, em, parent, from, association, claimsProvider); + this.keyPredicates = keyPredicates; - this.subQuery = parent.getQuery().subquery(this.jpaEntity.getKeyType()); + this.subQuery = createSubQuery(parent); this.locale = parent.getLocale(); createRoots(association); } + Subquery createSubQuery(final JPAAbstractQuery parent) { + // Null if there is no et for collection property + if (this.jpaEntity == null) + return parent.getQuery().subquery(((JPAEntityType) association.getSourceType()).getKeyType()); + else + return parent.getQuery().subquery(this.jpaEntity.getKeyType()); + } + final void buildExpression(final VisitableExpression expression, final List groups) throws ODataApplicationException { this.filterComplier = new JPAFilterElementComplier(odata, sd, em, jpaEntity, new JPAOperationConverter(cb, @@ -77,9 +86,7 @@ protected void createGroupBy(final Subquery subQuery, final From from, final List> groupByList = new ArrayList<>(); for (final JPAOnConditionItem onCondition : conditionItems) { - Path subPath = from; - for (final JPAElement jpaPathElement : onCondition.getRightPath().getPath()) - subPath = subPath.get(jpaPathElement.getInternalName()); + final var subPath = ExpressionUtility.convertToCriteriaPath(from, onCondition.getRightPath().getPath()); groupByList.add(subPath); } subQuery.groupBy(groupByList); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAQueryPair.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAQueryPair.java index a3b4aa9cd..f518ee69d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAQueryPair.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAQueryPair.java @@ -1,6 +1,6 @@ package com.sap.olingo.jpa.processor.core.query; - -record JPAQueryPair(JPAAbstractQuery inner, JPAAbstractQuery outer) { + +record JPAQueryPair(JPAAbstractQuery inner, JPAAbstractQuery outer) { @Override public String toString() { diff --git a/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties b/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties index dc8969db8..87756bf3a 100644 --- a/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties +++ b/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties @@ -78,6 +78,7 @@ ODataJPAQueryException.QUERY_PREPARATION_NOT_IMPLEMENTED = The requested service ODataJPAQueryException.QUERY_PREPARATION_NOT_ALLOWED_MEMBER = Not authorized to use '%1$s' within OrderBy clauses ODataJPAQueryException.QUERY_PREPARATION_ORDER_BY_TRANSIENT= Usage of '%1$s' within OrderBy clauses not supported ODataJPAQueryException.QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING=The expand implementation requires that a join table ('%1$s') has an entity. +ODataJPAQueryException.QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED=Filter with collection property not supported ODataJPAQueryException.NOT_SUPPORTED_RESOURCE_TYPE = Resource type '%1$s' not supported ODataJPAQueryException.MISSING_CLAIMS_PROVIDER = Authorization information missing ODataJPAQueryException.MISSING_CLAIM = Authorization information missing for at least one property diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProviderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProviderTest.java new file mode 100644 index 000000000..5d215b6aa --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPADefaultPagingProviderTest.java @@ -0,0 +1,79 @@ +package com.sap.olingo.jpa.processor.core.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.queryoption.SkipOption; +import org.apache.olingo.server.api.uri.queryoption.TopOption; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class JPADefaultPagingProviderTest { + + private JPAODataPagingProvider cut; + private UriInfo uriInfo; + private SkipOption skipOption; + private TopOption topOption; + + @BeforeEach + void setup() { + cut = new JPADefaultPagingProvider(); + uriInfo = mock(UriInfo.class); + skipOption = mock(SkipOption.class); + topOption = mock(TopOption.class); + } + + @Test + void testGetNextPageReturnsEmptyOptional() { + assertTrue(cut.getNextPage("Test", null, null, null, null).isEmpty()); + } + + @Test + void testGetFirstPageReturnsSkipZeroIfAbsence() throws ODataApplicationException { + when(uriInfo.getSkipOption()).thenReturn(null); + final var act = cut.getFirstPage(null, null, uriInfo, null, null, null); + assertEquals(0, act.orElseGet(() -> fail("No page found")).skip()); + } + + @Test + void testGetFirstPageReturnsSkipAsRequested() throws ODataApplicationException { + when(skipOption.getValue()).thenReturn(99); + when(uriInfo.getSkipOption()).thenReturn(skipOption); + final var act = cut.getFirstPage(null, null, uriInfo, null, null, null); + assertEquals(99, act.orElseGet(() -> fail("No page found")).skip()); + } + + @Test + void testGetFirstPageReturnsTopMaxIfAbsence() throws ODataApplicationException { + when(uriInfo.getTopOption()).thenReturn(null); + final var act = cut.getFirstPage(null, null, uriInfo, null, null, null); + assertEquals(Integer.MAX_VALUE, act.orElseGet(() -> fail("No page found")).top()); + } + + @Test + void testGetFirstPageReturnsTopAsRequested() throws ODataApplicationException { + when(topOption.getValue()).thenReturn(99); + when(uriInfo.getTopOption()).thenReturn(topOption); + final var act = cut.getFirstPage(null, null, uriInfo, null, null, null); + assertEquals(99, act.orElseGet(() -> fail("No page found")).top()); + } + + @Test + void testGetFirstPageReturnsTopAndSkipAsRequested() throws ODataApplicationException { + when(topOption.getValue()).thenReturn(13); + when(uriInfo.getTopOption()).thenReturn(topOption); + when(skipOption.getValue()).thenReturn(99); + when(uriInfo.getSkipOption()).thenReturn(skipOption); + final var act = cut.getFirstPage(null, null, uriInfo, null, null, null); + assertEquals(13, act.orElseGet(() -> fail("No page found")).top()); + assertEquals(99, act.orElseGet(() -> fail("No page found")).skip()); + assertNull(act.get().skipToken()); + } + +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java index 3b5bba93c..a447d63db 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java @@ -76,4 +76,9 @@ public JPAODataPagingProvider getPagingProvider() { public List getAnnotationProvider() { return Collections.singletonList(annotationProvider); } + + @Override + public JPAODataQueryDirectives getQueryDirectives() { + return null; + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesTest.java new file mode 100644 index 000000000..511dcf899 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataQueryDirectivesTest.java @@ -0,0 +1,32 @@ +package com.sap.olingo.jpa.processor.core.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.Test; + +class JPAODataQueryDirectivesTest { + + @Test + void testBuildWithoutSettingMaxZero() throws ODataException { + + final JPAODataSessionContextAccess act = JPAODataServiceContext.with() + .useQueryDirectives().build().build(); + assertEquals(0, act.getQueryDirectives().getMaxValuesInInClause()); + } + + @Test + void testBuildProvideSetValue() throws ODataException { + + final JPAODataSessionContextAccess act = JPAODataServiceContext.with() + .useQueryDirectives().maxValuesInInClause(300).build().build(); + assertEquals(300, act.getQueryDirectives().getMaxValuesInInClause()); + } + + @Test + void testContextReturnDirectivesWithZero() throws ODataException { + + final JPAODataSessionContextAccess act = JPAODataServiceContext.with().build(); + assertEquals(0, act.getQueryDirectives().getMaxValuesInInClause()); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandlerTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandlerTest.java index b15f730f1..d94dae4ba 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandlerTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataRequestHandlerTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -23,7 +24,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaRequestMapper; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; import com.sap.olingo.jpa.processor.core.util.TestBase; @@ -112,7 +112,7 @@ void testMappingPathInSessionContextCreatesMapper() throws ODataException { .build(); cut = new JPAODataRequestHandler(context, odata); cut.process(request, response); - verify(handler, times(1)).process(argThat(new WrappedHttpRequestMatcher()), any()); + verify(handler, times(1)).process(isA(HttpServletRequestWrapper.class), any()); } @Test @@ -144,31 +144,16 @@ void testEmptyMappingPathInSessionContextEmptyMapper() throws ODataException { verify(handler, times(1)).process(argThat(new HttpRequestMatcher()), any()); } - public static class HttpRequestMatcher implements ArgumentMatcher { + public static class HttpRequestMatcher implements ArgumentMatcher { @Override - public boolean matches(final javax.servlet.http.HttpServletRequest argument) { - if (argument instanceof JakartaRequestMapper) { - final HttpServletRequest wrapped = ((JakartaRequestMapper) argument).getWrapped(); - return wrapped instanceof HttpServletRequest && !(wrapped instanceof HttpServletRequestWrapper); - } else - return false; - } - } - - public static class WrappedHttpRequestMatcher implements ArgumentMatcher { - @Override - public boolean matches(final javax.servlet.http.HttpServletRequest argument) { - if (argument instanceof JakartaRequestMapper) { - final HttpServletRequest wrapped = ((JakartaRequestMapper) argument).getWrapped(); - return wrapped instanceof HttpServletRequestWrapper; - } else - return false; + public boolean matches(final HttpServletRequest argument) { + return argument instanceof HttpServletRequest && !(argument instanceof HttpServletRequestWrapper); } } public int getStatus() { - final ArgumentCaptor acStatus = ArgumentCaptor.forClass(Integer.class); - verify(response).setStatus(acStatus.capture()); - return acStatus.getValue(); + final ArgumentCaptor status = ArgumentCaptor.forClass(Integer.class); + verify(response).setStatus(status.capture()); + return status.getValue(); } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java index e5ab06c25..f37e73999 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java @@ -107,6 +107,17 @@ void checkReturnsProvidedPagingProvider() throws ODataException { assertEquals(provider, cut.getPagingProvider()); } + @Test + void checkReturnsDefaultProvidedPagingIfNotProvider() throws ODataException { + cut = JPAODataServiceContext.with() + .setDataSource(ds) + .setPUnit(PUNIT_NAME) + .setPagingProvider(null) + .build(); + + assertTrue(cut.getPagingProvider() instanceof JPADefaultPagingProvider); + } + @Test void checkEmptyListOnNoReferencesProvided() throws ODataException { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java new file mode 100644 index 000000000..940f25e53 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java @@ -0,0 +1,101 @@ +package com.sap.olingo.jpa.processor.core.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Optional; + +import jakarta.persistence.EntityManagerFactory; + +import org.apache.olingo.commons.api.edmx.EdmxReference; +import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; +import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; + +class JPAODataSessionContextAccessTest { + private JPAODataSessionContextAccess cut; + + @BeforeEach + void setup() { + cut = new JPAODataSessionContextAccessDouble(); + } + + @Test + void testDefaultGetEntityManagerFactory() { + final Optional act = cut.getEntityManagerFactory(); + + assertNotNull(act); + assertTrue(act.isEmpty()); + } + + @Test + void testDefaultGetErrorProcessor() { + assertNull(cut.getErrorProcessor()); + } + + @Test + void testDefaultGetMappingPath() { + assertTrue(cut.getMappingPath().isEmpty()); + } + + @Test + void testDefaultGetBatchProcessorFactory() { + assertNull(cut.getBatchProcessorFactory()); + } + + @Test + void testDefaultUseAbsoluteContextURL() { + assertFalse(cut.useAbsoluteContextURL()); + } + + private static class JPAODataSessionContextAccessDouble implements JPAODataSessionContextAccess { + + @Override + public JPAODataDatabaseProcessor getDatabaseProcessor() { + return null; + } + + @Override + public JPAEdmProvider getEdmProvider() throws ODataException { + return null; + } + + @Override + public JPAODataDatabaseOperations getOperationConverter() { + return null; + } + + @Override + public List getReferences() { + return null; + } + + @Override + public List getPackageName() { + return null; + } + + @Override + public JPAODataPagingProvider getPagingProvider() { + return null; + } + + @Override + public List getAnnotationProvider() { + return null; + } + + @Override + public JPAODataQueryDirectives getQueryDirectives() { + return null; + } + + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProviderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProviderTest.java index cfc243743..550062cc8 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProviderTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProviderTest.java @@ -110,7 +110,7 @@ void testReturnNullIfEntitySetIsUnknownButMaxPageSizeHeader() throws ODataApplic } @Test - void testReturnRespectMaxPageSizeHeader() throws ODataApplicationException { + void testReturnGetFirstPageRespectMaxPageSizeHeader() throws ODataApplicationException { final UriInfo info = buildUriInfo(); final JPAExamplePagingProvider cut = createOrganizationCut(5); final JPAODataPage act = cut.getFirstPage(info, 3, countQuery, null); @@ -121,6 +121,19 @@ void testReturnRespectMaxPageSizeHeader() throws ODataApplicationException { assertEquals(info, act.uriInfo()); } + @Test + void testReturnGetNextPageRespectMaxPageSizeHeader() throws ODataApplicationException { + final UriInfo info = buildUriInfo(); + final JPAExamplePagingProvider cut = createOrganizationCut(5); + JPAODataPage act = cut.getFirstPage(info, 3, countQuery, null); + act = cut.getNextPage(toODataString((String) act.skipToken())); + + assertEquals(3, act.skip()); + assertEquals(3, act.top()); + assertNotNull(toODataString((String) act.skipToken())); + assertEquals(info, act.uriInfo()); + } + @Test void testReturnSkipTokenNullAtLastPage() throws ODataApplicationException { final UriInfo info = buildUriInfo(); @@ -196,7 +209,18 @@ void testRespectTopSkipOfUriNextPage() throws ODataApplicationException { } @Test - void testBufferFilled() throws ODataApplicationException { + void testNoSkipTokenIfRealNoReturnedLowerPage() throws ODataApplicationException { + final UriInfo info = buildUriInfo(); + addTopSkipToUri(info, 8, 10); + final JPAExamplePagingProvider cut = createOrganizationCut(5); + final JPAODataPage act = cut.getFirstPage(info, null, countQuery, null); + + assertNull(act.skipToken()); + assertEquals(8, act.skip()); + } + + @Test + void testBufferFull() throws ODataApplicationException { final UriInfo info = buildUriInfo(); final Map sizes = new HashMap<>(); sizes.put("Organizations", 2); @@ -212,7 +236,7 @@ void testBufferFilled() throws ODataApplicationException { } @Test - void testBufferNotFilled() throws ODataApplicationException { + void testBufferNotFull() throws ODataApplicationException { final UriInfo info = buildUriInfo(); final Map sizes = new HashMap<>(); sizes.put("Organizations", 2); @@ -269,11 +293,15 @@ private UriInfo buildUriInfo(final String esName, final String etName) { } private void addTopSkipToUri(final UriInfo info) { + addTopSkipToUri(info, 2, 7); + } + + private void addTopSkipToUri(final UriInfo info, final int skip, final int top) { final SkipOption skipOption = mock(SkipOption.class); final TopOption topOption = mock(TopOption.class); - when(skipOption.getValue()).thenReturn(2); - when(topOption.getValue()).thenReturn(7); + when(skipOption.getValue()).thenReturn(skip); + when(topOption.getValue()).thenReturn(top); when(info.getSkipOption()).thenReturn(skipOption); when(info.getTopOption()).thenReturn(topOption); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapperTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapperTest.java deleted file mode 100644 index 4d31564f2..000000000 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaRequestMapperTest.java +++ /dev/null @@ -1,491 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.Principal; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class JakartaRequestMapperTest { - private static final String PARAMETER_NAME = "Parameter Name"; - private static final String PATH = "test/path"; - private static final String HEADER_VALUE = "Header Value"; - private static final String DATE_HEADER = "DateHeader"; - private static final String COOKIE_PATH = "COOKIE_PATH"; - private static final String COOKIE_DOMAIN = "COOKIE_DOMAIN"; - private static final String COOKIE_COMMAND = "COOKIE_COMMAND"; - private static final String COOKIE_ATTRIBUTE_VALUE = "COOKIE_ATTRIBUTE_VALUE"; - private static final String COOKIE_ATTRIBUTE = "COOKIE_ATTRIBUTE"; - private static final String COOKIE_VALUE = "Watch You"; - private static final String COOKIE_NAME = "TestCookie"; - private static final String HEADER_NAME = "SingleHeader"; - private JakartaRequestMapper cut; - private HttpServletRequest jakartaRequest; - - @BeforeEach - void setup() { - jakartaRequest = mock(HttpServletRequest.class); - cut = new JakartaRequestMapper(jakartaRequest); - } - - @Test - void testJakartaRequestMapperThrowsNPE() { - assertThrows(NullPointerException.class, () -> new JakartaRequestMapper(null)); - } - - @Test - void testGetAuthType() { - when(jakartaRequest.getAuthType()).thenReturn("Basic"); - assertEquals("Basic", cut.getAuthType()); - } - - @Test - void testGetCookies() { - final Cookie cookie = buildCookie(); - when(jakartaRequest.getCookies()).thenReturn(new Cookie[] { cookie }); - final javax.servlet.http.Cookie[] acts = cut.getCookies(); - assertEquals(1, acts.length); - final javax.servlet.http.Cookie act = acts[0]; - assertEquals(COOKIE_NAME, act.getName()); - assertEquals(COOKIE_VALUE, act.getValue()); - assertEquals(0, act.getVersion()); // See getter command - assertNull(act.getComment()); // See getter command - assertEquals(COOKIE_DOMAIN.toLowerCase(), act.getDomain()); - assertEquals(300000, act.getMaxAge()); - assertEquals(COOKIE_PATH, act.getPath()); - assertTrue(act.getSecure()); - assertTrue(act.isHttpOnly()); - } - - @Test - void testGetDateHeader() { - when(jakartaRequest.getDateHeader(DATE_HEADER)).thenReturn(10L); - assertEquals(10L, cut.getDateHeader(DATE_HEADER)); - } - - @Test - void testGetHeader() { - when(jakartaRequest.getHeader(HEADER_NAME)).thenReturn(HEADER_VALUE); - assertEquals(HEADER_VALUE, cut.getHeader(HEADER_NAME)); - } - - @Test - void testGetHeaders() { - @SuppressWarnings("unchecked") - final Enumeration enumeration = mock(Enumeration.class); - when(jakartaRequest.getHeaders(HEADER_NAME)).thenReturn(enumeration); - assertEquals(enumeration, cut.getHeaders(HEADER_NAME)); - } - - @Test - void testGetHeaderNames() { - @SuppressWarnings("unchecked") - final Enumeration enumeration = mock(Enumeration.class); - when(jakartaRequest.getHeaderNames()).thenReturn(enumeration); - assertEquals(enumeration, cut.getHeaderNames()); - } - - @Test - void testGetIntHeader() { - when(jakartaRequest.getIntHeader(HEADER_NAME)).thenReturn(10); - assertEquals(10, cut.getIntHeader(HEADER_NAME)); - } - - @Test - void testGetMethod() { - when(jakartaRequest.getMethod()).thenReturn("POST"); - assertEquals("POST", cut.getMethod()); - } - - @Test - void testGetPathInfo() { - when(jakartaRequest.getPathInfo()).thenReturn(PATH); - assertEquals(PATH, cut.getPathInfo()); - } - - @Test - void testGetPathTranslated() { - when(jakartaRequest.getPathTranslated()).thenReturn(PATH); - assertEquals(PATH, cut.getPathTranslated()); - } - - @Test - void testGetContextPath() { - when(jakartaRequest.getContextPath()).thenReturn(PATH); - assertEquals(PATH, cut.getContextPath()); - } - - @Test - void testGetQueryString() { - when(jakartaRequest.getQueryString()).thenReturn(PATH); - assertEquals(PATH, cut.getQueryString()); - } - - @Test - void testGetRemoteUser() { - when(jakartaRequest.getRemoteUser()).thenReturn("Willi"); - assertEquals("Willi", cut.getRemoteUser()); - } - - @Test - void testIsUserInRole() { - when(jakartaRequest.isUserInRole("Manager")).thenReturn(true); - when(jakartaRequest.isUserInRole("Employee")).thenReturn(false); - assertTrue(cut.isUserInRole("Manager")); - assertFalse(cut.isUserInRole("Employee")); - } - - @Test - void testGetUserPrincipal() { - final Principal principal = mock(Principal.class); - when(jakartaRequest.getUserPrincipal()).thenReturn(principal); - assertEquals(principal, cut.getUserPrincipal()); - } - - @Test - void testGetRequestedSessionId() { - when(jakartaRequest.getRequestedSessionId()).thenReturn("123"); - assertNull(cut.getRequestedSessionId()); - } - - @Test - void testGetRequestURI() { - when(jakartaRequest.getRequestURI()).thenReturn("/test/hallo.html"); - assertEquals("/test/hallo.html", cut.getRequestURI()); - } - - @Test - void testGetRequestURL() { - final StringBuffer url = new StringBuffer(); - when(jakartaRequest.getRequestURL()).thenReturn(url); - assertEquals(url, cut.getRequestURL()); - } - - @Test - void testGetServletPath() { - when(jakartaRequest.getServletPath()).thenReturn(PATH + HEADER_VALUE); - assertEquals(PATH + HEADER_VALUE, cut.getServletPath()); - } - - @Test - void testGetSessionThrowsException() { - assertThrows(IllegalAccessError.class, () -> cut.getSession(true)); - assertThrows(IllegalAccessError.class, () -> cut.getSession()); - } - - @Test - void testIsRequestedSessionIdValid() { - when(jakartaRequest.isRequestedSessionIdValid()).thenReturn(true); - assertTrue(cut.isRequestedSessionIdValid()); - } - - @Test - void testIsRequestedSessionIdFromCookie() { - when(jakartaRequest.isRequestedSessionIdFromCookie()).thenReturn(true); - assertTrue(cut.isRequestedSessionIdFromCookie()); - } - - @Test - void testIsRequestedSessionIdFromURL() { - when(jakartaRequest.isRequestedSessionIdFromURL()).thenReturn(true); - assertTrue(cut.isRequestedSessionIdFromURL()); - } - - @Test - void testIsRequestedSessionIdFromUrl() { - when(jakartaRequest.isRequestedSessionIdFromURL()).thenReturn(true); - assertTrue(cut.isRequestedSessionIdFromUrl()); - } - - @Test - void testAuthenticate() throws IOException, ServletException, javax.servlet.ServletException { - final HttpServletResponse response = mock(HttpServletResponse.class); - final javax.servlet.http.HttpServletResponse responseOld = mock(javax.servlet.http.HttpServletResponse.class); - - when(jakartaRequest.authenticate(response)).thenReturn(true); - assertFalse(cut.authenticate(responseOld)); - } - - @Test - void testLogin() throws javax.servlet.ServletException, ServletException { - cut.login("Willi", "1234"); - verify(jakartaRequest).login("Willi", "1234"); - } - - @Test - void testLoginRethrowsException() throws javax.servlet.ServletException, ServletException { - doThrow(new ServletException()).when(jakartaRequest).login("Willi", "1234"); - assertThrows(javax.servlet.ServletException.class, () -> cut.login("Willi", "1234")); - } - - @Test - void testLogout() throws javax.servlet.ServletException, ServletException { - cut.logout(); - verify(jakartaRequest).logout(); - } - - @Test - void testLogoutRethrowsException() throws javax.servlet.ServletException, ServletException { - doThrow(new ServletException()).when(jakartaRequest).logout(); - assertThrows(javax.servlet.ServletException.class, () -> cut.logout()); - } - - @Test - void testGetPartThrowsException() { - assertThrows(IllegalAccessError.class, () -> cut.getPart(COOKIE_NAME)); - assertThrows(IllegalAccessError.class, () -> cut.getParts()); - } - - @Test - void testGetAttributeNames() { - @SuppressWarnings("unchecked") - final Enumeration enumeration = mock(Enumeration.class); - when(jakartaRequest.getAttributeNames()).thenReturn(enumeration); - assertEquals(enumeration, cut.getAttributeNames()); - } - - @Test - void testGetCharacterEncoding() { - when(jakartaRequest.getCharacterEncoding()).thenReturn("UTF-8"); - assertEquals("UTF-8", cut.getCharacterEncoding()); - } - - @Test - void testSetCharacterEncoding() throws UnsupportedEncodingException { - cut.setCharacterEncoding("UTF-8"); - verify(jakartaRequest).setCharacterEncoding("UTF-8"); - } - - @Test - void testGetContentLength() { - when(jakartaRequest.getContentLength()).thenReturn(356); - assertEquals(356, cut.getContentLength()); - } - - @Test - void testGetContentType() { - when(jakartaRequest.getContentType()).thenReturn("HTML"); - assertEquals("HTML", cut.getContentType()); - } - - @Test - void testGetInputStream() throws IOException { - final ServletInputStream inputStream = mock(ServletInputStream.class); - when(jakartaRequest.getInputStream()).thenReturn(inputStream); - assertTrue(cut.getInputStream() instanceof JakartaServletInputStream); - } - - @Test - void testGetParameter() throws UnsupportedEncodingException { - when(jakartaRequest.getParameter(PARAMETER_NAME)).thenReturn("HTML"); - assertEquals("HTML", cut.getParameter(PARAMETER_NAME)); - } - - @Test - void testGetParameterNames() throws UnsupportedEncodingException { - @SuppressWarnings("unchecked") - final Enumeration enumeration = mock(Enumeration.class); - when(jakartaRequest.getParameterNames()).thenReturn(enumeration); - assertEquals(enumeration, cut.getParameterNames()); - } - - @Test - void testGetParameterValues() throws UnsupportedEncodingException { - final String[] values = new String[] { "A", "B" }; - when(jakartaRequest.getParameterValues(PARAMETER_NAME)).thenReturn(values); - assertEquals(values, cut.getParameterValues(PARAMETER_NAME)); - } - - @Test - void testGetParameterMap() throws UnsupportedEncodingException { - final Map parameters = new HashMap<>(); - when(jakartaRequest.getParameterMap()).thenReturn(parameters); - assertEquals(parameters, cut.getParameterMap()); - } - - @Test - void testGetScheme() throws UnsupportedEncodingException { - when(jakartaRequest.getScheme()).thenReturn("http"); - assertEquals("http", cut.getScheme()); - } - - @Test - void testGetServerName() throws UnsupportedEncodingException { - when(jakartaRequest.getServerName()).thenReturn("localhost"); - assertEquals("localhost", cut.getServerName()); - } - - @Test - void testGetServerPort() throws UnsupportedEncodingException { - when(jakartaRequest.getServerPort()).thenReturn(1234); - assertEquals(1234, cut.getServerPort()); - } - - @Test - void testGetReader() throws IOException { - final BufferedReader reader = mock(BufferedReader.class); - when(jakartaRequest.getReader()).thenReturn(reader); - assertEquals(reader, cut.getReader()); - } - - @Test - void testGetRemoteAddr() throws IOException { - when(jakartaRequest.getRemoteAddr()).thenReturn("Test"); - assertEquals("Test", cut.getRemoteAddr()); - } - - @Test - void testGetRemoteHost() throws IOException { - when(jakartaRequest.getRemoteHost()).thenReturn("remotehost"); - assertEquals("remotehost", cut.getRemoteHost()); - } - - @Test - void testGetRemotePort() throws IOException { - when(jakartaRequest.getRemotePort()).thenReturn(80); - assertEquals(80, cut.getRemotePort()); - } - - @Test - void testRemoveAttribute() throws IOException { - cut.removeAttribute("Attribute Name"); - verify(jakartaRequest).removeAttribute("Attribute Name"); - } - - @Test - void testSetAttribute() { - cut.setAttribute("Attribute Name", 110); - verify(jakartaRequest).setAttribute("Attribute Name", 110); - } - - @Test - void testGetLocale() throws IOException { - when(jakartaRequest.getLocale()).thenReturn(Locale.FRENCH); - assertEquals(Locale.FRENCH, cut.getLocale()); - } - - @Test - void testGetLocales() throws IOException { - @SuppressWarnings("unchecked") - final Enumeration enumeration = mock(Enumeration.class); - when(jakartaRequest.getLocales()).thenReturn(enumeration); - assertEquals(enumeration, cut.getLocales()); - } - - @Test - void testIsSecure() throws IOException { - when(jakartaRequest.isSecure()).thenReturn(true); - assertEquals(true, cut.isSecure()); - } - - @Test - void testGetLocalName() throws IOException { - when(jakartaRequest.getLocalName()).thenReturn("Local Name"); - assertEquals("Local Name", cut.getLocalName()); - } - - @Test - void testGetLocalAddr() throws IOException { - when(jakartaRequest.getLocalAddr()).thenReturn("Local Addresse"); - assertEquals("Local Addresse", cut.getLocalAddr()); - } - - @Test - void testGetLocalPort() throws IOException { - when(jakartaRequest.getLocalPort()).thenReturn(9009); - assertEquals(9009, cut.getLocalPort()); - } - - @Test - void testGetRealPath() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.getRealPath(PATH)); - } - - @Test - void testGetRequestDispatcherThrows() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.getRequestDispatcher(PATH)); - } - - @Test - void testGetServletContextThrows() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.getServletContext()); - } - - @Test - void testStartAsyncThrows() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.startAsync()); - } - - @Test - void testStartAsyncParameterThrows() throws IOException { - final ServletRequest servletRequest = mock(ServletRequest.class); - final ServletResponse servletResponse = mock(ServletResponse.class); - assertThrows(IllegalAccessError.class, () -> cut.startAsync(servletRequest, servletResponse)); - } - - @Test - void testIsAsyncStarted() { - when(jakartaRequest.isAsyncStarted()).thenReturn(true); - assertTrue(cut.isAsyncStarted()); - } - - @Test - void testIsAsyncSupported() { - when(jakartaRequest.isAsyncSupported()).thenReturn(true); - assertTrue(cut.isAsyncSupported()); - } - - @Test - void testGetAsyncContextThrows() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.getAsyncContext()); - } - - @Test - void testGetDispatcherTypeThrows() throws IOException { - assertThrows(IllegalAccessError.class, () -> cut.getDispatcherType()); - } - - @Test - void testGetWrapper() { - assertEquals(jakartaRequest, cut.getWrapped()); - } - - @SuppressWarnings("removal") - private Cookie buildCookie() { - final Cookie cookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - cookie.setAttribute(COOKIE_ATTRIBUTE, COOKIE_ATTRIBUTE_VALUE); - cookie.setComment(COOKIE_COMMAND); - cookie.setDomain(COOKIE_DOMAIN); - cookie.setHttpOnly(true); - cookie.setMaxAge(300000); - cookie.setPath(COOKIE_PATH); - cookie.setSecure(true); - cookie.setVersion(3); - return cookie; - } -} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapperTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapperTest.java deleted file mode 100644 index 01ef28798..000000000 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/mapper/JakartaResponseMapperTest.java +++ /dev/null @@ -1,292 +0,0 @@ -package com.sap.olingo.jpa.processor.core.api.mapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.util.Collection; -import java.util.Locale; - -import javax.servlet.http.Cookie; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletResponse; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -class JakartaResponseMapperTest { - private static final String HEADER_VALUE = "HeaderValue"; - private static final String HEADER_NAME2 = "HeaderName2"; - private static final String HEADER_NAME = "HeaderName"; - private static final String COOKIE_PATH = "COOKIE_PATH"; - private static final String COOKIE_DOMAIN = "COOKIE_DOMAIN"; - private static final String COOKIE_COMMAND = "COOKIE_COMMAND"; - private static final String COOKIE_VALUE = "Watch You"; - private static final String COOKIE_NAME = "TestCookie"; - private HttpServletResponse jakartaResponse; - private JakartaResponseMapper cut; - - @BeforeEach - void setup() { - jakartaResponse = mock(HttpServletResponse.class); - cut = new JakartaResponseMapper(jakartaResponse); - } - - @Test - void testGetCharacterEncoding() { - when(jakartaResponse.getCharacterEncoding()).thenReturn("UTF-8"); - assertEquals("UTF-8", cut.getCharacterEncoding()); - } - - @Test - void testGetContentType() { - when(jakartaResponse.getContentType()).thenReturn("HTML"); - assertEquals("HTML", cut.getContentType()); - } - - @Test - void testGetOutputStream() throws IOException { - final ServletOutputStream outputStream = mock(ServletOutputStream.class); - when(jakartaResponse.getOutputStream()).thenReturn(outputStream); - assertTrue(cut.getOutputStream() instanceof JakartaServletOutputStream); - } - - @Test - void testGetWriter() throws IOException { - final PrintWriter writer = mock(PrintWriter.class); - when(jakartaResponse.getWriter()).thenReturn(writer); - assertEquals(writer, cut.getWriter()); - } - - @Test - void testSetCharacterEncoding() throws UnsupportedEncodingException { - cut.setCharacterEncoding("UTF-8"); - verify(jakartaResponse).setCharacterEncoding("UTF-8"); - } - - @Test - void testSetContentLength() throws UnsupportedEncodingException { - cut.setContentLength(654); - verify(jakartaResponse).setContentLength(654); - } - - @Test - void testSetContentType() throws UnsupportedEncodingException { - cut.setContentType("Test"); - verify(jakartaResponse).setContentType("Test"); - } - - @Test - void testSetBufferSize() throws UnsupportedEncodingException { - cut.setBufferSize(654); - verify(jakartaResponse).setBufferSize(654); - } - - @Test - void testBufferSize() throws IOException { - when(jakartaResponse.getBufferSize()).thenReturn(1000); - assertEquals(1000, cut.getBufferSize()); - } - - @Test - void testFlushBuffer() throws IOException { - cut.flushBuffer(); - verify(jakartaResponse).flushBuffer(); - } - - @Test - void testResetBuffer() throws IOException { - cut.resetBuffer(); - verify(jakartaResponse).resetBuffer(); - } - - @Test - void testIsCommitted() throws IOException { - when(jakartaResponse.isCommitted()).thenReturn(true); - assertEquals(true, cut.isCommitted()); - } - - @Test - void testReset() throws IOException { - cut.reset(); - verify(jakartaResponse).reset(); - } - - @Test - void testSetLocale() throws UnsupportedEncodingException { - cut.setLocale(Locale.CANADA_FRENCH); - verify(jakartaResponse).setLocale(Locale.CANADA_FRENCH); - } - - @Test - void testGetLocale() { - when(jakartaResponse.getLocale()).thenReturn(Locale.GERMANY); - assertEquals(Locale.GERMANY, cut.getLocale()); - } - - @SuppressWarnings("removal") - @Test - void testAddCookies() { - final Cookie cookie = buildCookie(); - cut.addCookie(cookie); - - final ArgumentCaptor argument = ArgumentCaptor.forClass( - jakarta.servlet.http.Cookie.class); - verify(jakartaResponse).addCookie(argument.capture()); - - final jakarta.servlet.http.Cookie act = argument.getValue(); - - assertEquals(COOKIE_NAME, act.getName()); - assertEquals(COOKIE_VALUE, act.getValue()); - assertEquals(0, act.getVersion()); // See getter command - assertNull(act.getComment()); // See getter command - assertEquals(COOKIE_DOMAIN.toLowerCase(), act.getDomain()); - assertEquals(300000, act.getMaxAge()); - assertEquals(COOKIE_PATH, act.getPath()); - assertTrue(act.getSecure()); - assertTrue(act.isHttpOnly()); - } - - @Test - void testContainsHeader() { - when(jakartaResponse.containsHeader("Header")).thenReturn(true); - assertTrue(cut.containsHeader("Header")); - when(jakartaResponse.containsHeader("NoHeader")).thenReturn(false); - assertFalse(cut.containsHeader("NoHeader")); - } - - @Test - void testEncodeURL() { - when(jakartaResponse.encodeURL("test/test")).thenReturn("test/test"); - assertEquals("test/test", cut.encodeURL("test/test")); - } - - @Test - void testEncodeRedirectURL() { - when(jakartaResponse.encodeRedirectURL("test/test")).thenReturn("test/test"); - assertEquals("test/test", cut.encodeRedirectURL("test/test")); - } - - @Test - void testEncodeUrl() { - when(jakartaResponse.encodeURL("test/test")).thenReturn("test/test"); - assertEquals("test/test", cut.encodeUrl("test/test")); - } - - @Test - void testEncodeRedirectUrl() { - when(jakartaResponse.encodeRedirectURL("test/test")).thenReturn("test/test"); - assertEquals("test/test", cut.encodeRedirectUrl("test/test")); - } - - @Test - void testSendError() throws IOException { - cut.sendError(400); - verify(jakartaResponse).sendError(400); - cut.sendError(404, "Not Found"); - verify(jakartaResponse).sendError(404, "Not Found"); - } - - @Test - void testSendRedirect() throws IOException { - cut.sendRedirect("Redirect"); - verify(jakartaResponse).sendRedirect("Redirect"); - } - - @Test - void testSetDateHeader() throws IOException { - cut.setDateHeader(HEADER_NAME, 123); - verify(jakartaResponse).setDateHeader(HEADER_NAME, 123); - } - - @Test - void testAddDateHeader() throws IOException { - cut.addDateHeader(HEADER_NAME, 321); - verify(jakartaResponse).addDateHeader(HEADER_NAME, 321); - } - - @Test - void testSetHeader() throws IOException { - cut.setHeader(HEADER_NAME, "Hello"); - verify(jakartaResponse).setHeader(HEADER_NAME, "Hello"); - } - - @Test - void testAddHeader() throws IOException { - cut.addHeader(HEADER_NAME, "Test"); - verify(jakartaResponse).addHeader(HEADER_NAME, "Test"); - } - - @Test - void testSetIntHeader() throws IOException { - cut.setIntHeader(HEADER_NAME, 789); - verify(jakartaResponse).setIntHeader(HEADER_NAME, 789); - } - - @Test - void testAddIntHeader() throws IOException { - cut.addIntHeader(HEADER_NAME, 987); - verify(jakartaResponse).addIntHeader(HEADER_NAME, 987); - } - - @Test - void testSetStatus() throws IOException { - cut.setStatus(200); - verify(jakartaResponse).setStatus(200); - } - - @Test - void testSetStatusMessage() throws IOException { - cut.setStatus(201, "Created"); - verify(jakartaResponse).setStatus(201); - } - - @Test - void testGetStatus() { - when(jakartaResponse.getStatus()).thenReturn(258); - assertEquals(258, cut.getStatus()); - } - - @Test - void testGetHeader() { - when(jakartaResponse.getHeader(HEADER_NAME2)).thenReturn(HEADER_VALUE); - assertEquals(HEADER_VALUE, cut.getHeader(HEADER_NAME2)); - } - - @Test - void testGetHeaders() { - @SuppressWarnings("unchecked") - final Collection collection = mock(Collection.class); - when(jakartaResponse.getHeaders(HEADER_NAME)).thenReturn(collection); - assertEquals(collection, cut.getHeaders(HEADER_NAME)); - } - - @Test - void testGetHeaderNames() { - @SuppressWarnings("unchecked") - final Collection collection = mock(Collection.class); - when(jakartaResponse.getHeaderNames()).thenReturn(collection); - assertEquals(collection, cut.getHeaderNames()); - } - - private Cookie buildCookie() { - final Cookie cookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - cookie.setComment(COOKIE_COMMAND); - cookie.setDomain(COOKIE_DOMAIN); - cookie.setHttpOnly(true); - cookie.setMaxAge(300000); - cookie.setPath(COOKIE_PATH); - cookie.setSecure(true); - cookie.setVersion(3); - return cookie; - } -} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessorTest.java index 37ca02bcd..7e071a003 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessorTest.java @@ -107,8 +107,8 @@ public Expression answer(final InvocationOnMock invocation) throws Thro } }); when(cb.mod(cbResult, 2)).thenReturn(cbResult); - when(cb.equal(cbResult, 1)).thenReturn(cbPredicate); - ((JPAODataDatabaseOperations) cut).setCriteriaBuilder(cb); + when(cb.equal(cbResult, 1)).thenReturn(cbPredicate); + ((JPAODataDatabaseOperations) cut).setCriteriaBuilder(cb); final Expression act = ((JPAODataDatabaseOperations) cut).convert(operator); assertNotNull(act); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAArithmeticOperator.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorTest.java similarity index 99% rename from jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAArithmeticOperator.java rename to jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorTest.java index d0f7570f9..76a5bf49f 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAArithmeticOperator.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAArithmeticOperatorTest.java @@ -20,7 +20,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; -class TestJPAArithmeticOperator { +class JPAArithmeticOperatorTest { private CriteriaBuilder cb; private JPAOperationConverter converter; diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDogTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDogTest.java index e6f55deab..b82040004 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDogTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAFilterRestrictionsWatchDogTest.java @@ -47,7 +47,7 @@ class JPAFilterRestrictionsWatchDogTest { void setup() throws ODataJPAQueryException { annotatable = mock(JPAAnnotatable.class); annotation = mock(CsdlAnnotation.class); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); } private static Stream provideFilteringIsSupported() { @@ -68,7 +68,7 @@ void testHandleIfFilterIsSupportedAndExpressionGiven(final boolean isAnnotated, when(filterable.getValue()).thenReturn(Boolean.toString(annotatedValue)); // when(filterRequired.getValue()).thenReturn(Boolean.toString(false)); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); assertShallThrow(throwsException, filter); } @@ -88,7 +88,7 @@ void testHandleIfFilterIsRequiredAndExpressionNotGiven(final boolean isAnnotated setAnnotation(isAnnotated); when(filterRequired.getValue()).thenReturn(Boolean.toString(annotatedValue)); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); assertShallThrow(throwsException, null); } @@ -98,7 +98,7 @@ void testHandleBuildRequiredProperties() throws ODataJPAModelException, ODataJPA setAnnotation(true); requiredProperties.add(createAnnotationPath("AlternativeCode")); requiredProperties.add(createAnnotationPath("AlternativeId")); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); assertEquals(2, cut.getRequiredPropertyPath().size()); } @@ -107,7 +107,7 @@ void testHandleVisitedProperty() throws ODataJPAModelException, ODataJPAQueryExc setAnnotation(true); requiredProperties.add(createAnnotationPath("AlternativeCode")); requiredProperties.add(createAnnotationPath("AlternativeId")); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); final JPAPath firstPath = mock(JPAPath.class); when(firstPath.getAlias()).thenReturn("AlternativeCode"); @@ -134,7 +134,7 @@ void testHandleNotAllPropertiesUsed() throws ODataJPAModelException, ODataJPAQue when(filterable.getValue()).thenReturn(Boolean.toString(true)); when(filterRequired.getValue()).thenReturn(Boolean.toString(true)); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); assertThrows(ODataJPAFilterException.class, () -> cut.watch(filter)); } @@ -148,13 +148,24 @@ void testHandleNotAllPropertiesUsedButNotRequired() throws ODataJPAModelExceptio when(filterable.getValue()).thenReturn(Boolean.toString(true)); when(filterRequired.getValue()).thenReturn(Boolean.toString(false)); - cut = new JPAFilterRestrictionsWatchDog(annotatable); + cut = new JPAFilterRestrictionsWatchDog(annotatable, false); assertDoesNotThrow(() -> cut.watch(filter)); } // Filter not required but required properties + @Test + void testHandleFilterIsRequiredAndKeyRequested() throws ODataJPAModelException, ODataJPAQueryException { + + setAnnotation(true); + when(filterRequired.getValue()).thenReturn(Boolean.toString(true)); + + cut = new JPAFilterRestrictionsWatchDog(annotatable, true); + + assertDoesNotThrow(() -> cut.watch((Expression) null)); + } + private void assertShallThrow(final boolean throwsException, final Expression filter) { if (throwsException) assertThrows(ODataJPAFilterException.class, () -> cut.watch(filter)); @@ -165,12 +176,14 @@ private void assertShallThrow(final boolean throwsException, final Expression cut; + + @BeforeEach + void setUp() throws Exception { + converter = mock(JPAOperationConverter.class); + } + + @SuppressWarnings("unchecked") + @Test + void testGetCallsConverter() throws ODataApplicationException { + final JPAMemberOperator left = mock(JPAMemberOperator.class); + final List right = new ArrayList<>(); + final Expression exp = mock(Expression.class); + when(converter.convert(any(JPAInOperator.class))).thenReturn(exp); + + cut = new JPAInOperatorImpl<>(converter, left, right); + + assertEquals(exp, cut.get()); + verify(converter).convert(cut); + } + + @Test + void testGetName() { + cut = new JPAInOperatorImpl<>(converter, null, null); + assertEquals("IN", cut.getName()); + } + + @SuppressWarnings("unchecked") + @Test + void testGetLeft() throws ODataApplicationException { + final JPAMemberOperator left = mock(JPAMemberOperator.class); + final Path exp = mock(Path.class); + doReturn(exp).when(left).get(); + cut = new JPAInOperatorImpl<>(converter, left, null); + assertEquals(exp, cut.getLeft()); + } + + @Test + void testGetFixValues() throws ODataApplicationException { + final List right = new ArrayList<>(); + cut = new JPAInOperatorImpl<>(converter, null, right); + assertEquals(right, cut.getFixValues()); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPALiteralOperatorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPALiteralOperatorTest.java new file mode 100644 index 000000000..bb22c02c8 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPALiteralOperatorTest.java @@ -0,0 +1,89 @@ +package com.sap.olingo.jpa.processor.core.filter; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.stream.Stream; + +import org.apache.olingo.commons.core.edm.primitivetype.SingletonPrimitiveType; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.expression.Literal; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.reflections8.Reflections; +import org.reflections8.scanners.SubTypesScanner; +import org.reflections8.util.ConfigurationBuilder; + +class JPALiteralOperatorTest { + private static OData odata = OData.newInstance(); + + @TestFactory + Stream testNonGeometryPrimitiveTypeAreConverted() { + final ConfigurationBuilder configBuilder = new ConfigurationBuilder(); + configBuilder.setScanners(new SubTypesScanner(false)); + configBuilder.forPackages(SingletonPrimitiveType.class.getPackage().getName()); + + final Reflections reflection = new Reflections(configBuilder); + final Set> edmPrimitiveTypes = reflection.getSubTypesOf( + SingletonPrimitiveType.class); + + return edmPrimitiveTypes + .stream() + .filter(type -> type.getSuperclass() == SingletonPrimitiveType.class) + .map(JPALiteralOperatorTest::createEdmPrimitiveType) + .filter(i -> i != null) + .map(JPALiteralOperatorTest::createLiteralOperator) + .map(operator -> dynamicTest(operator.getLiteral().getType().getName(), () -> assertTypeConversion(operator))); + } + + private void assertTypeConversion(final JPALiteralOperator operator) throws ODataApplicationException { + final Object act = operator.get(); + assertNotNull(act); + assertFalse(act.toString().contains("'")); + } + + private static SingletonPrimitiveType createEdmPrimitiveType( + final Class typeClass) { + try { + final Method method = typeClass.getMethod("getInstance"); + return (SingletonPrimitiveType) method.invoke(null); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException + | SecurityException e) { + return null; + } + } + + private static JPALiteralOperator createLiteralOperator(final SingletonPrimitiveType typeInstance) { + final Literal literal = mock(Literal.class); + when(literal.getType()).thenReturn(typeInstance); + when(literal.getText()).thenReturn(determineLiteral(typeInstance)); + + return new JPALiteralOperator(odata, literal); + } + + private static String determineLiteral(final SingletonPrimitiveType typeInstance) { + switch (typeInstance.getName()) { + case "Guid": + return "819a3e3b-837e-4ecb-a600-654ef7b5aace"; + case "Date": + return "2021-10-01"; + case "Boolean": + return "true"; + case "TimeOfDay": + return "10:00:12.10"; + case "DateTimeOffset": + return "2021-10-01T10:00:12Z"; + case "Duration": + return "P2DT12H30M5S"; + default: + return "123"; + } + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAOperationConverter.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverterTest.java similarity index 94% rename from jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAOperationConverter.java rename to jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverterTest.java index ce8221710..3163f54d1 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAOperationConverter.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAOperationConverterTest.java @@ -16,8 +16,9 @@ import org.junit.jupiter.api.Test; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAFilterException; -class TestJPAOperationConverter { +class JPAOperationConverterTest { private CriteriaBuilder cb; private Expression expressionLeft; @@ -166,6 +167,14 @@ void testUnknownOperation_CallExtension() throws ODataApplicationException { assertEquals(HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), act.getStatusCode()); } + + @SuppressWarnings("unchecked") + @Test + void testConvertInOperatorNotInThrowsException() { + final JPAInOperator jpaOperator = mock(JPAInOperator.class); + when(jpaOperator.getOperator()).thenReturn(BinaryOperatorKind.HAS); + assertThrows(ODataJPAFilterException.class, () -> cut.convert(jpaOperator)); + } } //case MOD: \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitorTest.java index a50c5657d..aef7c3fbe 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/JPAVisitorTest.java @@ -1,6 +1,7 @@ package com.sap.olingo.jpa.processor.core.filter; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doReturn; @@ -8,7 +9,6 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import jakarta.persistence.criteria.CriteriaBuilder; @@ -88,9 +88,8 @@ void testVisitLambdaReferenceThrowsException() { } @Test - void testVisitBinaryOperatorThrowsException() { - assertThrows(ODataJPAFilterException.class, () -> cut.visitBinaryOperator(BinaryOperatorKind.IN, null, - Collections.emptyList())); + void testVisitTypeLiteralThrowsException() { + assertThrows(ODataJPAFilterException.class, () -> cut.visitTypeLiteral(null)); } @Test @@ -119,4 +118,16 @@ void createFunctionOperation() throws ExpressionVisitException, ODataApplication assertTrue(cut.visitMember(member) instanceof JPADBFunctionOperator); } + @Test + void testVisitBinaryOperatorWithListThrowsExceptionNotIn() { + final List right = new ArrayList<>(); + assertThrows(ODataJPAFilterException.class, () -> cut.visitBinaryOperator(BinaryOperatorKind.HAS, null, right)); + } + + @Test + void testVisitBinaryOperatorWithListAllowsIn() throws ExpressionVisitException, ODataApplicationException { + final JPAOperator left = mock(JPAOperator.class); + final List right = new ArrayList<>(); + assertNotNull(cut.visitBinaryOperator(BinaryOperatorKind.IN, left, right)); + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java index 6f5a55546..7f0a0ee44 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java @@ -1,10 +1,12 @@ package com.sap.olingo.jpa.processor.core.filter; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.sql.DataSource; @@ -17,6 +19,9 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import com.fasterxml.jackson.databind.node.ArrayNode; import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; @@ -48,14 +53,6 @@ public static void tearDownClass() throws ODataJPAModelException { DropDensityFunction(); } - @Test - void testFilterOnFunction() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=$it/Area,Population=$it/Population) gt 1"); - helper.assertStatus(200); - } - @Test void testFilterOnFunctionAndProperty() throws IOException, ODataException { @@ -69,47 +66,39 @@ void testFilterOnFunctionAndProperty() throws IOException, ODataException { } @Test - void testFilterOnFunctionAndMultiply() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area,Population=Population) mul 1000000 gt 100"); - helper.assertStatus(200); - - final ArrayNode orgs = helper.getValues(); - assertEquals(59, orgs.size()); - } - - @Test - void testFilterOnFunctionWithFixedValue() throws IOException, ODataException { + void testFilterOnFunction() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=13079087,Population=$it/Population) mul 1000000 gt 1000"); + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=$it/Area,Population=$it/Population) gt 1"); helper.assertStatus(200); - - final ArrayNode orgs = helper.getValues(); - assertEquals(29, orgs.size()); } - @Test - void testFilterOnFunctionComputedValue() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area div 1000000,Population=Population) gt 1000"); - helper.assertStatus(200); - - final ArrayNode orgs = helper.getValues(); - assertEquals(7, orgs.size()); + private static Stream provideFunctionQueries() { + return Stream.of( + arguments("FunctionAndMultiply", + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area,Population=Population) mul 1000000 gt 100", + 59), + arguments("FunctionWithFixedValue", + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=13079087,Population=$it/Population) mul 1000000 gt 1000", + 29), + arguments("FunctionComputedValue", + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area div 1000000,Population=Population) gt 1000", + 7), + arguments("FunctionMixParamOrder", + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Population=Population,Area=Area) mul 1000000 gt 1000", + 7)); } - @Test - void testFilterOnFunctionMixParamOrder() throws IOException, ODataException { + @ParameterizedTest + @MethodSource("provideFunctionQueries") + void testFilterOnFunctionAndMultiply(final String text, final String queryString, final int numberOfResults) + throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Population=Population,Area=Area) mul 1000000 gt 1000"); + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, queryString); helper.assertStatus(200); - final ArrayNode orgs = helper.getValues(); - assertEquals(7, orgs.size()); + final ArrayNode divisions = helper.getValues(); + assertEquals(numberOfResults, divisions.size(), text); } private static void CreateDensityFunction() { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAQueryWhereClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAQueryWhereClause.java index e52e25481..545fb182d 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAQueryWhereClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPAQueryWhereClause.java @@ -4,6 +4,7 @@ import static org.apache.olingo.commons.api.http.HttpStatusCode.OK; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -22,6 +23,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCapabilitiesAnnotationsProvider; import com.sap.olingo.jpa.processor.core.api.JPAClaimsPair; import com.sap.olingo.jpa.processor.core.api.JPAODataClaimsProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataGroupsProvider; @@ -104,9 +106,10 @@ void testFilterOneEqualsInvert() throws IOException, ODataException { static Stream getFilterQuery() { return Stream.of( + // Simple filter arguments("OneNotEqual", "Organizations?$filter=ID ne '3'", 9), - arguments("OneGreaterEquals", "Organizations?$filter=ID ge '5'", 5), // '10' is smaller than '5' when comparing - // strings! + // '10' is smaller than '5' when comparing strings! + arguments("OneGreaterEquals", "Organizations?$filter=ID ge '5'", 5), arguments("OneLowerThanTwo", "AdministrativeDivisions?$filter=DivisionCode lt CountryCode", 244), arguments("OneGreaterThan", "Organizations?$filter=ID gt '5'", 4), arguments("OneLowerThan", "Organizations?$filter=ID lt '5'", 5), @@ -126,6 +129,7 @@ static Stream getFilterQuery() { arguments("Contains", "AdministrativeDivisions?$filter=contains(CodeID,'166')", 110), arguments("Endswith", "AdministrativeDivisions?$filter=endswith(CodeID,'166-1')", 4), arguments("Startswith", "AdministrativeDivisions?$filter=startswith(DivisionCode,'DE-')", 16), + arguments("Not Startswith", "AdministrativeDivisions?$filter=not startswith(DivisionCode,'BE')", 176), arguments("IndexOf", "AdministrativeDivisions?$filter=indexof(DivisionCode,'3') eq 4", 7), arguments("SubstringStartIndex", "AdministrativeDivisionDescriptions?$filter=Language eq 'de' and substring(Name,6) eq 'Dakota'", 2), @@ -143,6 +147,13 @@ static Stream getFilterQuery() { arguments("OneHas", "Persons?$filter=AccessRights has com.sap.olingo.jpa.AccessRights'READ'", 1), arguments("OnNull", "AdministrativeDivisions?$filter=CodePublisher eq 'ISO' and ParentCodeID eq null", 4), arguments("OneEqualsTwoProperties", "AdministrativeDivisions?$filter=DivisionCode eq CountryCode", 4), + arguments("SubstringStartEndIndexToLower", + "AdministrativeDivisionDescriptions?$filter=Language eq 'de' and tolower(substring(Name,0,5)) eq 'north'", + 2), + // IN expression + arguments("Simple IN", "AdministrativeDivisions?$filter=ParentDivisionCode in ('BE1', 'BE2')", 6), + arguments("Simple NOT IN", "AdministrativeDivisions?$filter=not (ParentDivisionCode in ('BE1', 'BE2'))", 219), + // Filter to many associations arguments("NavigationPropertyToManyValueAnyNoRestriction", "Organizations?$select=ID&$filter=Roles/any()", 4), arguments("NavigationPropertyToManyValueAnyMultiParameter", "Organizations?$select=ID&$filter=Roles/any(d:d/RoleCategory eq 'A' and d/BusinessPartnerID eq '1')", 1), @@ -152,8 +163,21 @@ static Stream getFilterQuery() { "Organizations?$select=ID&$filter=Roles/any(d:d/RoleCategory eq 'A')", 3), arguments("NavigationPropertyToManyValueAll", "Organizations?$select=ID&$filter=Roles/all(d:d/RoleCategory eq 'A')", 1), + arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectAll", + "Organizations?$filter=Address/RegionName eq 'Kalifornien'", 3), + arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectId", + "Organizations?$filter=Address/RegionName eq 'Kalifornien'&$select=ID", 3), + arguments("NavigationPropertyDescriptionToOneValueViaComplexTypeWSubselect1", + "Organizations?$filter=AdministrativeInformation/Created/User/LocationName eq 'Schweiz'", 1), + arguments("NavigationPropertyDescriptionToOneValueViaComplexTypeWSubselect2", + "Organizations?$filter=AdministrativeInformation/Created/User/LocationName eq 'Schweiz'&$select=ID", 1), + // Filter collection property + arguments("CountCollectionPropertyOne", + "Persons?$select=ID&$filter=InhouseAddress/any(d:d/Building eq '7')", 1), // https://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398301 // Example 43: return all Categories with less than 10 products + // Filter to many associations count + arguments("NoChildren", "AdministrativeDivisions?$filter=Children/$count eq 0", 220), arguments("CountNavigationPropertyTwo", "Organizations?$select=ID&$filter=Roles/$count eq 2", 1), arguments("CountNavigationPropertyZero", "Organizations?$select=ID&$filter=Roles/$count eq 0", 6), arguments("CountNavigationPropertyMultipleHops", @@ -163,7 +187,10 @@ static Stream getFilterQuery() { arguments("CountNavigationPropertyMultipleHopsNavigations zero", "AdministrativeDivisions?$filter=Parent/Children/$count eq 0", 0), arguments("CountNavigationPropertyJoinTable not zero", "JoinSources?$filter=OneToMany/$count eq 2", 1), + // Filter collection property count arguments("CountCollectionPropertyOne", "Organizations?$select=ID&$filter=Comment/$count ge 1", 2), + arguments("CountCollectionPropertyTwoJoinOne", "CollectionWithTwoKeys?$filter=Nested/$count eq 1", 1), + arguments("CountCollectionPropertyTwoJoinZero", "CollectionWithTwoKeys?$filter=Nested/$count eq 0", 3), // To one association null arguments("NavigationPropertyIsNull", "AssociationOneToOneSources?$format=json&$filter=ColumnTarget eq null", 1), @@ -174,29 +201,19 @@ static Stream getFilterQuery() { arguments("NavigationPropertyMixCountAndNull", "AdministrativeDivisions?$filter=Parent/Children/$count eq 2 and Parent/Parent/Parent eq null", 2), arguments("NavigationPropertyIsNullJoinTable", "JoinTargets?$filter=ManyToOne ne null", 2), - + // Filter to one association arguments("NavigationPropertyToOneValue", "AdministrativeDivisions?$filter=Parent/CodeID eq 'NUTS1'", 11), arguments("NavigationPropertyToOneValueAndEquals", "AdministrativeDivisions?$filter=Parent/CodeID eq 'NUTS1' and DivisionCode eq 'BE34'", 1), arguments("NavigationPropertyToOneValueTwoHops", "AdministrativeDivisions?$filter=Parent/Parent/CodeID eq 'NUTS1' and DivisionCode eq 'BE212'", 1), arguments("NavigationPropertyToOneValueViaComplexType", - "Organizations?$filter=AdministrativeInformation/Created/User/LastName eq 'Mustermann'", 8), - arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectAll", - "Organizations?$filter=Address/RegionName eq 'Kalifornien'", 3), - arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectId", - "Organizations?$filter=Address/RegionName eq 'Kalifornien'&$select=ID", 3), - arguments("NavigationPropertyDescriptionToOneValueViaComplexTypeWSubselect1", - "Organizations?$filter=AdministrativeInformation/Created/User/LocationName eq 'Schweiz'", 1), - arguments("NavigationPropertyDescriptionToOneValueViaComplexTypeWSubselect2", - "Organizations?$filter=AdministrativeInformation/Created/User/LocationName eq 'Schweiz'&$select=ID", 1), - arguments("SubstringStartEndIndexToLower", - "AdministrativeDivisionDescriptions?$filter=Language eq 'de' and tolower(substring(Name,0,5)) eq 'north'", - 2)); + "Organizations?$filter=AdministrativeInformation/Created/User/LastName eq 'Mustermann'", 8)); } @ParameterizedTest @MethodSource("getFilterQuery") + // @Tag(Assertions.CB_ONLY_TEST) void testFilterOne(final String text, final String queryString, final int numberOfResults) throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, queryString); @@ -638,7 +655,7 @@ void testFilterCollectionOnPropertyWithNavigation() throws IOException, ODataExc } @Test - void testFilterCollectionPropertyWithOutNavigationThrowsError() throws IOException, ODataException { + void testFilterCollectionPropertyWithoutNavigationThrowsError() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Persons?$select=ID&$filter=InhouseAddress/TaskID eq 'DEV'"); @@ -646,6 +663,14 @@ void testFilterCollectionPropertyWithOutNavigationThrowsError() throws IOExcepti helper.assertStatus(400); // The URI is malformed } + @Test + void testFilterCollectionPropertyWithoutEntityTypeThrowsError() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionWithTwoKeys?$filter=Nested/any(d:d/Inner/Figure1 eq 1)"); + helper.assertStatus(400); + } + @Test void testFilterOnGroupedSimplePropertyWithoutGroupsReturnsForbidden() throws IOException, ODataException { @@ -732,4 +757,66 @@ void testNavigationPropertyToManyValueAnyViaJoinTable() throws IOException, ODat "BusinessPartnerProtecteds?$filter=RolesJoinProtected/all(d:d/RoleCategory eq 'B')", provided); helper.assertStatus(200); } + + @Test + void testFilterRestrictionByAnnotation() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AnnotationsParents?$top=1", new JavaBasedCapabilitiesAnnotationsProvider()); + helper.assertStatus(400); + } + + @Test + void testStartsWithCompleteness() throws IOException, ODataException { + IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count"); + final var all = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=not startswith(DivisionCode,'BE')"); + final var notStarts = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=startswith(DivisionCode,'BE')"); + final var starts = helper.getSingleValue().asInt(); + + assertEquals(all, notStarts + starts); + } + + @Test + void testStartsWithCompletenessContainingNull() throws IOException, ODataException { + IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count"); + final var all = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=not startswith(ParentDivisionCode,'BE')"); + final var notStarts = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=startswith(ParentDivisionCode,'BE')"); + final var starts = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=ParentDivisionCode eq null"); + final var nullValues = helper.getSingleValue().asInt(); + assertNotEquals(0, nullValues); + assertEquals(all, notStarts + starts + nullValues); + } + + @Test + void testContainsCompleteness() throws IOException, ODataException { + IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count"); + final var all = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=not contains(DivisionCode,'14')"); + final var notStarts = helper.getSingleValue().asInt(); + + helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions/$count?$filter=contains(DivisionCode,'14')"); + final var starts = helper.getSingleValue().asInt(); + + assertEquals(all, notStarts + starts); + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebuggerTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebuggerTest.java index 58d62c738..50e503a0f 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebuggerTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebuggerTest.java @@ -67,20 +67,20 @@ void teardown() { } @Test - void testMeassumentCreated() throws Exception { + void testMeasurementCreated() throws Exception { try (JPARuntimeMeasurement measurement = cutDebugOn.newMeasurement(cutDebugOn, "firstTest")) {} assertFalse(cutDebugOn.getRuntimeInformation().isEmpty()); } @Test - void testNoMeassumentDebugFalls() throws Exception { + void testNoMeasurementDebugFalls() throws Exception { cutDebugOn = new JPACoreDebugger(false); try (JPARuntimeMeasurement measurement = cutDebugOn.newMeasurement(cutDebugOn, "firstTest")) {} assertTrue(cutDebugOn.getRuntimeInformation().isEmpty()); } @Test - void testMeassumentCreateMeassument() throws Exception { + void testMeasurementCreateMeasurement() throws Exception { try (JPARuntimeMeasurement measurement = cutDebugOn.newMeasurement(cutDebugOn, "firstTest")) { TimeUnit.MILLISECONDS.sleep(100); } @@ -103,6 +103,18 @@ void testRuntimeMeasurementEmptyAfterStopWhenOff() throws InterruptedException { assertTrue(StringUtils.isNotEmpty(act)); } + @SuppressWarnings("resource") + @Test + void testMemoryMeasurement() throws InterruptedException { + final JPARuntimeMeasurement measurement; + try (final JPARuntimeMeasurement m = cutDebugOn.newMeasurement(cutDebugOn, "firstTest")) { + @SuppressWarnings("unused") + final String[] dummy = new String[100]; + measurement = m; + } + assertTrue(measurement.getMemoryConsumption() > 0); + } + @Test void testDebugLogWithTread() { System.setErr(printOut); @@ -145,6 +157,18 @@ void testTraceLogText() { assertTrue(act.contains("Hallo")); } + @SuppressWarnings("resource") + @Test + void testMemoryConsumption() throws InterruptedException { + final JPARuntimeMeasurement act; + try (JPARuntimeMeasurement measurement = cutDebugOn.newMeasurement(cutDebugOn, "firstTest")) { + act = measurement; + } finally { + + } + assertTrue(act.getMemoryConsumption() < 10); + } + private static class LogHandler extends Handler { private List cache = new ArrayList<>(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAEmptyDebuggerTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAEmptyDebuggerTest.java index 58f192f0c..b8b64c0e1 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAEmptyDebuggerTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAEmptyDebuggerTest.java @@ -1,5 +1,6 @@ package com.sap.olingo.jpa.processor.core.processor; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; @@ -19,7 +20,7 @@ void setup() { @Test void testMeasurementCreated() throws Exception { try (JPARuntimeMeasurement measurement = cut.newMeasurement(cut, "firstTest")) { - + assertEquals(0L, measurement.getMemoryConsumption()); } assertTrue(cut.getRuntimeInformation().isEmpty()); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContextTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContextTest.java index 78c77812b..d1318aa78 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContextTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataInternalRequestContextTest.java @@ -41,6 +41,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataDefaultTransactionFactory; import com.sap.olingo.jpa.processor.core.api.JPAODataGroupProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; +import com.sap.olingo.jpa.processor.core.api.JPAODataQueryDirectives; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAODataSessionContextAccess; @@ -75,6 +76,7 @@ class JPAODataInternalRequestContextTest { private JPAODataDatabaseProcessor dbProcessor; private JPAEdmProvider edmProvider; private JPAODataDatabaseOperations operationConverter; + private JPAODataQueryDirectives queryDirectives; @BeforeEach void setup() throws ODataException { @@ -97,6 +99,7 @@ void setup() throws ODataException { dbProcessor = mock(JPAODataDatabaseProcessor.class); edmProvider = mock(JPAEdmProvider.class); operationConverter = mock(JPAODataDatabaseOperations.class); + queryDirectives = new JPAODataQueryDirectives.JPAODataQueryDirectivesImpl(0); page = new JPAODataPage(uriInfo, 0, 0, claims); when(contextAccess.getTransactionFactory()).thenReturn(transactionFactory); @@ -106,6 +109,7 @@ void setup() throws ODataException { when(contextAccess.getGroupsProvider()).thenReturn(groups); when(contextAccess.getProvidedLocale()).thenReturn(locales); when(contextAccess.getDebugger()).thenReturn(debugger); + when(contextAccess.getQueryDirectives()).thenReturn(queryDirectives); when(requestContext.getClaimsProvider()).thenReturn(claims); when(requestContext.getCUDRequestHandler()).thenReturn(cudHandler); @@ -120,6 +124,7 @@ void setup() throws ODataException { when(sessionContext.getDatabaseProcessor()).thenReturn(dbProcessor); when(sessionContext.getEdmProvider()).thenReturn(edmProvider); when(sessionContext.getOperationConverter()).thenReturn(operationConverter); + when(sessionContext.getQueryDirectives()).thenReturn(queryDirectives); } @Test @@ -139,6 +144,7 @@ void testCreateFromContextAccessWithDebugger() throws ODataJPAIllegalAccessExcep assertEquals(locales, cut.getProvidedLocale()); assertEquals(page, cut.getPage()); assertEquals(uriInfo, cut.getUriInfo()); + assertEquals(queryDirectives, cut.getQueryDirectives()); } @Test @@ -157,6 +163,7 @@ void testCreateFromContextUriAccessWithDebugger() throws ODataJPAIllegalAccessEx assertTrue(cut.getDebugger() instanceof JPAEmptyDebugger); assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); + assertEquals(queryDirectives, cut.getQueryDirectives()); } @Test @@ -176,6 +183,7 @@ void testCreateFromContextUriContextAccess() throws ODataJPAIllegalAccessExcepti assertTrue(cut.getDebugger() instanceof JPAEmptyDebugger); assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); + assertEquals(queryDirectives, cut.getQueryDirectives()); } @Test @@ -194,6 +202,7 @@ void testCreateFromContextUriContextAccessHeader() throws ODataJPAIllegalAccessE assertTrue(cut.getDebugger() instanceof JPAEmptyDebugger); assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); + assertEquals(queryDirectives, cut.getQueryDirectives()); } @Test @@ -209,6 +218,7 @@ void testCreateFromRequestContextWithDebugSupport() throws ODataJPAIllegalAccess assertEquals(transactionFactory, cut.getTransactionFactory()); assertEquals(locales, cut.getProvidedLocale()); assertEquals(customParameter, cut.getRequestParameter()); + assertEquals(queryDirectives, cut.getQueryDirectives()); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQueryTest.java index 133633b8c..1d92f7795 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractSubQueryTest.java @@ -1,6 +1,9 @@ package com.sap.olingo.jpa.processor.core.query; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -27,14 +30,17 @@ import org.junit.jupiter.params.provider.MethodSource; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorSubquery; import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerProtected; +import com.sap.olingo.jpa.processor.core.testmodel.Organization; import com.sap.olingo.jpa.processor.core.util.TestBase; import com.sap.olingo.jpa.processor.core.util.TestHelper; @@ -177,6 +183,20 @@ void testCreateSelectClauseAggregationForExpandInThrowsExceptionStandard() { () -> cut.createSelectClauseAggregation(subQuery, from, conditionItems, true)); } + @Test + void isCollectionPropertyReturnsFalseForNavigation() { + assertFalse(cut.toCollectionProperty()); + } + + @Test + void isCollectionPropertyReturnsTrueForCollection() throws ODataJPAModelException { + final var attribute = helper.getJPAAttribute(Organization.class, "comment") + .map(a -> (JPACollectionAttribute) a) + .orElseGet(() -> fail()); + cut = new JPASubQuery(odata, null, null, em, parent, from, attribute.asAssociation()); + assertTrue(cut.toCollectionProperty()); + } + private JPAPath createJpaPath(final String attribute) { final JPAPath jpaPath = mock(JPAPath.class); final JPAElement element = mock(JPAElement.class); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQueryTest.java new file mode 100644 index 000000000..5aa08b696 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQueryTest.java @@ -0,0 +1,90 @@ +package com.sap.olingo.jpa.processor.core.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Tuple; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.ODataApplicationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; +import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; +import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; +import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; +import com.sap.olingo.jpa.processor.core.processor.JPAEmptyDebugger; +import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartner; +import com.sap.olingo.jpa.processor.core.util.TestBase; +import com.sap.olingo.jpa.processor.core.util.TestHelper; +import com.sap.olingo.jpa.processor.core.util.TestQueryBase; + +class JPAJoinCountQueryTest extends TestQueryBase { + private CriteriaBuilder cb; + @SuppressWarnings("rawtypes") + private CriteriaQuery cq; + private EntityManager em; + private JPAODataRequestContextAccess localContext; + private JPAHttpHeaderMap headerMap; + private JPARequestParameterMap parameterMap; + + @SuppressWarnings("unchecked") + @Override + @BeforeEach + public void setup() throws ODataException, ODataJPAIllegalAccessException { + em = mock(EntityManager.class); + cb = spy(emf.getCriteriaBuilder()); + cq = mock(CriteriaQuery.class); + localContext = mock(JPAODataRequestContextAccess.class); + headerMap = mock(JPAHttpHeaderMap.class); + parameterMap = mock(JPARequestParameterMap.class); + + buildUriInfo("BusinessPartners", "BusinessPartner"); + helper = new TestHelper(emf, PUNIT_NAME); + nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); + jpaEntityType = helper.getJPAEntityType(BusinessPartner.class); + createHeaders(); + + when(localContext.getUriInfo()).thenReturn(uriInfo); + when(localContext.getEntityManager()).thenReturn(em); + when(localContext.getEdmProvider()).thenReturn(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages)); + when(localContext.getDebugger()).thenReturn(new JPAEmptyDebugger()); + when(localContext.getOperationConverter()).thenReturn(new JPADefaultDatabaseProcessor()); + when(localContext.getHeader()).thenReturn(headerMap); + when(localContext.getRequestParameter()).thenReturn(parameterMap); + when(em.getCriteriaBuilder()).thenReturn(cb); + when(cb.createQuery(any())).thenReturn(cq); + when(cb.createTupleQuery()).thenReturn(cq); + + cut = new JPAJoinCountQuery(null, localContext); + } + + @SuppressWarnings("unchecked") + @Test + void testCountResultsIsLong() throws ODataApplicationException { + final TypedQuery typedQuery = mock(TypedQuery.class); + final Expression countExpression = mock(Expression.class); + final var result = mock(Tuple.class); + when(cq.multiselect(any(), any())).thenReturn(cq); + doReturn(countExpression).when(cb).countDistinct(any()); + doReturn(countExpression).when(cb).count(any()); + when(em.createQuery(any(CriteriaQuery.class))).thenReturn(typedQuery); + when(result.get(0)).thenReturn(5L); + when(typedQuery.getSingleResult()).thenReturn(result); + final var act = ((JPAJoinCountQuery) cut).countResults(); + assertEquals(5L, act); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQueryTest.java index df9cc1d8a..612a2f1af 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQueryTest.java @@ -1,22 +1,16 @@ package com.sap.olingo.jpa.processor.core.query; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Expression; import org.apache.olingo.commons.api.ex.ODataException; -import org.apache.olingo.server.api.ODataApplicationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -71,40 +65,12 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { cut = new JPAJoinQuery(null, localContext); } - @SuppressWarnings("unchecked") - @Test - void testCountResultsIsInteger() throws ODataApplicationException { - final TypedQuery typedQuery = mock(TypedQuery.class); - final Expression countExpression = mock(Expression.class); - when(cb.createQuery(any())).thenReturn(cq); - doReturn(countExpression).when(cb).countDistinct(any()); - doReturn(countExpression).when(cb).count(any()); - when(em.createQuery(any(CriteriaQuery.class))).thenReturn(typedQuery); - when(typedQuery.getSingleResult()).thenReturn(5); - final Long act = ((JPAJoinQuery) cut).countResults(); - assertEquals(5L, act); - } - - @SuppressWarnings("unchecked") - @Test - void testCountResultsIsLong() throws ODataApplicationException { - final TypedQuery typedQuery = mock(TypedQuery.class); - final Expression countExpression = mock(Expression.class); - when(cb.createQuery(any())).thenReturn(cq); - doReturn(countExpression).when(cb).countDistinct(any()); - doReturn(countExpression).when(cb).count(any()); - when(em.createQuery(any(CriteriaQuery.class))).thenReturn(typedQuery); - when(typedQuery.getSingleResult()).thenReturn(5L); - final Long act = ((JPAJoinQuery) cut).countResults(); - assertEquals(5L, act); - } - @Test void testDerivedTypeRequestedTrueTwoLevels() { - final JPAStructuredType rootType = mock(JPAStructuredType.class); - final JPAStructuredType baseType = mock(JPAStructuredType.class); - final JPAStructuredType potentialSubType = mock(JPAStructuredType.class); + final var rootType = mock(JPAStructuredType.class); + final var baseType = mock(JPAStructuredType.class); + final var potentialSubType = mock(JPAStructuredType.class); when(potentialSubType.getBaseType()).thenReturn(baseType); when(baseType.getBaseType()).thenReturn(rootType); @@ -115,8 +81,8 @@ void testDerivedTypeRequestedTrueTwoLevels() { @Test void testDerivedTypeRequestedTrue() { - final JPAStructuredType baseType = mock(JPAStructuredType.class); - final JPAStructuredType potentialSubType = mock(JPAStructuredType.class); + final var baseType = mock(JPAStructuredType.class); + final var potentialSubType = mock(JPAStructuredType.class); when(potentialSubType.getBaseType()).thenReturn(baseType); @@ -126,8 +92,8 @@ void testDerivedTypeRequestedTrue() { @Test void testDerivedTypeRequestedFalseNoBaseType() { - final JPAStructuredType baseType = mock(JPAStructuredType.class); - final JPAStructuredType potentialSubType = mock(JPAStructuredType.class); + final var baseType = mock(JPAStructuredType.class); + final var potentialSubType = mock(JPAStructuredType.class); when(potentialSubType.getBaseType()).thenReturn(null); @@ -137,9 +103,9 @@ void testDerivedTypeRequestedFalseNoBaseType() { @Test void testDerivedTypeRequestedFalseOtherBaseType() { - final JPAStructuredType baseType = mock(JPAStructuredType.class); - final JPAStructuredType baseType2 = mock(JPAStructuredType.class); - final JPAStructuredType potentialSubType = mock(JPAStructuredType.class); + final var baseType = mock(JPAStructuredType.class); + final var baseType2 = mock(JPAStructuredType.class); + final var potentialSubType = mock(JPAStructuredType.class); when(potentialSubType.getBaseType()).thenReturn(baseType2); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQueryTest.java index b9482b4a3..ddf18baef 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationCountQueryTest.java @@ -114,6 +114,7 @@ protected void createEdmEntityType(final Class clazz) throws ODataJPAModelExc @Test void testCutExists() throws ODataApplicationException, ODataJPAModelException { + association = helper.getJPAAssociationPath(BusinessPartnerProtected.class, "RolesProtected"); createEdmEntityType(BusinessPartnerRoleProtected.class); cut = createCut(); assertNotNull(cut); @@ -123,7 +124,7 @@ void testCutExists() throws ODataApplicationException, ODataJPAModelException { @Test void testGetSubQueryThrowsExceptionWhenChildQueryProvided() throws ODataApplicationException, ODataJPAModelException { - + association = helper.getJPAAssociationPath(BusinessPartnerProtected.class, "RolesProtected"); createEdmEntityType(BusinessPartnerRoleProtected.class); cut = createCut(); assertThrows(ODataJPAQueryException.class, () -> cut.getSubQuery(subQuery, null, Collections.emptyList())); @@ -236,6 +237,7 @@ void testQueryOneJoinColumnsWithClaims() throws ODataApplicationException, OData @Test void testGetLeftOnEarlyAccess() throws ODataApplicationException, ODataJPAIllegalAccessException, ODataJPAModelException { + association = helper.getJPAAssociationPath(BusinessPartnerProtected.class, "RolesProtected"); createEdmEntityType(BusinessPartnerRoleProtected.class); cut = createCut(); assertLeftEarlyAccess(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilderTest.java index 3e6fcd8f7..985b92626 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilderTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryBuilderTest.java @@ -41,8 +41,10 @@ import org.junit.jupiter.params.provider.MethodSource; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder; @@ -119,6 +121,7 @@ void setup() throws ODataJPAModelException { @Test void testCreatesFilterQuery() throws ODataApplicationException, ODataJPAModelException { final OData o = OData.newInstance(); + buildAssoziationPath(); cut.setOdata(o) .setServiceDocument(sd) @@ -154,6 +157,8 @@ private static Stream provideCountExpressions() { @MethodSource("provideCountExpressions") void testCreatesCountQuery(final VisitableExpression exp) throws ODataApplicationException, ODataJPAModelException { final OData o = OData.newInstance(); + final var assoziationPath = buildAssoziationPath(); + when(navigationInfo.getAssociationPath()).thenReturn(assoziationPath); cut.setOdata(o) .setServiceDocument(sd) @@ -189,6 +194,8 @@ private static Stream provideNullExpressions() { @MethodSource("provideNullExpressions") void testCreatesNullQuery(final VisitableExpression exp) throws ODataApplicationException, ODataJPAModelException { final OData o = OData.newInstance(); + final var assoziationPath = buildAssoziationPath(); + when(navigationInfo.getAssociationPath()).thenReturn(assoziationPath); cut.setOdata(o) .setServiceDocument(sd) @@ -234,9 +241,11 @@ void testAsExistsQueryForProcessorCriteriaBuilderNotCount() throws ODataJPAFilte void testAsInQueryTrueForCriteriaBuilderAssociationOneAttribute() throws ODataJPAModelException, ODataJPAFilterException, ODataJPAQueryException { + final JPAPath leftPath = mock(JPAPath.class); final JPAAssociationPath associationPath = mock(JPAAssociationPath.class); final JPAOnConditionItem onCondition = mock(JPAOnConditionItem.class); when(associationPath.getJoinColumnsList()).thenReturn(Collections.singletonList(onCondition)); + when(associationPath.getLeftColumnsList()).thenReturn(Collections.singletonList(leftPath)); when(navigationInfo.getAssociationPath()).thenReturn(associationPath); cut.setNavigationInfo(navigationInfo); cut.setExpression(buildCountFromCountExpression()); @@ -246,11 +255,8 @@ void testAsInQueryTrueForCriteriaBuilderAssociationOneAttribute() throws ODataJP @Test void testAsInQueryFalseForCriteriaBuilderAssociationTwoAttribute() throws ODataJPAModelException, ODataJPAFilterException, ODataJPAQueryException { - - final JPAAssociationPath associationPath = mock(JPAAssociationPath.class); - final JPAOnConditionItem onCondition = mock(JPAOnConditionItem.class); - when(associationPath.getJoinColumnsList()).thenReturn(Arrays.asList(onCondition, onCondition)); - when(navigationInfo.getAssociationPath()).thenReturn(associationPath); + final var assoziationPath = buildAssoziationPath(); + when(navigationInfo.getAssociationPath()).thenReturn(assoziationPath); cut.setNavigationInfo(navigationInfo); cut.setExpression(buildCountFromCountExpression()); assertFalse(cut.asInQuery()); @@ -260,12 +266,21 @@ void testAsInQueryFalseForCriteriaBuilderAssociationTwoAttribute() throws ODataJ void testAsInQueryRethrowsException() throws ODataJPAFilterException, ODataJPAModelException, ODataJPAQueryException { final JPAAssociationPath associationPath = mock(JPAAssociationPath.class); + when(associationPath.getLeftColumnsList()).thenThrow(ODataJPAModelException.class); when(associationPath.getJoinColumnsList()).thenThrow(ODataJPAModelException.class); when(navigationInfo.getAssociationPath()).thenReturn(associationPath); cut.setNavigationInfo(navigationInfo); assertThrows(ODataJPAFilterException.class, () -> cut.asInQuery()); } + private JPAAssociationPath buildAssoziationPath() throws ODataJPAModelException { + final JPAOnConditionItem onCondition = mock(JPAOnConditionItem.class); + final JPAElement pathItem = mock(JPAElement.class); + when(association.getJoinColumnsList()).thenReturn(Arrays.asList(onCondition, onCondition)); + when(association.getPath()).thenReturn(Arrays.asList(pathItem)); + return association; + } + private static VisitableExpression buildCountFromCountExpression() { final UriInfoResource uriInfo = mock(UriInfoResource.class); final JPACountExpression exp = mock(JPACountExpression.class); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java index 71730632d..c9bce4f88 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java @@ -860,7 +860,7 @@ void testExpandViaJoinTable1LevelNoMappedHidden() throws IOException, ODataExcep final ObjectNode organization = helper.getValue(); final ObjectNode err = (ObjectNode) organization.get("error"); final String msg = err.get("message").asText(); - assertTrue(msg.contains("JoinHiddenRelation")); + assertTrue(msg.contains("JoinHiddenRelation"), msg); } else { helper.assertStatus(200); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryCollection.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryCollection.java index 3f1f6a11b..5a651e53d 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryCollection.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryCollection.java @@ -78,7 +78,7 @@ void testSelectAllWithPrimitiveCollection() throws IOException, ODataException { void testSelectWithNestedComplexCollection() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Collections('504')?$select=Nested"); + "Collections('503')?$select=Nested"); helper.assertStatus(200); final ObjectNode collection = helper.getValue(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryNavigationCount.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryNavigationCount.java index 0d56515ad..dd6f92c14 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryNavigationCount.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryNavigationCount.java @@ -1,11 +1,17 @@ package com.sap.olingo.jpa.processor.core.query; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.IOException; +import java.util.stream.Stream; import org.apache.olingo.commons.api.ex.ODataException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCapabilitiesAnnotationsProvider; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; @@ -13,49 +19,52 @@ class TestJPAQueryNavigationCount extends TestBase { - @Test - void testEntitySetCount() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations/$count"); - assertEquals(200, helper.getStatus()); - - assertEquals("10", helper.getRawResult()); + static Stream provideCountQueries() { + return Stream.of( + arguments("EntitySetCount", "Organizations/$count", "10"), + arguments("EntityNavigateCount", "Organizations('3')/Roles/$count", "3"), + arguments("EntitySetCountWithFilterOn", "AdministrativeDivisions/$count?$filter=Parent eq null", "23"), + arguments("EntitySetCountWithFilterOn", "Organizations/$count?$filter=Address/HouseNumber gt '30'", "7"), + arguments("EntitySetCountWithFilterOnDescription", "Persons/$count?$filter=LocationName eq 'Deutschland'", + "2")); } - @Test - void testEntityNavigateCount() throws IOException, ODataException { + @ParameterizedTest + @MethodSource("provideCountQueries") + void testEntitySetCount(final String text, final String queryString, final String numberOfResults) + throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations('3')/Roles/$count"); + final var helper = new IntegrationTestHelper(emf, queryString); assertEquals(200, helper.getStatus()); - assertEquals("3", helper.getRawResult()); + assertEquals(numberOfResults, helper.getRawResult(), text); } - @Test - void testEntitySetCountWithFilterOn() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations/$count?$filter=Address/HouseNumber gt '30'"); - - assertEquals(200, helper.getStatus()); - assertEquals("7", helper.getRawResult()); + static Stream provideCountTrueQueries() { + return Stream.of( + arguments("EntitySetCount", "Organizations?$count = true", 10), + arguments("EntityNavigateCount", "Organizations('3')/Roles?$count = true", 3), + arguments("EntitySetCountWithFilterOn", "AdministrativeDivisions?$count = true&$filter=Parent eq null", 23), + arguments("EntitySetCountWithFilterOnDescription", + "Persons?$count = true&$filter=LocationName eq 'Deutschland'", 2)); } - @Test - void testEntitySetCountWithFilterOnDescription() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Persons/$count?$filter=LocationName eq 'Deutschland'", - new JavaBasedCapabilitiesAnnotationsProvider()); + @ParameterizedTest + @MethodSource("provideCountTrueQueries") + void testEntitySetCountTrue(final String text, final String queryString, final Integer numberOfResults) + throws IOException, ODataException { + final var helper = new IntegrationTestHelper(emf, queryString); assertEquals(200, helper.getStatus()); - assertEquals("2", helper.getRawResult()); + final var act = helper.getValue().get("@odata.count"); + assertNotNull(act); + assertEquals(numberOfResults, act.asInt()); } @Test void testCountNotSupported() throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + final var helper = new IntegrationTestHelper(emf, "AnnotationsParents(CodePublisher='Eurostat',CodeID='NUTS2',DivisionCode='BE24')/Children/$count", new JavaBasedCapabilitiesAnnotationsProvider()); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java index e5a8b60a1..a037ec8cc 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java @@ -1,14 +1,32 @@ package com.sap.olingo.jpa.processor.core.query; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Optional; + +import jakarta.servlet.http.HttpServletResponse; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.debug.DefaultDebugSupport; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; +import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; +import com.sap.olingo.jpa.processor.core.api.JPAODataRequestProcessor; +import com.sap.olingo.jpa.processor.core.api.JPAODataServiceContext; +import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; import com.sap.olingo.jpa.processor.core.util.TestBase; @@ -55,4 +73,55 @@ void testTopSkip() throws IOException, ODataException { assertEquals("LAU2", act.get(0).get("CodeID").asText()); assertEquals("31022", act.get(0).get("DivisionCode").asText()); } + + @Test + void testTopReturnsAllIfToLarge() throws IOException, ODataException { + final OData odata = OData.newInstance(); + + final var sessionContext = JPAODataServiceContext.with() + .setPUnit(IntegrationTestHelper.PUNIT_NAME) + .setEntityManagerFactory(emf) + .setRequestMappingPath("bp/v1") + .setTypePackage(TestBase.enumPackages) + .build(); + + final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); + when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); + when(externalContext.getClaimsProvider()).thenReturn(Optional.empty()); + when(externalContext.getGroupsProvider()).thenReturn(Optional.empty()); + when(externalContext.getDebuggerSupport()).thenReturn(new DefaultDebugSupport()); + when(externalContext.getRequestParameter()).thenReturn(mock(JPARequestParameterMap.class)); + final var requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext); + + final var handler = odata.createHandler(odata.createServiceMetadata(sessionContext.getEdmProvider(), + new ArrayList<>())); + + final var request = IntegrationTestHelper.getRequestMock(IntegrationTestHelper.uriPrefix + "Persons?$top=5000"); + final var response = IntegrationTestHelper.getResponseMock(); + handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); + handler.process(request, response); + + final ObjectMapper mapper = new ObjectMapper(); + final ObjectNode value = (ObjectNode) mapper.readTree(getRawResult(response)); + assertNull(value.get("@odata.nextLink")); + + } + + public String getRawResult(final HttpServletResponse response) throws IOException { + final InputStream in = asInputStream(response); + final StringBuilder builder = new StringBuilder(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String read; + + while ((read = reader.readLine()) != null) { + builder.append(read); + } + reader.close(); + return builder.toString(); + } + + public InputStream asInputStream(final HttpServletResponse response) throws IOException { + return new IntegrationTestHelper.ResultStream((IntegrationTestHelper.OutPutStream) response.getOutputStream()); + } + } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java index bc98bc6f9..2cf30707c 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -14,6 +15,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Optional; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; @@ -57,9 +59,9 @@ void testReturnsNotImplementedIfPagingProviderNotAvailable() throws IOException, } @Test - void testReturnsGoneIfPagingProviderReturnsNullForSkiptoken() throws IOException, ODataException { + void testReturnsGoneIfPagingProviderReturnsNullForSkipToken() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getNextPage("xyz")).thenReturn(null); + when(provider.getNextPage(eq("xyz"), any(), any(), any(), any())).thenReturn(Optional.empty()); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$skiptoken=xyz", provider); helper.assertStatus(410); @@ -68,7 +70,7 @@ void testReturnsGoneIfPagingProviderReturnsNullForSkiptoken() throws IOException @Test void testReturnsFullResultIfProviderDoesNotReturnPage() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenReturn(null); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())).thenReturn(Optional.empty()); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations", provider); helper.assertStatus(200); assertEquals(10, helper.getValues().size()); @@ -78,8 +80,8 @@ void testReturnsFullResultIfProviderDoesNotReturnPage() throws IOException, ODat void testReturnsPartResultIfProviderPages() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); assertEquals(5, helper.getValues().size()); @@ -89,8 +91,8 @@ void testReturnsPartResultIfProviderPages() throws IOException, ODataException { void testReturnsNextLinkIfProviderPages() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); @@ -102,8 +104,9 @@ void testReturnsNextLinkIfProviderPages() throws IOException, ODataException { void testReturnsNextLinkNotAStringIfProviderPages() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, Integer.valueOf(123456789))); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, Integer.valueOf( + 123456789)))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); @@ -116,38 +119,39 @@ void testReturnsNextPagesRespectingFilter() throws IOException, ODataException { final UriInfo uriInfo = buildUriInfo(); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getNextPage("xyz")).thenReturn(new JPAODataPage(uriInfo, 5, 5, null)); + when(provider.getNextPage(eq("xyz"), any(), any(), any(), any())) + .thenReturn(Optional.of(new JPAODataPage(uriInfo, 5, 5, null))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$skiptoken=xyz", provider); helper.assertStatus(200); assertEquals(5, helper.getValues().size()); - final ObjectNode org = (ObjectNode) helper.getValues().get(4); - assertEquals("1", org.get("ID").asText()); + final ObjectNode organization = (ObjectNode) helper.getValues().get(4); + assertEquals("1", organization.get("ID").asText()); } @Test void testEntityManagerProvided() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); - verify(provider).getFirstPage(any(), any(), any(), notNull()); + verify(provider).getFirstPage(any(), any(), any(), any(), any(), notNull()); } @Test void testCountQueryProvided() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); - verify(provider).getFirstPage(any(), any(), notNull(), any()); + verify(provider).getFirstPage(any(), any(), any(), any(), notNull(), any()); } @Test @@ -155,13 +159,13 @@ void testCountQueryProvidedWithProtection() throws IOException, ODataException { final JPAODataClaimsProvider claims = new JPAODataClaimsProvider(); claims.add("UserId", new JPAClaimsPair<>("Willi")); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "BusinessPartnerProtecteds", provider, claims); helper.assertStatus(200); final ArrayNode act = helper.getValues(); assertEquals(3, act.size()); - verify(provider).getFirstPage(any(), any(), argThat(new CountQueryMatcher(3L)), any()); + verify(provider).getFirstPage(any(), any(), any(), any(), argThat(new CountQueryMatcher(3L)), any()); } @Test @@ -170,8 +174,8 @@ void testMaxPageSizeHeaderProvided() throws IOException, ODataException { headers = new HashMap<>(); final List headerValues = new ArrayList<>(0); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); headerValues.add("odata.maxpagesize=50"); headers.put("Prefer", headerValues); @@ -179,7 +183,7 @@ void testMaxPageSizeHeaderProvided() throws IOException, ODataException { headers); helper.assertStatus(200); - verify(provider).getFirstPage(any(), notNull(), any(), any()); + verify(provider).getFirstPage(any(), any(), any(), notNull(), any(), any()); } @Test @@ -187,8 +191,8 @@ void testMaxPageSizeHeaderProvidedInLowerCase() throws IOException, ODataExcepti headers = new HashMap<>(); final List headerValues = new ArrayList<>(0); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); headerValues.add("odata.maxpagesize=50"); headers.put("prefer", headerValues); @@ -196,7 +200,7 @@ void testMaxPageSizeHeaderProvidedInLowerCase() throws IOException, ODataExcepti headers); helper.assertStatus(200); - verify(provider).getFirstPage(any(), notNull(), any(), any()); + verify(provider).getFirstPage(any(), any(), any(), notNull(), any(), any()); } @Test @@ -204,13 +208,13 @@ void testUriInfoProvided() throws IOException, ODataException { final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc", provider); helper.assertStatus(200); - verify(provider).getFirstPage(notNull(), any(), any(), any()); + verify(provider).getFirstPage(any(), any(), notNull(), any(), any(), any()); } @Test @@ -220,8 +224,8 @@ void testMaxPageSiteHeaderNotANumber() throws IOException, ODataException { final List headerValues = new ArrayList<>(0); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i + .getArguments()[2], 0, 5, "Hugo")); headerValues.add("odata.maxpagesize=Hugo"); headers.put("Prefer", headerValues); @@ -235,30 +239,31 @@ void testMaxPageSiteHeaderNotANumber() throws IOException, ODataException { void testSelectSubsetOfFields() throws IOException, ODataException { final UriInfo uriInfo = buildUriInfo(); final JPAODataPagingProvider provider = mock(JPAODataPagingProvider.class); - final SelectOption selOpt = mock(SelectOption.class); - final List selItems = new ArrayList<>(); - final SelectItem selItem = mock(SelectItem.class); + final SelectOption selectOpt = mock(SelectOption.class); + final List selectItems = new ArrayList<>(); + final SelectItem selectItem = mock(SelectItem.class); - when(uriInfo.getSelectOption()).thenReturn(selOpt); - when(selOpt.getKind()).thenReturn(SystemQueryOptionKind.SELECT); - when(selOpt.getSelectItems()).thenReturn(selItems); - selItems.add(selItem); + when(uriInfo.getSelectOption()).thenReturn(selectOpt); + when(selectOpt.getKind()).thenReturn(SystemQueryOptionKind.SELECT); + when(selectOpt.getSelectItems()).thenReturn(selectItems); + selectItems.add(selectItem); final UriInfoResource selectPath = mock(UriInfoResource.class); final List selectPathItems = new ArrayList<>(0); final UriResourcePrimitiveProperty selectResource = mock(UriResourcePrimitiveProperty.class); final EdmProperty selectProperty = mock(EdmProperty.class); selectPathItems.add(selectResource); - when(selItem.getResourcePath()).thenReturn(selectPath); + when(selectItem.getResourcePath()).thenReturn(selectPath); when(selectPath.getUriResourceParts()).thenReturn(selectPathItems); when(selectResource.getSegmentValue()).thenReturn("ID"); when(selectResource.getProperty()).thenReturn(selectProperty); when(selectProperty.getName()).thenReturn("ID"); - when(provider.getFirstPage(any(), any(), any(), any())).thenAnswer(i -> new JPAODataPage((UriInfo) i - .getArguments()[0], 0, 5, "Hugo")); + when(provider.getFirstPage(any(), any(), any(), any(), any(), any())) + .thenAnswer(i -> Optional.of(new JPAODataPage((UriInfo) i.getArguments()[2], 0, 5, "Hugo"))); - when(provider.getNextPage("'Hugo'")).thenReturn(new JPAODataPage(uriInfo, 5, 5, "Willi")); + when(provider.getNextPage(eq("'Hugo'"), any(), any(), any(), any())) + .thenReturn(Optional.of(new JPAODataPage(uriInfo, 5, 5, "Willi"))); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=ID desc&$select=ID", provider); helper.assertStatus(200); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java index f33f9a2ed..d0a2e29e1 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java @@ -56,16 +56,14 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestProcessor; import com.sap.olingo.jpa.processor.core.api.JPAODataSessionContextAccess; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaRequestMapper; -import com.sap.olingo.jpa.processor.core.api.mapper.JakartaResponseMapper; import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; public class IntegrationTestHelper { public final HttpServletRequest request; public final HttpServletResponse response; private final ArgumentCaptor captorStatus; - private static final String uriPrefix = "http://localhost:8080/Test/Olingo.svc/"; - private static final String PUNIT_NAME = "com.sap.olingo.jpa"; + public static final String uriPrefix = "http://localhost:8080/Test/Olingo.svc/"; + public static final String PUNIT_NAME = "com.sap.olingo.jpa"; public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath) throws IOException, ODataException { @@ -153,17 +151,27 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour this.response = getResponseMock(); if (functionPackage != null) packages = ArrayUtils.add(packages, functionPackage); - final JPAEdmProvider edmProvider = new JPAEdmProvider(PUNIT_NAME, localEmf, null, packages, - annotationsProvider == null ? Collections.emptyList() : Collections.singletonList(annotationsProvider)); - - final EntityManager em = createEmfWrapper(localEmf, edmProvider).createEntityManager(); + final JPAEdmProvider edmProvider = buildEdmProvider(localEmf, annotationsProvider, packages); final JPAODataSessionContextAccess sessionContext = new JPAODataContextAccessDouble(edmProvider, dataSource, pagingProvider, annotationsProvider, functionPackage); final ODataHttpHandler handler = odata.createHandler(odata.createServiceMetadata(sessionContext.getEdmProvider(), new ArrayList<>())); + final JPAODataInternalRequestContext requestContext = buildRequestContext(localEmf, claims, groups, edmProvider, + sessionContext); + + handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); + handler.register(new JPAODataBatchProcessor(sessionContext, requestContext)); + handler.process(request, response); + + } + + public JPAODataInternalRequestContext buildRequestContext(final EntityManagerFactory localEmf, + final JPAODataClaimsProvider claims, final JPAODataGroupProvider groups, final JPAEdmProvider edmProvider, + final JPAODataSessionContextAccess sessionContext) throws ODataException { + final EntityManager em = createEmfWrapper(localEmf, edmProvider).createEntityManager(); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(em); when(externalContext.getClaimsProvider()).thenReturn(Optional.ofNullable(claims)); @@ -172,11 +180,14 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour when(externalContext.getRequestParameter()).thenReturn(mock(JPARequestParameterMap.class)); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext); + return requestContext; + } - handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); - handler.register(new JPAODataBatchProcessor(sessionContext, requestContext)); - handler.process(new JakartaRequestMapper(request), new JakartaResponseMapper(response)); - + public JPAEdmProvider buildEdmProvider(final EntityManagerFactory localEmf, + final AnnotationProvider annotationsProvider, final String[] packages) throws ODataException { + final JPAEdmProvider edmProvider = new JPAEdmProvider(PUNIT_NAME, localEmf, null, packages, + annotationsProvider == null ? Collections.emptyList() : Collections.singletonList(annotationsProvider)); + return edmProvider; } public HttpServletResponse getResponse() { @@ -363,12 +374,12 @@ private EntityManagerFactory createEmfWrapper(@Nonnull final EntityManagerFactor } } - private static class OutPutStream extends ServletOutputStream { + public static class OutPutStream extends ServletOutputStream { List buffer = new ArrayList<>(); @Override - public void write(final int b) throws IOException { - buffer.add(b); + public void write(final int nextByte) throws IOException { + buffer.add(nextByte); } public Iterator getBuffer() { @@ -391,7 +402,7 @@ public void setWriteListener(final WriteListener writeListener) { } // - class ResultStream extends InputStream { + public static class ResultStream extends InputStream { private final Iterator bufferExcess; private final int size; diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/JPAEntityTypeDouble.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/JPAEntityTypeDouble.java index abbaea1b5..7409c7225 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/JPAEntityTypeDouble.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/JPAEntityTypeDouble.java @@ -234,4 +234,10 @@ public CsdlAnnotation getAnnotation(final String alias, final String term) throw public JPAStructuredType getBaseType() { return base.getBaseType(); } + + @Override + public Object getAnnotationValue(final String alias, final String term, final String property) + throws ODataJPAModelException { + return this.base.getAnnotationValue(alias, term, property); + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java index 7c7a41449..5e6ecadc2 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java @@ -54,8 +54,8 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); jpaEntityType = helper.getJPAEntityType("BusinessPartnerWithGroupss"); createHeaders(); - context = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), - dataSource, + context = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), + dataSource, null, null); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestHelper.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestHelper.java index 153cf6876..08d88fcec 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestHelper.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestHelper.java @@ -75,6 +75,12 @@ public Optional getJPAAttribute(final String entitySetName, final return jpaEntity.getAttribute(attributeInternalName); } + public Optional getJPAAttribute(final Class clazz, final String attributeInternalName) + throws ODataJPAModelException { + final JPAEntityType jpaEntity = sd.getEntity(clazz); + return jpaEntity.getAttribute(attributeInternalName); + } + public EdmFunction getStoredProcedure(final EntityType jpaEntityType, final String string) { if (jpaEntityType.getJavaType() instanceof AnnotatedElement) { final EdmFunctions jpaStoredProcedureList = ((AnnotatedElement) jpaEntityType.getJavaType()) diff --git a/jpa/odata-jpa-spring-support/.project b/jpa/odata-jpa-spring-support/.project new file mode 100644 index 000000000..5ba7aca2f --- /dev/null +++ b/jpa/odata-jpa-spring-support/.project @@ -0,0 +1,34 @@ + + + jpa-spring-support + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-test/.settings/org.eclipse.core.resources.prefs b/jpa/odata-jpa-test/.settings/org.eclipse.core.resources.prefs index 365bbd609..04cfa2c1a 100644 --- a/jpa/odata-jpa-test/.settings/org.eclipse.core.resources.prefs +++ b/jpa/odata-jpa-test/.settings/org.eclipse.core.resources.prefs @@ -2,4 +2,5 @@ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 encoding//src/main/resources=UTF-8 encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 encoding/=UTF-8 diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/errormodel/KeyPartOfGroup.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/errormodel/KeyPartOfGroup.java index daa61639d..3228753e5 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/errormodel/KeyPartOfGroup.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/errormodel/KeyPartOfGroup.java @@ -51,10 +51,10 @@ public int hashCode() { @Override public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof KeyPartOfGroup)) return false; - final KeyPartOfGroup other = (KeyPartOfGroup) obj; - return eTag == other.eTag && Objects.equals(iD, other.iD); + if (obj instanceof final KeyPartOfGroup other) + + return eTag == other.eTag && Objects.equals(iD, other.iD); + return false; } } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AbstractGroup.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AbstractGroup.java index e7a6c7d0c..addf4df11 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AbstractGroup.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AbstractGroup.java @@ -43,10 +43,9 @@ public int hashCode() { @Override public boolean equals(final Object object) { - if (this == object) return true; - if (!(object instanceof AbstractGroup)) return false; - final AbstractGroup other = (AbstractGroup) object; - return Objects.equals(id, other.id); + if (object instanceof final AbstractGroup other) + return Objects.equals(id, other.id); + return false; } } \ No newline at end of file diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AnnotationsParent.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AnnotationsParent.java index e6bf6803a..64e4b37c5 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AnnotationsParent.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AnnotationsParent.java @@ -18,7 +18,10 @@ import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.ExpandRestrictions; import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.FilterRestrictions; import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.SortRestrictions; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.UpdateMethod; +import com.sap.olingo.jpa.metadata.odata.v4.capabilities.terms.UpdateRestrictions; import com.sap.olingo.jpa.metadata.odata.v4.core.terms.ComputedDefaultValue; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Example; import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Immutable; @FilterRestrictions(requiresFilter = true, requiredProperties = { "parentCodeID", "parentDivisionCode" }) @@ -26,6 +29,8 @@ nonSortableProperties = { "alternativeCode" }) @ExpandRestrictions(maxLevels = 2, nonExpandableProperties = { "children" }) @CountRestrictions(nonCountableNavigationProperties = { "children" }) +@UpdateRestrictions(updateMethod = UpdateMethod.PATCH, description = "Just to test") +@Example(description = "Get the roots", externalValue = "../AnnotationsParent?$filter=Parent eq null") //@EdmEntityType(extensionProvider = LauFilter.class) @IdClass(AdministrativeDivisionKey.class) @Entity(name = "AnnotationsParent") @@ -78,6 +83,7 @@ public class AnnotationsParent { insertable = false, updatable = false) private AnnotationsParent actualParent; + @Example(description = "Get the leaves", externalValue = "../AnnotationsParent?$filter=Children/$count eq 0") @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private final List children = new ArrayList<>(); diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/BusinessPartner.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/BusinessPartner.java index 1bd44b976..f4ee1107a 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/BusinessPartner.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/BusinessPartner.java @@ -29,7 +29,6 @@ import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmDescriptionAssociation; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEntityType; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmFunction; -import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmFunctions; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmParameter; @@ -38,32 +37,31 @@ @Entity(name = "BusinessPartner") @Table(schema = "\"OLINGO\"", name = "\"BusinessPartner\"") @EdmEntityType(extensionProvider = EmptyQueryExtensionProvider.class) -@EdmFunctions({ - @EdmFunction( - name = "CountRoles", - functionName = "COUNT_ROLES", - returnType = @EdmFunction.ReturnType(isCollection = true), - parameter = { @EdmParameter(name = "Amount", parameterName = "a", type = String.class), - }), - - @EdmFunction( - name = "max", - functionName = "MAX", - isBound = false, - hasFunctionImport = false, - returnType = @EdmFunction.ReturnType(type = BigDecimal.class, isCollection = false), - parameter = { @EdmParameter(name = "Path", parameterName = "path", type = String.class), - }), - - @EdmFunction( - name = "IsPrime", - functionName = "IS_PRIME", - isBound = false, - hasFunctionImport = true, - returnType = @EdmFunction.ReturnType(type = Boolean.class, isNullable = false), - parameter = { @EdmParameter(name = "Number", type = BigDecimal.class, precision = 32, scale = 0) }), - -}) + +@EdmFunction( + name = "CountRoles", + functionName = "COUNT_ROLES", + returnType = @EdmFunction.ReturnType(isCollection = true), + parameter = { @EdmParameter(name = "Amount", parameterName = "a", type = String.class), + }) + +@EdmFunction( + name = "max", + functionName = "MAX", + isBound = false, + hasFunctionImport = false, + returnType = @EdmFunction.ReturnType(type = BigDecimal.class, isCollection = false), + parameter = { @EdmParameter(name = "Path", parameterName = "path", type = String.class), + }) + +@EdmFunction( + name = "IsPrime", + functionName = "IS_PRIME", + isBound = false, + hasFunctionImport = true, + returnType = @EdmFunction.ReturnType(type = Boolean.class, isNullable = false), + parameter = { @EdmParameter(name = "Number", type = BigDecimal.class, precision = 32, scale = 0) }) + public abstract class BusinessPartner implements KeyAccess { @Id @Column(name = "\"ID\"") diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/CollectionWithTwoKey.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/CollectionWithTwoKey.java new file mode 100644 index 000000000..4142f55f1 --- /dev/null +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/CollectionWithTwoKey.java @@ -0,0 +1,54 @@ +package com.sap.olingo.jpa.processor.core.testmodel; + +import java.util.List; +import java.util.Objects; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; + +@Entity +@Table(schema = "\"OLINGO\"", name = "\"Collections\"") +public class CollectionWithTwoKey { + + @Id + @Column(name = "\"ID\"") + private String iD; + + @Column(name = "\"Number\"") + private Long number; + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(schema = "\"OLINGO\"", name = "\"NestedComplex\"", + joinColumns = { + @JoinColumn(name = "\"ID\"", referencedColumnName = "\"ID\""), + @JoinColumn(name = "\"Number\"", referencedColumnName = "\"Number\"") + }) + private List nested; // Must not be assigned to an ArrayList + + public String getID() { + return iD; + } + + public void setID(final String ID) { + this.iD = ID; + } + + @Override + public int hashCode() { + return Objects.hash(iD, number); + } + + @Override + public boolean equals(final Object object) { + if (object instanceof final CollectionWithTwoKey other) + return Objects.equals(iD, other.iD) && Objects.equals(number, other.number); + return false; + } + +} diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DummyToBeIgnored.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DummyToBeIgnored.java index 431e1900d..3edbdb61e 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DummyToBeIgnored.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DummyToBeIgnored.java @@ -13,7 +13,6 @@ import jakarta.persistence.Table; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmFunction; -import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmFunctions; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmParameter; @@ -22,14 +21,11 @@ * */ @Entity -@EdmFunctions({ - @EdmFunction( - name = "IsOdd", - functionName = "IS_ODD", - returnType = @EdmFunction.ReturnType(isCollection = true), - parameter = { @EdmParameter(name = "Number", type = BigDecimal.class, precision = 32, scale = 0) }), - -}) +@EdmFunction( + name = "IsOdd", + functionName = "IS_ODD", + returnType = @EdmFunction.ReturnType(isCollection = true), + parameter = { @EdmParameter(name = "Number", type = BigDecimal.class, precision = 32, scale = 0) }) @Table(schema = "\"OLINGO\"", name = "\"DummyToBeIgnored\"") @EdmIgnore public class DummyToBeIgnored { diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/GroupNameCalculator.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/GroupNameCalculator.java index 1b0ac63bd..14ec4d806 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/GroupNameCalculator.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/GroupNameCalculator.java @@ -2,7 +2,7 @@ * */ package com.sap.olingo.jpa.processor.core.testmodel; - + import jakarta.persistence.Tuple; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmTransientPropertyCalculator; diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/JoinPartnerRoleRelationKey.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/JoinPartnerRoleRelationKey.java index 1261d262b..fa8297bd9 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/JoinPartnerRoleRelationKey.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/JoinPartnerRoleRelationKey.java @@ -39,11 +39,10 @@ public int hashCode() { } @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof JoinPartnerRoleRelationKey)) return false; - final JoinPartnerRoleRelationKey other = (JoinPartnerRoleRelationKey) obj; - return Objects.equals(sourceID, other.sourceID) && Objects.equals(targetID, other.targetID); + public boolean equals(final Object object) { + if (object instanceof final JoinPartnerRoleRelationKey other) + return Objects.equals(sourceID, other.sourceID) && Objects.equals(targetID, other.targetID); + return false; } } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/Pages.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/Pages.java new file mode 100644 index 000000000..307b350f8 --- /dev/null +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/Pages.java @@ -0,0 +1,132 @@ +package com.sap.olingo.jpa.processor.core.testmodel; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; + +@EdmIgnore +@Entity +@Table(schema = "\"OLINGO\"", name = "\"Pages\"") +public class Pages { + + @Id + @Column(name = "\"token\"") + private String token; + + @Column(name = "\"skip\"") + private Integer skip; + + @Column(name = "\"top\"") + private Integer top; + + @Column(name = "\"last\"") + private Integer last; + + @Column(name = "\"baseUri\"") + private String baseUri; + + @Column(name = "\"oDataPath\"") + private String oDataPath; + + @Column(name = "\"queryPath\"") + private String queryPath; + + @Column(name = "\"fragments\"") + private String fragments; + + public Pages() { + // Needed for JPA + } + + public Pages(final String token, final Integer skip, final Integer top, final Integer last, final String baseUri, + final String oDataPath, final String queryPath, final String fragments) { + super(); + this.token = token; + this.skip = skip; + this.top = top; + this.last = last; + this.baseUri = baseUri; + this.oDataPath = oDataPath; + this.queryPath = queryPath; + this.fragments = fragments; + } + + public Pages(final Pages previousPage, final int skip, final String token) { + super(); + this.token = token; + this.skip = skip; + this.top = previousPage.top; + this.last = previousPage.last; + this.baseUri = previousPage.baseUri; + this.oDataPath = previousPage.oDataPath; + this.queryPath = previousPage.queryPath; + this.fragments = previousPage.fragments; + } + + public String getToken() { + return token; + } + + public void setToken(final String token) { + this.token = token; + } + + public Integer getSkip() { + return skip; + } + + public void setSkip(final Integer skip) { + this.skip = skip; + } + + public Integer getTop() { + return top; + } + + public void setTop(final Integer top) { + this.top = top; + } + + public String getBaseUri() { + return baseUri; + } + + public void setBaseUri(final String baseUri) { + this.baseUri = baseUri; + } + + public String getODataPath() { + return oDataPath; + } + + public void setODataPath(final String oDataPath) { + this.oDataPath = oDataPath; + } + + public String getQueryPath() { + return queryPath; + } + + public void setQueryPath(final String queryPath) { + this.queryPath = queryPath; + } + + public String getFragments() { + return fragments; + } + + public void setFragments(final String fragments) { + this.fragments = fragments; + } + + public Integer getLast() { + return last; + } + + public void setLast(final Integer last) { + this.last = last; + } +} diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PersonDeepProtectedHidden.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PersonDeepProtectedHidden.java index 76b7e935a..1d95ead2b 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PersonDeepProtectedHidden.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PersonDeepProtectedHidden.java @@ -13,7 +13,6 @@ import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmIgnore; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtectedBy; -import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmProtections; @EdmIgnore // Only @Entity(name = "PersonDeepProtected") @@ -33,10 +32,8 @@ public class PersonDeepProtectedHidden extends BusinessPartnerProtected {// #NOS private AddressDeepProtected inhouseAddress; @Embedded - @EdmProtections({ - @EdmProtectedBy(name = "Creator", path = "created/by"), - @EdmProtectedBy(name = "Updator", path = "updated/by") - }) + @EdmProtectedBy(name = "Creator", path = "created/by") + @EdmProtectedBy(name = "Updator", path = "updated/by") private final AdministrativeInformation protectedAdminInfo = new AdministrativeInformation(); public PersonDeepProtectedHidden() { diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PostalAddressDataWithGroup.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PostalAddressDataWithGroup.java index faadf14dc..94be3ac56 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PostalAddressDataWithGroup.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/PostalAddressDataWithGroup.java @@ -6,7 +6,6 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinColumns; import jakarta.persistence.OneToMany; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmDescriptionAssociation; @@ -44,14 +43,12 @@ public class PostalAddressDataWithGroup { @EdmDescriptionAssociation(languageAttribute = "key/language", descriptionAttribute = "name") @OneToMany(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "\"CodePublisher\"", referencedColumnName = "\"Address.RegionCodePublisher\"", - insertable = false, updatable = false), - @JoinColumn(name = "\"CodeID\"", referencedColumnName = "\"Address.RegionCodeID\"", insertable = false, - updatable = false), - @JoinColumn(name = "\"DivisionCode\"", referencedColumnName = "\"Address.Region\"", insertable = false, - updatable = false) - }) + @JoinColumn(name = "\"CodePublisher\"", referencedColumnName = "\"Address.RegionCodePublisher\"", + insertable = false, updatable = false) + @JoinColumn(name = "\"CodeID\"", referencedColumnName = "\"Address.RegionCodeID\"", insertable = false, + updatable = false) + @JoinColumn(name = "\"DivisionCode\"", referencedColumnName = "\"Address.Region\"", insertable = false, + updatable = false) private Collection regionName; public String getStreetName() { diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriod.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriod.java index 34b22b8d9..91258a663 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriod.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriod.java @@ -66,11 +66,10 @@ public int hashCode() { } @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof TemporalWithValidityPeriod)) return false; - final TemporalWithValidityPeriod other = (TemporalWithValidityPeriod) obj; - return Objects.equals(id, other.id) && Objects.equals(validityStartDate, other.validityStartDate); + public boolean equals(final Object object) { + if (object instanceof final TemporalWithValidityPeriod other) + return Objects.equals(id, other.id) && Objects.equals(validityStartDate, other.validityStartDate); + return false; } } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriodKey.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriodKey.java index 129c5ee9b..8478a6a4f 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriodKey.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TemporalWithValidityPeriodKey.java @@ -51,11 +51,10 @@ public int hashCode() { } @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (!(obj instanceof TemporalWithValidityPeriodKey)) return false; - final TemporalWithValidityPeriodKey other = (TemporalWithValidityPeriodKey) obj; - return Objects.equals(id, other.id) && Objects.equals(validityStartDate, other.validityStartDate); + public boolean equals(final Object object) { + if (object instanceof final TemporalWithValidityPeriodKey other) + return Objects.equals(id, other.id) && Objects.equals(validityStartDate, other.validityStartDate); + return false; } } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/util/TestDataConstants.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/util/TestDataConstants.java index a15ac50f1..0e1a15b81 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/util/TestDataConstants.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/util/TestDataConstants.java @@ -11,8 +11,8 @@ public enum TestDataConstants { NO_ATTRIBUTES_ORGANIZATION(4), NO_ATTRIBUTES_PERSON(2), NO_DEC_ATTRIBUTES_BUSINESS_PARTNER(9), - NO_ENTITY_TYPES(34), - NO_ENTITY_SETS(32), + NO_ENTITY_TYPES(35), + NO_ENTITY_SETS(33), NO_SINGLETONS(3), NO_COMPLEXT_TYPES(25); diff --git a/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml b/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml index d26268c0f..cfd4cefd7 100644 --- a/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml +++ b/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml @@ -32,6 +32,7 @@ com.sap.olingo.jpa.processor.core.testmodel.CollectionSecondLevelComplex com.sap.olingo.jpa.processor.core.testmodel.CollectionInnerComplex com.sap.olingo.jpa.processor.core.testmodel.CollectionNestedComplexWithTransient + com.sap.olingo.jpa.processor.core.testmodel.CollectionWithTwoKey com.sap.olingo.jpa.processor.core.testmodel.Comment com.sap.olingo.jpa.processor.core.testmodel.CommunicationData com.sap.olingo.jpa.processor.core.testmodel.ComplexWithTransientComplexCollection diff --git a/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql b/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql index 8e7710e85..9208dc332 100644 --- a/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql +++ b/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql @@ -435,7 +435,7 @@ CREATE TABLE "AdministrativeDivision"( "ParentCodeID" VARCHAR(10), "ParentDivisionCode" VARCHAR(10), "AlternativeCode" VARCHAR(10), - "Area" int, --DECIMAL(31,0), + "Area" BIGINT, --DECIMAL(31,0), "Population" BIGINT, PRIMARY KEY ("CodePublisher", "CodeID", "DivisionCode")); @@ -746,7 +746,7 @@ insert into "NestedComplex" values('501',1, 1, 1, 1); insert into "NestedComplex" values('501',3, 1, 1, 1); insert into "NestedComplex" values('503',1, 4, 5, 6); insert into "NestedComplex" values('504',1, 1, 3, 6); - +insert into "NestedComplex" values('504',3, 1, 1, 7); CREATE TABLE "CollectionsDeep" ( "ID" VARCHAR(32) NOT NULL , @@ -971,6 +971,18 @@ CREATE TABLE "TemporalWithValidityPeriod"( INSERT INTO "TemporalWithValidityPeriod" values ('99', '2022-11-01', '9999-12-31','Electrician'); INSERT INTO "TemporalWithValidityPeriod" values ('98', '2022-01-01', '9999-12-31','Architect'); +-------------------------------------------- + +-- CREATE TABLE "Pages"( +-- "token" uuid NOT NULL, +-- "skip" INTEGER NOT NULL, +-- "top" INTEGER NOT NULL, +-- "baseUri" VARCHAR(255), +-- "oDataPath" VARCHAR(255), +-- "queryPath" VARCHAR(255), +-- "fragments" VARCHAR(255), +-- PRIMARY KEY ("ID", "StartDate")); + -------------------------------------------- CREATE TABLE "DummyToBeIgnored" ( "ID" VARCHAR(32) NOT NULL , @@ -1003,4 +1015,5 @@ CREATE TABLE "DummyToBeIgnored" ( -- AND NOT( a."CodePublisher" = "Publisher" -- AND a."CodeID" = "ID" -- AND a."DivisionCode" = "Division" ) --- ); \ No newline at end of file +-- ); + diff --git a/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TestStandardMethodsOfTestModel.java b/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TestStandardMethodsOfTestModel.java index 873ccbe59..fa3884f12 100644 --- a/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TestStandardMethodsOfTestModel.java +++ b/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TestStandardMethodsOfTestModel.java @@ -48,6 +48,7 @@ import com.sap.olingo.jpa.processor.core.testmodel.CollectionNestedComplex; import com.sap.olingo.jpa.processor.core.testmodel.CollectionPartOfComplex; import com.sap.olingo.jpa.processor.core.testmodel.CollectionSecondLevelComplex; +import com.sap.olingo.jpa.processor.core.testmodel.CollectionWithTwoKey; import com.sap.olingo.jpa.processor.core.testmodel.Comment; import com.sap.olingo.jpa.processor.core.testmodel.CommunicationData; import com.sap.olingo.jpa.processor.core.testmodel.Country; @@ -121,6 +122,7 @@ static Stream testModelEntities() { arguments(CollectionDeep.class), arguments(CollectionFirstLevelComplex.class), arguments(CollectionSecondLevelComplex.class), + arguments(CollectionWithTwoKey.class), arguments(Comment.class), arguments(Country.class), arguments(CountryKey.class), diff --git a/jpa/odata-jpa-test/src/test/resources/requests/request-set.http b/jpa/odata-jpa-test/src/test/resources/requests/request-set.http new file mode 100644 index 000000000..49bae9a8a --- /dev/null +++ b/jpa/odata-jpa-test/src/test/resources/requests/request-set.http @@ -0,0 +1,61 @@ +GET http://localhost:9010/bp/v1/$metadata + +GET http://localhost:9010/bp/v1/AdministrativeDivisions?$expand=Children&$top=100 +Accept: application/json + +GET http://localhost:9010/bp/v1/AdministrativeDivisions?$skiptoken='32544e87-f99a-48f7-8e3e-c41a907a2bf7' +Accept: application/json + +GET http://localhost:9010/bp/v1/AdministrativeDivisions?$filter=Children/$count eq 0 + +GET http://localhost:9010/bp/v1/AdministrativeDivisions?$filter=Parent eq null + +GET http://localhost:9010/bp/v1/BusinessPartners?$filter=Roles/$count eq 0 + +GET http://localhost:9010/bp/v1/JoinSources?$filter=OneToMany/$count eq 0 + +GET http://localhost:9010/bp/v1/BusinessPartnerProtecteds?$format=json +Authorization: Basic TWFydmluOjEyMzQ1Njc4 + +GET http://localhost:9010/bp/v1/BusinessPartnerProtecteds?$select=ID&$expand=RolesProtected&$filter=RolesProtected/$count eq 1&$format=json +Authorization: Basic TWFydmluOjEyMzQ1Njc4 + +GET http://localhost:9010/bp/v1/BusinessPartnerProtecteds?$select=ID&$expand=Roles&$filter=Roles/$count eq 1&$format=json +Authorization: Basic TWFydmluOjEyMzQ1Njc4 + +GET http://localhost:9010/bp/v1/CollectionWithTwoKeys?$filter=Nested/$count eq 1 + +GET http://localhost:9010/bp/v1/Organizations?$select=ID&$filter=Comment/$count ge 1 + +GET http://localhost:8088/transportation/v1/Airports?$format=json +Accept: application/json + + +https://raw.githubusercontent.com/oasis-tcs/odata-vocabularies/main/vocabularies/Org.OData.Capabilities.V1.xml + +http://localhost:8080/open-resource-discovery-service/v0/systemInstances?$expand=consumptionBundles +Accept: application/json +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnQiOiJ7XCJjb25zdW1lclRlbmFudFwiOiBcImJiYmJiYmJiLWJiYmItYmJiYi1iYmJiLWJiYmJiYmJiYmJiYlwiLFwicHJvdmlkZXJUZW5hbnRcIjogXCI0YzYzZTNiMi0zMzAxLTQ3OTYtYmM5NS05ZmI1YjI3ODAzNDJcIn0iLCJ0b2tlbkNsaWVudElEIjoiNDcxMSIsInJlZ2lvbiI6IjQ3MTEiLCJzY29wZXMiOiJpbnRlcm5hbF92aXNpYmlsaXR5OnJlYWQiLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +applicationTenantId: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb + + +http://localhost:8080/open-resource-discovery-service/v0/systemInstances +Accept: application/json +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbnQiOiJ7XCJjb25zdW1lclRlbmFudFwiOiBcImJiYmJiYmJiLWJiYmItYmJiYi1iYmJiLWJiYmJiYmJiYmJiYlwiLFwicHJvdmlkZXJUZW5hbnRcIjogXCI0YzYzZTNiMi0zMzAxLTQ3OTYtYmM5NS05ZmI1YjI3ODAzNDJcIn0iLCJ0b2tlbkNsaWVudElEIjoiNDcxMSIsInJlZ2lvbiI6IjQ3MTEiLCJzY29wZXMiOiJpbnRlcm5hbF92aXNpYmlsaXR5OnJlYWQiLCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +applicationTenantId: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb + + +http://localhost:8080/open-resource-discovery-service/v0/$metadata + + +POST http://localhost:9010/bp/v1/$batch +Content-Type: multipart/mixed;boundary=batch123 + +--batch123 +Content-Type: application/http + +GET BusinessPartners HTTP/1.1 +Accept: application/json + + +--batch123-- \ No newline at end of file diff --git a/jpa/odata-jpa-vocabularies/.project b/jpa/odata-jpa-vocabularies/.project new file mode 100644 index 000000000..2dd23f43d --- /dev/null +++ b/jpa/odata-jpa-vocabularies/.project @@ -0,0 +1,47 @@ + + + jpa-vocabularies + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + + + 1634102235489 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/AliasAccess.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/AliasAccess.java new file mode 100644 index 000000000..f04cbbd80 --- /dev/null +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/AliasAccess.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies; + +public interface AliasAccess { + String alias(); +} diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/NullAsDefault.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/NullAsDefault.java new file mode 100644 index 000000000..49003d11f --- /dev/null +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/NullAsDefault.java @@ -0,0 +1,14 @@ +package com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; + +/** + * Meta annotation to marks a field of an annotation that the real default value shall be null instead of the given one. + * This is necessary as null as default is not supported by Java + */ +@Retention(RUNTIME) +public @interface NullAsDefault { + +} diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/ODataAnnotatable.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/ODataAnnotatable.java index 76ee54e4c..b1ae61dec 100644 --- a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/ODataAnnotatable.java +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/ODataAnnotatable.java @@ -13,7 +13,9 @@ public interface ODataAnnotatable { * @return * @throws ODataJPAModelException */ - ODataPropertyPath convertStringToPath(final String internalPath) throws ODataPathNotFoundException; + default ODataPropertyPath convertStringToPath(final String internalPath) throws ODataPathNotFoundException { + return null; + } /** * @@ -21,20 +23,28 @@ public interface ODataAnnotatable { * @return * @throws ODataJPAModelException */ - ODataNavigationPath convertStringToNavigationPath(final String internalPath) throws ODataPathNotFoundException; + default ODataNavigationPath convertStringToNavigationPath(final String internalPath) + throws ODataPathNotFoundException { + return null; + } /** - * Searches to java annotation at an OData annotatable element. In case no annotation is found, Null is returned; + * Searches for a java annotation at an OData annotatable element. In case no annotation is found, Null is returned; * @param name of the annotation, as it would be returned by {@link Class#getName()} + * @deprecated Make use of {@link #javaAnnotations(String)} instead * @return */ + @Deprecated(forRemoval = true, since = "2.1.0") @CheckForNull - Annotation javaAnnotation(String name); + default Annotation javaAnnotation(final String name) { + return null; + } /** * Provide all java annotation at the annotatable that come from the given package. * @param packageName - * @return A map with key simple name ( {@link Class#getSimpleName()}) of the annotation and value the annotation + * @return A map with key simple name {@link Class#getSimpleName()}) of the annotation and value the + * annotation * instance */ Map javaAnnotations(String packageName); diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/PropertyAccess.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/PropertyAccess.java new file mode 100644 index 000000000..0c80db0f3 --- /dev/null +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/PropertyAccess.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies; + +public interface PropertyAccess { + String property(); +} diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/TermAccess.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/TermAccess.java new file mode 100644 index 000000000..d37e99c8f --- /dev/null +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/TermAccess.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies; + +public interface TermAccess { + String term(); +} diff --git a/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/Vocabulary.java b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/Vocabulary.java new file mode 100644 index 000000000..9485bbfab --- /dev/null +++ b/jpa/odata-jpa-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/core/edm/extension/vocabularies/Vocabulary.java @@ -0,0 +1,31 @@ +/** + * + */ +package com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ANNOTATION_TYPE) +/** + * @author Oliver Grande + * @since 1.1.1 + * 02.01.2023 + */ +public @interface Vocabulary { + /** + * Alias given in the vocabulary definition. E.g. Capabilities for + * Org.OData.Capabilities.V1 + */ + String alias(); + + /** + * Supported applicability. This could be a subset of the defined applicability. + */ + Applicability[] appliesTo(); +} diff --git a/jpa/pom.xml b/jpa/pom.xml index 8897cd127..5072b300f 100644 --- a/jpa/pom.xml +++ b/jpa/pom.xml @@ -13,17 +13,17 @@ 17 17 3.11.0 - 4.10.0 + 5.0.0 2.16.0 1.7.1 4.3.0 3.8.0 - 10.1.0 + 10.4.1 6.0.0 - 3.0.1 + 4.0.1 3.1.0 ${project.version} - 6.1.4 + 6.1.5 4.0.2 6.4.0.Final 3.2.2 @@ -31,6 +31,7 @@ 1.10.1 5.8.0 0.8.11 + 4.1.108.Final ${project.basedir}/odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml, ${project.basedir}/../odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml @@ -188,11 +189,6 @@ jakarta.servlet-api ${jakarta.version} - - javax.servlet - javax.servlet-api - ${javax.version} - jakarta.transaction jakarta.transaction-api @@ -238,7 +234,7 @@ io.netty netty-codec-http - 4.1.107.Final + ${netty.version} net.oneandone.reflections8 @@ -298,7 +294,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - LATEST + 3.10.0.2594 org.basepom.maven @@ -327,6 +323,16 @@ + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + + + org.apache.commons:commons-collections4 + + +