From 20b06f826558bc626176a6ed44aeba29d4106784 Mon Sep 17 00:00:00 2001 From: Oliver Grande Date: Sat, 29 Jun 2024 11:43:58 +0200 Subject: [PATCH] Defect/version 2.1.3 (#339) * Feature/transient fields (#142) * Fix null pointer if no enumerations are part of servcie * Enable HAS and enumeration as return type of operations * Allow Enumerations as operation parameter * Provide java based operations converted enumeration * Add SourceClear addon * Delete SourceClean addon * Enumerations at UDF functions - Works for bound functions - Works for functions with import * Correct unit tests * Enable multiple values for flags enumerations * Switch detection default between Embedded and Entity type * Allow collection attributes for metadata * Enable query without collection attributes * Rework query result conversion to reduce memory consumtion * Missing Metadata pom * First step entity with collections as return of function/action * Bugfix: Expand on Parent with $filter on navigation path * Enable the use of Join Tables - Filter on navigation via Join Table generally not supported * Create new query for inlinecount * Enable Join Tables@Navigation Filter - Not working mapped associations - Not working $count * Enable Join Tables@Navigation Filter - With mapped associations - With $count, know issue EclipsLink two sub types * Increase Version --> 0.2.6 * Remove deprecated JPAExecutableQuery * Bugfix: unidirectional join table based association * Access collection properties via navigation - New version * Access collection properties w and w/o $select * Access collection properties which are part of complex property * CUD operations on collection properties * Filter on collection properties pre optimization * Filter on collection properties any/all * Filter on collection w/o filter on $count and orderby $count * Correct issue #29 - Correct /$count implementation in JPAJoinQuery - Clean-up Hibernate differences -- Subquery from clause -- Id Class determination * Increase Version to 0.2.7 * Enable ..@odata.navigationLink in case of odata.metadata=full - links@complex types not yet working * Preparation for solution of issue OLINGO-1143 * Correct NullPointer and some clean-ups * Support order by $count - Increase version to 0.2.8 * Intermediate merge clean-up * Solve merge problem navigate complex collections * Resolve merge conflict converter * Correct error collection property and expand=* * Enable filter $count on collection property * Update version and url * Create and use page provider * Recreate change for solution of issue OLINGO-1143 * Finalize top level server driven paging * Correct string handling error * Correct link problem * Enable skiptoken to have other type than String * Move paging so next request injects old uriInfo correctly * Enable deep insert via complex properties * Ignore test for next commit * Enable create of a new entity linked by a to one using PATCH * Update to Olingo 4.5.0 - use new Olingo version - adopt one test to changed @odata.context content * Collection attributes rework. Issue #60 - Change processor pom -> javax.servlet became provided * Enable annotation for properties to mark them authorization relevant * Enable multiple protected properties at complex attributes * Rework EdmProtectedBy annotation to handle multiple claims at complex * Process protection * Rework handling of ignored protected * Made inner class static * Fixing issue #60 * $count query not working correctly on complex collection attributes * Various small bugs - Reorg. pom.xml - Correct http status on empty result - Correct empty check for collection properties * Correct typo in interface JPAODataPagingProvider * Prepare JPAODataDatabaseTableFunction for paging * Change to HSQLDB * Remove function from sql-file to work around problems with Derby * Correct NullPointerException EdmEntitySetResult * Determine result of create dependent based on before image * EntityType getAttribute did not return value for embedded id * Provide CUD example and correct query status codes * Correct query response * Maven Archetype for Spring based service * Upgrade to Junit5 to get support for Java > 1.8 * Extract interface for better unit test support - Cleanup tests * Clean-up interface implementation * Add SQL function handling * Complete DB function changes * Clean-up test class * Clean-up test class * Update Version number * Update Version number * Additional test for API classes * Enable suppress of wildcard in metadata * Enable wildcards during query processing * Increase information on error during filter * Support of PUT on collection properties and primitive properties * Fix Null Pointer on PATCH with return != minimal * Claims where not respected by queries issue #69 part one - /$count - ?$count=true - paging queries * Claims where not respected by queries issue #69 part two - $filter - Update on Olingo 4.6.0 * Restrict the DISTINCT to protections only * Increase test coverage - New tests - Rework vocabulary handling * Cleanup Vocabularies II - Add Action and Function - Remove NavigationProperty and dependent * Additional test and solution of issue #78 * Enable new annotation EdmVisibleFor at properties * Introduction of Request Context - Deprecation of two variants of JPAODataGetHandler.process - Increase test coverage * Restrict EdmVisiableFor to nullable non key attributes * Skip properties from selection that do not belong to provided group * Handle collection properties and navigation path * Enable groups at complex collections * Handle OrderBy clause * Correct unit test * Rework test containing images * Rework context handling - Move of cud handler to request context - Move of DebugSupport to request context (is request specific because of isUserAuthorized) - Introduction of builder for Service Context to get a leaner creation of handler * Providing groups during modifying requests * Re-enable old shortcut with late metadata provisioning * Update pom for missing jackson dependency * Update Archetype to version 0.3.4 * Update Version * Extension of session context, so an emf can be provided * Ignore annotation qualified if empty * Go back to Olingo JSON deserializer * Add qualifier to annotation * Increase release * Clean-up JPAEdmNameBuilder * Preparation of custom name builder - Create public interface - replace class by new interface * Revoke test adoption * Providing custome name builder * Update annotation EdmFunction * Increase release * Adopt archetype - Simplify service - Add integration test * Modify example did not persist on create * Deep Insert did not responded deep * Always select ETag * Complete ETag handling * Missing Context Changes * Reset unit test to current pushed state * Increase test coverage * Adopt test to real number of complex types * Enable V4.01 JSON format as request and response * Additional Integration tests support OData V4.01 JSON * Allow usage of different types of transaction * Increase Spring support * Bugfix/issue83 (#120) * Update Version * Extension of session context, so an emf can be provided * Preparation of custom name builder - Create public interface - replace class by new interface * Revoke test adoption * Providing custome name builder * Update annotation EdmFunction * Increase release * Adopt archetype - Simplify service - Add integration test * Modify example did not persist on create * Deep Insert did not responded deep * Always select ETag * Complete ETag handling * Missing Context Changes * Reset unit test to current pushed state * Increase test coverage * Adopt test to real number of complex types * Enable V4.01 JSON format as request and response * Additional Integration tests support OData V4.01 JSON * Allow usage of different types of transaction * Increase Spring support * Restrict $expand select by key range * Support root with navigation like AdministrativeDivision(...)/Children * Protect dilution of test coverage by test package (#133) * Increase release * Increase release (#136) * Protect dilution of test coverage by test package (#135) * Remove deprecated artifacts (#134) * Remove deprecated artefacts * Missing test adoptions * Upgrade Olingo version and processor version * Feature/transient fields (#137) * Increase release * Upgrade Olingo version and processor version * Prevent NPE on expand empty result (#138) * Correct SonarQube and SpotBug hints * Feature/transient fields (#139) * Increase release * Upgrade Olingo version and processor version * Correct SonarQube and SpotBug hints * SpotBugs and clean-ups * Build metadata I * First draft of criteria builder and query implementation (#140) * First draft of criteria builder and query implementation * Add Transient * First join version * Correct structured type * Test adoption was missing * Update criteria builder * Solve enum error * Support aggregation function * Correct buildInverseJoinColumns() error * Add Test buildInverseJoinColumns() error * Enable table join without entity type * Correct Error table name * Own test for JPAEdmProvider and correct name builder error * Additional test * Support parallel processing of batch requests * Complete synchronized creation of metadata * Enable transient field calculator * Suppress transient fields at $filter and $orderby * Enable transient collections and collections with transient attributes * Additional tests for changing operations * Enable absolute path in url * Add sonar plugin (#144) * Add sonar plugin * Update pom with sonar plugin * Feature/logging (#145) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Feature/logging (#147) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Feature/logging (#148) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Feature/logging (#150) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Feature/logging (#151) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Update README.md * Update README.md * Feature/logging (#152) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Clean-up sonar issues * Create additional test for debugger * Add tests for debugger * Update README.md * Replace database derby -> hsqldb * Update README.md (#153) * Feature/logging (#154) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Clean-up sonar issues * Create additional test for debugger * Add tests for debugger * Update README.md * Replace database derby -> hsqldb * Cleanup sonar issue * Increase test coverage for criteria builder (#155) * Feature/criteria builder (#156) * Increase test coverage for criteria builder * Replace double implementation by mocks * Update README.md (#157) * Feature/criteria builder (#158) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Feature/criteria builder (#159) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Update README.md (#160) * Update README.md * Update README.md * Remove potential null pointer (#161) * Bugfix/sonar issues (#162) * Remove potential null pointer * Remove potential Null Pointer * Bugfix/sonar issues (#163) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Bugfix/sonar issues (#164) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Additional clean-ups * Work on soanr issues * Bugfix/sonar issues (#165) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Additional clean-ups * Work on soanr issues * Additional cleanups * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors (#179) * Feature/release 1.0.0 (#192) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/syntax errors (#181) * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors * Correct syntax errors metadata * Clean-up typos * Rework build of OrderBy creation (#183) * Bugfix/syntax errors (#185) * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors * Add README.md and correct spelling errors (#179) * Correct syntax errors metadata * Clean-up typos * Update Version -> 1.0.0 * Usage of builder to create external request context (#187) * Bugfix/continue on error (#189) * Move batch processor from API to PROCESSOR package * Correct continue-on-error handling * Support generated Id in example CUD handler (#190) * Support generated Id in example CUD handler * Correct failing test * Adoption of archetype to 1.0.0 (#191) * Adoption of archetype to 1.0.0 * Correct sql error * Clean-up sonar issues * Correct criteria builder implementation (#193) * Feature/criteria builder (#194) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Feature/criteria builder (#195) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Feature/criteria builder (#196) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Feature/criteria builder (#197) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Feature/criteria builder (#198) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Feature/criteria builder (#199) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Intermediate state * Enable next block of queries * Expand with Join Tables * Last unit test corrections * Eliminate typos * Finalize changes * missing variable usage (#200) * missing variable usage * Increase processor version * Clean-up Sonar issues (#201) * Bugfix/no mapper for operations (#202) * Clean-up Sonar issues * Add test for type mapping for actions and java function + Cleanup test classes * Feature/1.0.1 (#203) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Change version to 1.0.1 * New version (#204) * Bugfix/filter eq withdate (#205) * New version * Support filter on Date and DateTime * Correct precision * Change another test * Us generated alias for column selection (#206) * Upgrade Version (#207) * Us generated alias for column selection * Upgrade version * Reduce complexity (#209) * ALL did not work with functions like startswith (#210) * Update version (#208) * Entity Set Path @ DB Functions (#211) * Feature/new archetype (#212) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Feature/new archetype (#213) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Allow Star for non string claim properties * Add Equals method to DeepProtectedExample * Feature/new archetype (#214) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Allow Star for non string claim properties * Add Equals method to DeepProtectedExample * Enable grant access to all for non string fields read * Replace Reflections API by Reflections8 (#215) * Support entitytypes singeltons (#216) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet - Read not ready * Update README.md (#218) * Update README.md * Update README.md * Update README.md * Solve issue 136 (#217) * Solve issue 136 * Eliminate hyphen * Eliminate WhiteSource detected vulnerabilities (#219) * Eliminate WhiteSource detected vulnerabilities * Add now required dependency * Remove setExternalName from Metadata Post Processor (#220) * Support entitytypes singeltons (#221) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Support entitytypes singeltons (#222) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Query extension declaration (#223) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Support entity types and singletons (#224) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Clean-up sonar issues * Support cast on navigations (#225) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Clean-up sonar issues * Cast within navigation path * Add cast collections * Add tests for EdmBoundCast * Support Cast on $expand * Update README.md (#226) * Update README.md * Update README.md * Update README.md * Update README.md * Bugfix/no constranis with ignored properties (#227) * Suppress referential constraint for properties that shall be ignored * No Referential Constraint in case one property shall be ignored * Bugfix/no constranis with ignored properties (#228) * Suppress referential constraint for properties that shall be ignored * No Referential Constraint in case one property shall be ignored * Bring back error on ignored * Enable subtyping for complex types (#229) * Support transient properties that require ignored properties (#231) * Bugfix/multi level inheritance (#232) * Adopt process-cb * Correct type converter problem * Use dbType for tuple result mapping * Bugfix/multi level inheritance (#233) * Adopt process-cb * Correct type converter problem * Use dbType for tuple result mapping * Correct constructor test of extension * Feature/release 1.0.3 (#234) * Correct typos and replace deprecated method * Set final Version * Feature/release 1.0.4 (#235) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Feature/release 1.0.4 (#236) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Small adoptions to test model * Reset change * Error correction (#237) * Feature/release 1.0.5 (#238) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Small adoptions to test model * Reset change * Add test for criteria builder * Error correction * Update H2 version (#239) * Update H2 version * Update version and rework DataSourceHelper * Feature/release 1.0.6 (#240) * Update H2 version * Update version and rework DataSourceHelper * Let H2 run in Postges mode * Upgrade version and additional tests (#241) * Upgrade version and additional tests * Remove session context from queries * Remove session context from processors * New version an sonar clean-up (#242) * Release 1.1.0 (#243) * New version an sonar clean-up * New path property fro sonar * Release 1.1.0 (#244) * New version an sonar clean-up * New path property fro sonar * Test usage of SNAPSHOT version * Update version -> 1.0.8 (#245) * Update version -> 1.0.8 * Count queries support Integer as result * Update archetype to support PATCH * Feature/release 1.0.0 (#247) * New version an sonar clean-up * New path property fro sonar * Test usage of SNAPSHOT version * Adopt archetype * Clean-up sonar issues * Resolve more sonar issues * Introducing virtual property * Clean-up associations and enable default columns when using ...-cb * Generate metadata for overloaded action (#248) * Generate metadata for overloaded action * Process action * Feature/action overload (#249) * Generate metadata for overloaded action * Process action * Use constructor of entity type an action is called for not of binding * Fix claims in collections where (#250) * Fix claims in collections where * Missed interface definition * Missed test renaming * Use dbtype to build key pair in case a conversion exists (#251) * Enable more constructors for binding parameter (#252) * Feature/more flexable constructor determination operations (#253) * Enable more constructors for binding parameter * Clean-up sonar errors * New type cast added - they have possible data lost (#254) * Collection not longer retrieves transient (#255) * Collection not longer retrieves transient * Skip unit test * Enhance partner determination (#256) * Bugfix/reuse navigation as partner (#257) * Enhance partner determination * Remove one sonar issue * Bugfix/reuse navigation as partner (#258) * Enhance partner determination * Remove one sonar issue * Correct partner determination * No fallback to server locale in case bundle requested local not found (#259) * Replace file reader (#260) * Replace fixed values by variables (#261) * Bugfix/error in controller test (#262) * Replace fixed values by variables * Update pom * Update archetype pom (#265) * Defect/archetype pom (#266) * Update archetype pom * Check older version of sonar plugin * Update test constants (#267) * Correct count behavior (#268) * Change to Java 11 (#269) * Change to Java 11 * Revert Java 11 * Update README.md (#270) * Defect/count not working on hana (#271) * Correct count behavior * Ignore sonar check. Equal methods are generated * Defect/java function parameter name (#272) * Raise meaningful exception on empty function parameter name * Meaningful exception if action parameter name empty * Raise message in case of Void as return type for functions (#273) * Raise message in case of Void as return type for functions * Add extenstion * Error in if clause * Extension of visitor for java functions (#274) * Update version to 1.1.1 (#275) * Feature/predefined annotations (#276) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Add page to order by builder (#277) * Feature/predefined annotations (#278) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Feature/predefined annotations (#279) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Feature/predefined annotations (#280) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Update .xmake.cfg (#283) * Update .xmake.cfg * Update .xmake.cfg * Correct spelling (#281) * Feature/predefined annotations (#282) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Update .xmake.cfg (#284) * Update .xmake.cfg * Update .xmake.cfg * Feature/predefined annotations (#286) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Support sorting restrictions by annotation * Build annotation based expand check * Expand star via path reads only requested from database * Check countability * Support collection property count * Annotations@Singeltons * Provide AnnotationProvider to all model elements * Enable annotations at properties * Feature/predefined annotations (#287) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Support sorting restrictions by annotation * Build annotation based expand check * Expand star via path reads only requested from database * Check countability * Support collection property count * Annotations@Singeltons * Provide AnnotationProvider to all model elements * Enable annotations at properties * Enable access to annotations via JPARequestEntity * Watch filtering * Clean-up pom and typo correction (#288) * Solve issue 212 and fix permission check issue with join tables (#289) * Solve issue 212 and fix permission check issue with join tables * $count for collection join as well * Clean-up code * Fix issue, converting type (#290) * Fix issue, converting type * Fix unit test * Correct type determination * Multi step Mapped Superclass (#291) * Upgrade H2 to 2.2.220 (#293) * Correct navigation to one is null (#294) * Fix join column determination with cyclic dependency (#295) * Fix join column determination with cyclic dependency * Additional test for navigation with mapped join table * Make OneToOne required and handle non JPA Processor errors in metadata (#296) * Defect/issue214 (#297) * Make OneToOne required and handle non JPA Processor errors in metadata * Clean-up warnings * Defect/issue214 (#298) * Make OneToOne required and handle non JPA Processor errors in metadata * Clean-up warnings * Add check for OneToMany * Clean-up sonar issues * Update .xmake.cfg (#300) * Update version to 2.0.0 (#299) * Update version to 2.0.0 * Unify junit version * Make use of Jakarta * Clean-up sonar issues created by switching to Java 17 * Update dependencies * Update dependencies and clean-up typos * Add unit tests * Increase test coverage * Defect/issue226 (#301) * Update version * Adopt correction done for release * Clean-up archetype * Clean-up error * Add test to check Olingo not supporting $expand...($level=; $expand...) * Note thrown runtime exception * First level of COUNT query as IN (#302) * Defect/issues239 (#303) * First level of COUNT query as IN * Deletion of JPACollectionFilterQuery * Defect/issues239 (#304) * First level of COUNT query as IN * Deletion of JPACollectionFilterQuery * Handle null where conditions * Rename properties file to prevent name clashes (#305) * Rename properties file to prevent name clashes * Update dependency, resolve conflict with Olingo * Clean-up pom and some code (#306) * Defect/pom cleanup (#307) * Clean-up pom and some code * Update dependencies * Archetype and SOnar clean-up (#308) * Add protection where to collection query (#309) * Check if cast is necessary (#310) * Set version to 2.0.2 (#311) * Update to Olingo 5.0.0 and remove wrapper (#314) * Extend interface for server driven paging (#313) * Extend interface for server driven paging * Correct failing tests * Clean-ups * Sonar clean-up * Change default implementation * Enhance paging provider interface * Finalize paging provider * Feature/enhancement of paging (#315) * Extend interface for server driven paging * Correct failing tests * Clean-ups * Sonar clean-up * Change default implementation * Enhance paging provider interface * Finalize paging provider * Use synchronized instead of thread save collections * First version and some clean-ups (#312) * First version and some clean-ups * Update filter restriction and new query directives * New query implementation for count (#316) * Feature/annotation api extension (#317) * First step * Enhancement with path expressions * Re-factor annotation search * Get annotation value from property * Prepare test of EntityType and NavigationProperty * Finalize annotation API * Update project files (#318) * Add null check for NOT IN clauses (#319) * Fix $count problem with collection properties (#320) * Fix $count problem with collection properties * Handle navigation filter query for collection properties * Defect/use raw uri for vocabularies (#321) * Update urls * update pom * Defect/use raw uri for vocabularies (#322) * Update urls * update pom * Defect/missing paging expand with cp (#323) * Introduction of default paging provider * Skip token shall be null * Handle LIMIT and OFFSET with TypedQuery (#324) * Defect/eclipse link hana problem (#325) * Handle LIMIT and OFFSET with TypedQuery * Update default for LIMIT * Check restrictions on IN clause (#326) * Correct NPE if enum property is nullable (#327) * Update README.md (#328) * Update README.md * Update README.md * Fix duplicates result multi expands issue#292 (#329) * Defect/enumeration convertion error (#330) * Update Version * Make use of @Enumerated and fix order issue * Update version to 2.1.2 * Suppress generation of defaults for LIMIT and OFFSET (#331) * Enable order by to one associations (#333) * Enhanced ETag handling for GET entity set requests (#332) * Enhanced ETag handling for GET entity set requests * Prepare ETag handling for functions and actions * Cleanup test for filter using db functions * Fix unit tests * ETag at Functions and Actions * Defect/orderby navigation to on (#334) * Enable order by to one associations * Group By at sub queries * Correct collection property select clause issues (#335) * Enable nested lambda expressions (#336) * Fix problem with expand and lambda to collection (#337) * Set version to 2.1.3 (#338) * Clean-up * Add missed files * Feature/release 2.1.3 (#339) * Set version to 2.1.3 * Update readme * Enable weak etags * Add word * Generalize unit test * Feature/release 2.1.3 (#340) * Set version to 2.1.3 * Update readme * Enable weak etags * Add word * Generalize unit test * Adopt validation header check to potential lower case conversion --- .gitignore | 2 +- README.md | 12 +- additionalWords.directory | 3 + .../odata-jpa-archetype-spring/pom.xml | 2 +- .../resources/archetype-resources/pom.xml | 4 +- .../main/java/controller/ODataController.java | 9 +- jpa-archetype/pom.xml | 6 +- jpa/.gitignore | 2 +- jpa/eclipse-codestyle-formatter.xml | 1001 +++++++---------- jpa/eclipse-import.importorder | 7 + jpa/odata-jpa-annotation/.project | 1 + jpa/odata-jpa-annotation/pom.xml | 2 +- .../converter/OffsetDateTimeConverter.java | 2 +- .../core/edm/annotation/EdmVisibleFor.java | 11 +- jpa/odata-jpa-coverage/pom.xml | 2 +- jpa/odata-jpa-metadata/pom.xml | 6 +- .../metadata/api/JPAEntityManagerFactory.java | 5 +- .../core/edm/mapper/api/JPAEntityType.java | 7 +- .../core/edm/mapper/api/JPAEtagValidator.java | 14 + .../edm/mapper/api/JPAStructuredType.java | 2 + .../mapper/impl/IntermediateEntityType.java | 27 +- .../edm/mapper/impl/IntermediateProperty.java | 18 +- .../impl/IntermediateStructuredType.java | 10 + .../impl/IntermediateEntityTypeTest.java | 75 +- .../mapper/impl/IntermediateSchemaTest.java | 4 +- .../impl/IntermediateSimplePropertyTest.java | 33 +- .../core/edm/mapper/impl/TestHelper.java | 3 +- jpa/odata-jpa-odata-vocabularies/pom.xml | 2 +- jpa/odata-jpa-processor-cb/pom.xml | 4 +- .../cb/impl/CriteriaBuilderImpl.java | 2 +- .../jpa/processor/cb/impl/TupleImpl.java | 14 +- ...teImplTest.java => PredicateImplTest.java} | 12 +- .../jpa/processor/cb/impl/TupleImplTest.java | 105 +- .../processor/cb/testobjects/UserType.java | 5 + jpa/odata-jpa-processor-ext/pom.xml | 8 +- jpa/odata-jpa-processor-parallel/README.md | 2 +- jpa/odata-jpa-processor-parallel/pom.xml | 2 +- .../test/util/IntegrationTestHelper.java | 3 +- jpa/odata-jpa-processor/.project | 6 - jpa/odata-jpa-processor/pom.xml | 8 +- .../core/api/JPADefaultPagingProvider.java | 22 +- .../core/api/JPAODataEtagHelper.java | 84 ++ .../api/JPAODataRequestContextAccess.java | 2 + .../core/api/JPAODataRequestHandler.java | 2 +- .../core/api/JPAODataTransactionFactory.java | 2 - .../api/example/JPAExamplePagingProvider.java | 1 - .../converter/JPAEntityResultConverter.java | 37 +- .../JPAStructuredResultConverter.java | 22 +- .../converter/JPATupleChildConverter.java | 2 +- .../converter/JPATupleResultConverter.java | 4 +- .../database/JPADefaultDatabaseProcessor.java | 9 +- .../JPAODataDatabaseTableFunction.java | 41 +- .../database/JPA_DERBY_DatabaseProcessor.java | 10 +- .../JPA_HSQLDB_DatabaseProcessor.java | 10 +- .../JPA_POSTSQL_DatabaseProcessor.java | 10 +- .../exception/ODataJPAProcessorException.java | 4 +- .../exception/ODataJPAQueryException.java | 1 + .../core/filter/JPAExistsOperation.java | 42 +- .../core/filter/JPALambdaOperation.java | 10 +- .../JPAAbstractRequestProcessor.java | 24 +- .../processor/JPAActionRequestProcessor.java | 3 +- .../processor/JPACUDRequestProcessor.java | 4 +- .../core/processor/JPACoreDebugger.java | 1 + .../processor/JPACountRequestProcessor.java | 2 +- .../processor/JPAETagValidationResult.java | 19 + .../JPAFunctionRequestProcessor.java | 6 +- .../JPANavigationRequestProcessor.java | 99 +- .../processor/JPAODataEtagHelperImpl.java | 58 + .../JPAODataInternalRequestContext.java | 12 +- .../JPAOperationRequestProcessor.java | 33 +- .../core/processor/JPAProcessorFactory.java | 6 +- .../core/query/JPAAbstractJoinQuery.java | 98 +- .../core/query/JPAAbstractQuery.java | 66 +- .../query/JPACollectionExpandWrapper.java | 73 +- .../core/query/JPACollectionItemInfo.java | 6 +- .../core/query/JPACollectionJoinQuery.java | 29 +- .../core/query/JPAExpandFilterQuery.java | 59 +- .../core/query/JPAExpandItemInfoFactory.java | 29 +- .../core/query/JPAExpandJoinQuery.java | 22 +- .../core/query/JPAExpandSubQuery.java | 14 +- .../processor/core/query/JPAJoinQuery.java | 26 +- .../core/query/JPAOrderByBuilder.java | 51 +- .../jpa/processor/core/query/Utility.java | 97 +- .../processor-exceptions-i18n.properties | 1 + .../api/JPADefaultPagingProviderTest.java | 16 + .../core/api/JPAODataContextAccessDouble.java | 2 +- .../JPAODataServiceContextBuilderTest.java | 3 +- .../JPAEntityResultConverterTest.java} | 40 +- .../JPAODataDatabaseTableFunctionTest.java | 40 + .../JPA_XXX_DatabaseProcessorTest.java | 52 +- .../core/filter/TestJPAQueryWhereClause.java | 304 +++-- .../core/filter/TestJavaFunctions.java | 7 +- .../core/filter/TestScalarDbFunctions.java | 114 ++ .../core/modify/TestJPACreateResult.java | 4 +- .../JPAActionRequestProcessorTest.java | 279 +++-- .../JPAFunctionRequestProcessorTest.java | 141 ++- .../JPAJavaFunctionProcessorTest.java | 42 +- .../JPANavigationRequestProcessorTest.java | 180 +++ .../processor/JPAODataEtagHelperImplTest.java | 117 ++ .../JPAODataInternalRequestContextTest.java | 43 +- .../processor/TestJPACreateProcessor.java | 66 +- .../processor/TestJPAModifyProcessor.java | 12 +- .../TestJPAODataRequestContextImpl.java | 26 +- .../processor/TestJPAUpdateProcessor.java | 61 +- .../query/JPACollectionExpandWrapperTest.java | 2 +- .../core/query/JPAExpandJoinQueryTest.java | 20 +- ...=> JPAFunctionRequestProcessorDbTest.java} | 34 +- .../core/query/JPAJoinQueryTest.java | 17 +- .../query/JPANavigationCountQueryTest.java | 2 +- .../query/JPANavigationFilterQueryTest.java | 2 +- .../query/JPANavigationNullQueryTest.java | 2 +- ...uilder.java => JPAOrderByBuilderTest.java} | 177 ++- .../query/TestJPAExpandQueryCreateResult.java | 5 +- .../processor/core/query/TestJPAFunction.java | 41 +- .../core/query/TestJPAFunctionSerializer.java | 21 +- .../core/query/TestJPAProcessorExpand.java | 37 - .../TestJPAQueryBuildSelectionPathList.java | 8 +- .../core/query/TestJPAQueryCollection.java | 110 +- .../core/query/TestJPAQueryFromClause.java | 12 +- .../core/query/TestJPAQueryHandlesETag.java | 78 ++ .../core/query/TestJPAQueryOrderByClause.java | 54 +- .../core/query/TestJPAQuerySelectClause.java | 8 +- .../TestJPAQuerySelectWithGroupClause.java | 14 +- .../core/query/TestJPAQueryTopSkip.java | 14 +- .../query/TestJPAQueryWithProtection.java | 12 +- .../core/query/TestJPAServerDrivenPaging.java | 4 +- .../query/TestJPATupleChildConverter.java | 7 +- ...TestJPATupleChildConverterCompoundKey.java | 5 +- .../jpa/processor/core/query/UtilityTest.java | 78 +- .../TestFunctionActionConstructor.java | 9 +- .../testobjects/TestFunctionReturnType.java | 1 + .../core/testobjects/TestJavaActions.java | 8 + .../core/util/IntegrationTestHelper.java | 29 +- .../core/util/JPAEntityTypeDouble.java | 7 + .../jpa/processor/core/util/TestBase.java | 11 +- .../processor/core/util/TestGroupBase.java | 5 +- .../processor/core/util/TestQueryBase.java | 7 +- jpa/odata-jpa-spring-support/pom.xml | 6 +- jpa/odata-jpa-test/pom.xml | 2 +- .../testmodel/AssociationOneToOneSource.java | 2 +- .../testmodel/AssociationOneToOneTarget.java | 3 + .../core/testmodel/DataSourceHelper.java | 5 + .../core/testmodel/DeepProtectedExample.java | 34 +- .../testmodel/PostalAddressDataWithGroup.java | 1 + .../testmodel/TimestampLongConverter.java | 27 + .../jpa/processor/core/testmodel/User.java | 30 + .../processor/core/testmodel/UserType.java | 8 + .../core/util/TestDataConstants.java | 4 +- .../db/migration/V1_1__SchemaMigration.java | 162 +++ .../main/resources/META-INF/persistence.xml | 12 +- .../resources/db/migration/V1_0__olingo.sql | 6 +- jpa/odata-jpa-vocabularies/pom.xml | 2 +- jpa/pom.xml | 751 +++++++------ 153 files changed, 3917 insertions(+), 2124 deletions(-) create mode 100644 jpa/eclipse-import.importorder create mode 100644 jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEtagValidator.java rename jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/{PrerdicateImplTest.java => PredicateImplTest.java} (94%) create mode 100644 jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/testobjects/UserType.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataEtagHelper.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java rename jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/{query/TestJPAInstanceResultConverter.java => converter/JPAEntityResultConverterTest.java} (72%) create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunctionTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessorTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImplTest.java rename jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/{TestJPAFunctionDB.java => JPAFunctionRequestProcessorDbTest.java} (82%) rename jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/{TestJPAOrderByBuilder.java => JPAOrderByBuilderTest.java} (75%) create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryHandlesETag.java create mode 100644 jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java create mode 100644 jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/UserType.java create mode 100644 jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java diff --git a/.gitignore b/.gitignore index d927c9022..be115f65f 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,4 @@ hs_err_pid* # --- Derby stuff **/jpa-processor/test/** /.vscode/ -/.idea/ +/.idea/ diff --git a/README.md b/README.md index 936d7f354..e8956fd99 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ clone the repository, import the projects and declare a dependency to either the com.sap.olingo odata-jpa-metadata - 2.1.1 + 2.1.3 ``` @@ -57,7 +57,7 @@ Or to the complete processor: com.sap.olingo odata-jpa-processor - 2.1.1 + 2.1.3 ``` @@ -101,4 +101,10 @@ Detailed information including third-party components and their licensing/copyri |2.0.0| - Minimum Java release now 17
- Switch to Jakarta Persistence
- Support of Spring Boot 3.x
- JPAEdmMetadataPostProcessor became an interface |Yes| |2.0.2| - Solution for issue [#239](https://github.com/SAP/olingo-jpa-processor-v4/issues/239)
- Partial solution for issue [#226](https://github.com/SAP/olingo-jpa-processor-v4/issues/226)
- Solution for issue [#238](https://github.com/SAP/olingo-jpa-processor-v4/issues/238) and [#236](https://github.com/SAP/olingo-jpa-processor-v4/issues/236)|No| |2.1.0| - Enhancement of annotation API
- Enhancement of API for server driven paging
- Optional support of IN operand
- Update to Olingo 5.0.0
- Rework $count implementation
- Fix problem with $count on collection properties|No| -|2.1.1| - Fix for issue [#292] (https://github.com/SAP/olingo-jpa-processor-v4/issues/292)|No| +|2.1.1| - Fix for issue [#292](https://github.com/SAP/olingo-jpa-processor-v4/issues/292)|No| +|2.1.3| + - Fix for issue [#319](https://github.com/SAP/olingo-jpa-processor-v4/issues/319)
+ - Fix for issue [#325](https://github.com/SAP/olingo-jpa-processor-v4/issues/325)
+ - Fix for issue [#327](https://github.com/SAP/olingo-jpa-processor-v4/issues/327)
+ - Fix for issue [#331](https://github.com/SAP/olingo-jpa-processor-v4/issues/331)
+ - Fix of en issue with $orderby and _to one_ navigation properties |No| diff --git a/additionalWords.directory b/additionalWords.directory index 6244fae27..e58801bab 100644 --- a/additionalWords.directory +++ b/additionalWords.directory @@ -62,3 +62,6 @@ Redis icao iata MULTI +Asc +Validator +olingo diff --git a/jpa-archetype/odata-jpa-archetype-spring/pom.xml b/jpa-archetype/odata-jpa-archetype-spring/pom.xml index c586159d7..c07394782 100644 --- a/jpa-archetype/odata-jpa-archetype-spring/pom.xml +++ b/jpa-archetype/odata-jpa-archetype-spring/pom.xml @@ -4,7 +4,7 @@ com.sap.olingo odata-jpa-archetype - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-archetype-spring Archetype - odata-jpa-archetype-spring 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 458a1954d..73163d341 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 @@ -17,10 +17,10 @@ - 2.1.3-SNAPSHOT + 2.1.3 17 UTF-8 - 4.0.2 + 4.0.3 3.1.0 diff --git a/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/src/main/java/controller/ODataController.java b/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/src/main/java/controller/ODataController.java index 35c78caa2..4f3ddbef6 100644 --- a/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/src/main/java/controller/ODataController.java +++ b/jpa-archetype/odata-jpa-archetype-spring/src/main/resources/archetype-resources/src/main/java/controller/ODataController.java @@ -19,10 +19,15 @@ @RequestScope public class ODataController { - @Autowired private JPAODataSessionContextAccess serviceContext; - @Autowired private JPAODataRequestContext requestContext; + + public ODataController(@Autowired JPAODataSessionContextAccess serviceContext, + @Autowired JPAODataRequestContext requestContext) { + super(); + this.serviceContext = serviceContext; + this.requestContext = requestContext; + } @RequestMapping(value = "**", method = { RequestMethod.GET, RequestMethod.PATCH, // NOSONAR RequestMethod.POST, RequestMethod.DELETE }) diff --git a/jpa-archetype/pom.xml b/jpa-archetype/pom.xml index a2c272acc..476f805fd 100644 --- a/jpa-archetype/pom.xml +++ b/jpa-archetype/pom.xml @@ -4,14 +4,14 @@ 4.0.0 com.sap.olingo odata-jpa-archetype - 2.1.3-SNAPSHOT + 2.1.3 pom https://github.com/SAP/olingo-jpa-processor-v4 - UTF-8 + UTF-8 17 - 2.1.3-SNAPSHOT + 2.1.3 diff --git a/jpa/.gitignore b/jpa/.gitignore index 6012aab3d..9ecd51296 100644 --- a/jpa/.gitignore +++ b/jpa/.gitignore @@ -8,4 +8,4 @@ target/ # --- EclipseIDE stuff START .settings/ .metadata -*.log +*.log \ No newline at end of file diff --git a/jpa/eclipse-codestyle-formatter.xml b/jpa/eclipse-codestyle-formatter.xml index 27c52f889..f525de7c5 100644 --- a/jpa/eclipse-codestyle-formatter.xml +++ b/jpa/eclipse-codestyle-formatter.xml @@ -1,601 +1,404 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jpa/eclipse-import.importorder b/jpa/eclipse-import.importorder new file mode 100644 index 000000000..d0495f73f --- /dev/null +++ b/jpa/eclipse-import.importorder @@ -0,0 +1,7 @@ +#Organize Import Order +#Sun Jun 09 11:30:55 CEST 2024 +0=java +1=javax +2=jakarta +3=org +4=com diff --git a/jpa/odata-jpa-annotation/.project b/jpa/odata-jpa-annotation/.project index 3e42f49f5..166e055d8 100644 --- a/jpa/odata-jpa-annotation/.project +++ b/jpa/odata-jpa-annotation/.project @@ -35,6 +35,7 @@ org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.common.modulecore.ModuleCoreNature diff --git a/jpa/odata-jpa-annotation/pom.xml b/jpa/odata-jpa-annotation/pom.xml index 8f6c0137b..18909afda 100644 --- a/jpa/odata-jpa-annotation/pom.xml +++ b/jpa/odata-jpa-annotation/pom.xml @@ -7,7 +7,7 @@ com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-annotation odata-jpa-annotation diff --git a/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/converter/OffsetDateTimeConverter.java b/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/converter/OffsetDateTimeConverter.java index f4c8a5235..7be56901f 100644 --- a/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/converter/OffsetDateTimeConverter.java +++ b/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/converter/OffsetDateTimeConverter.java @@ -10,7 +10,7 @@ * Default converter to convert from {@link java.time.OffsetDateTime} to {@link java.time.ZonedDateTime}. This is * required, as Olingo 4.7.1 only supports ZonedDateTime, where as JPA 2.2 supports OffsetDateTime. * @author Oliver Grande - * Created: 09.03.2020 + * @since 09.03.2020 * */ @Converter(autoApply = false) diff --git a/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/core/edm/annotation/EdmVisibleFor.java b/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/core/edm/annotation/EdmVisibleFor.java index d06d750fb..2ff174083 100644 --- a/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/core/edm/annotation/EdmVisibleFor.java +++ b/jpa/odata-jpa-annotation/src/main/java/com/sap/olingo/jpa/metadata/core/edm/annotation/EdmVisibleFor.java @@ -7,13 +7,14 @@ import java.lang.annotation.Target; /** - * The annotation can be used to assign attributes or properties to field or visibility groups. On case such a group is + * The annotation can be used to assign attributes or properties to field or visibility groups. In case such a group is * provided during a GET request all properties that are assigned to that group and all properties that are assigned to - * no group, or in other words that are not annotated, get selected. In case properties that belong to another group are - * requested, a null value is returned.

- * + * no group, or in other words that are not annotated, get selected. For properties that belong to another group are + * requested, a null value is returned. + *

+ * * Note: Keys, mandatory fields as well as association or navigation properties can not be annotated - * + * * @author Oliver Grande * */ diff --git a/jpa/odata-jpa-coverage/pom.xml b/jpa/odata-jpa-coverage/pom.xml index 9f2bd8e0b..7c0433792 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.3-SNAPSHOT + 2.1.3 odata-jpa-coverage diff --git a/jpa/odata-jpa-metadata/pom.xml b/jpa/odata-jpa-metadata/pom.xml index 129996100..fcb8d73ca 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.3-SNAPSHOT + com.sap.olingo + odata-jpa + 2.1.3 odata-jpa-metadata diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/api/JPAEntityManagerFactory.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/api/JPAEntityManagerFactory.java index fee0dc3d0..0dc5ca7f9 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/api/JPAEntityManagerFactory.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/api/JPAEntityManagerFactory.java @@ -28,8 +28,9 @@ public static EntityManagerFactory getEntityManagerFactory(final String pUnit, f final Map dsMap = emfMap.get(pUnit); EntityManagerFactory emf = dsMap.get(dsKey); - if (emf != null) - return emf; + if (emf != null) { + return emf; + } emf = Persistence.createEntityManagerFactory(pUnit, ds); dsMap.put(dsKey, emf); return emf; diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEntityType.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEntityType.java index 080722156..42e89afef 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEntityType.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEntityType.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Optional; +import javax.annotation.CheckForNull; + import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmQueryExtensionProvider; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; @@ -75,9 +77,10 @@ public interface JPAEntityType extends JPAStructuredType, JPAAnnotatable { public boolean hasEtag() throws ODataJPAModelException; - public boolean hasStream() throws ODataJPAModelException; + @CheckForNull + public JPAEtagValidator getEtagValidator() throws ODataJPAModelException; - public List searchChildPath(final JPAPath selectItemPath); + public boolean hasStream() throws ODataJPAModelException; public Optional> getQueryExtension() throws ODataJPAModelException; diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEtagValidator.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEtagValidator.java new file mode 100644 index 000000000..2bef8afe9 --- /dev/null +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAEtagValidator.java @@ -0,0 +1,14 @@ +package com.sap.olingo.jpa.metadata.core.edm.mapper.api; + +/** + * Strength of the entity tag (ETag) validator according to + * RFC 7232 Section-2.1
+ * + * @author Oliver Grande + * @since 25.06.2024 + * @version 2.1.3 + */ +public enum JPAEtagValidator { + WEAK, + STRONG +} diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAStructuredType.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAStructuredType.java index c6367b83d..b07263ecd 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAStructuredType.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/api/JPAStructuredType.java @@ -153,4 +153,6 @@ public Optional getAttribute(@Nonnull final UriResourceProperty ur */ @CheckForNull public JPAStructuredType getBaseType(); + + public List searchChildPath(final JPAPath selectItemPath); } 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 979d3fd37..e15381d83 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 @@ -41,6 +41,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; 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.JPAEtagValidator; 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.exception.ODataJPAModelException; @@ -60,6 +61,7 @@ final class IntermediateEntityType extends IntermediateStructuredType impl IntermediateEntityTypeAccess { private Optional etagPath; + private Optional etagValidator; private Optional>> extensionQueryProvider; private List keyAttributes; private final boolean asTopLevelOnly; @@ -183,6 +185,13 @@ public JPAPath getEtagPath() throws ODataJPAModelException { return null; } + @Override + public JPAEtagValidator getEtagValidator() throws ODataJPAModelException { + if (hasEtag()) + return etagValidator.orElse(null); + return null; + } + @Override public List getKey() throws ODataJPAModelException { if (!hasBuildStepPerformed(PROPERTY_BUILD)) { @@ -308,16 +317,6 @@ public boolean isAbstract() { return determineAbstract(); } - @Override - public List searchChildPath(final JPAPath selectItemPath) { - final List result = new ArrayList<>(); - for (final JPAPath path : this.resolvedPathMap.values()) { - if (!path.ignore() && path.getAlias().startsWith(selectItemPath.getAlias())) - result.add(path); - } - return result; - } - @SuppressWarnings("unchecked") @Override protected List extractEdmModelElements( @@ -351,6 +350,7 @@ protected synchronized void lazyBuildEdmItem() throws ODataJPAModelException { postProcessor.processEntityType(this); retrieveAnnotations(this, Applicability.ENTITY_TYPE); edmStructuralType = new CsdlEntityType(); + determineHasEtag(); edmStructuralType.setName(getExternalName()); edmStructuralType.setProperties(extractEdmModelElements(declaredPropertiesMap)); edmStructuralType.setNavigationProperties(extractEdmModelElements( @@ -360,7 +360,6 @@ protected synchronized void lazyBuildEdmItem() throws ODataJPAModelException { edmStructuralType.setBaseType(determineBaseType()); ((CsdlEntityType) edmStructuralType).setHasStream(determineHasStream()); edmStructuralType.setAnnotations(determineAnnotations()); - determineHasEtag(); checkPropertyConsistency(); // // TODO determine OpenType } @@ -529,10 +528,14 @@ private void determineHasEtag() throws ODataJPAModelException { for (final Entry property : this.declaredPropertiesMap.entrySet()) { if (property.getValue().isEtag()) { etagPath = Optional.of(getPath(property.getValue().getExternalName(), false)); + etagValidator = Optional.of(Number.class.isAssignableFrom(property.getValue().getJavaType()) + ? JPAEtagValidator.STRONG : JPAEtagValidator.WEAK); } } - if (getBaseType() instanceof final IntermediateEntityType baseEntityType) + if (getBaseType() instanceof final IntermediateEntityType baseEntityType) { etagPath = Optional.ofNullable(baseEntityType.getEtagPath()); + etagValidator = Optional.ofNullable(baseEntityType.getEtagValidator()); + } } private Optional getAnnotation(final Class annotated, final Class type) { diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateProperty.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateProperty.java index 94d7cbfa7..7eb4135f3 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateProperty.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateProperty.java @@ -25,6 +25,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.EntityManager; +import jakarta.persistence.Enumerated; import jakarta.persistence.Lob; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; @@ -230,6 +231,7 @@ protected void buildProperty(final JPAEdmNameBuilder nameBuilder) throws ODataJP if (this.jpaAttribute.getJavaMember() instanceof AnnotatedElement) { retrieveAnnotations(this, Applicability.PROPERTY); determineIgnore(); + determineIsEnum(); determineStructuredType(); determineInternalTypesFromConverter(); determineDBFieldName(); @@ -239,7 +241,6 @@ protected void buildProperty(final JPAEdmNameBuilder nameBuilder) throws ODataJP determineIsVersion(); determineProtection(); determineFieldGroups(); - determineIsEnum(); checkConsistency(); } postProcessor.processProperty(this, jpaAttribute.getDeclaringType().getJavaType().getCanonicalName()); @@ -561,7 +562,6 @@ private void determineDBFieldName() { dbFieldName = jpaColumnDetails.name(); if (dbFieldName.isEmpty()) { final var stringBuilder = new StringBuilder(DB_FIELD_NAME_PATTERN); - stringBuilder.replace(1, 3, internalName); dbFieldName = stringBuilder.toString(); } @@ -622,6 +622,20 @@ private void determineInternalTypesFromConverter() throws ODataJPAModelException throw new ODataJPAModelException( ODataJPAModelException.MessageKeys.TYPE_MAPPER_COULD_NOT_INSTANTIATED, e); } + } else { + final var enumerated = ((AnnotatedElement) this.jpaAttribute.getJavaMember()) + .getAnnotation(Enumerated.class); + if (enumerated != null) { + switch (enumerated.value()) { + case ORDINAL -> dbType = Integer.class; + case STRING -> dbType = String.class; + default -> throw new IllegalArgumentException("Unexpected value: " + enumerated.value()); + } + conversionRequired = false; + } else if (isEnum) { + dbType = Integer.class; + conversionRequired = false; + } } } diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateStructuredType.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateStructuredType.java index dc0d8964b..0138d770e 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateStructuredType.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateStructuredType.java @@ -286,6 +286,16 @@ public List getPathList() throws ODataJPAModelException { return pathList; } + @Override + public List searchChildPath(final JPAPath selectItemPath) { + final List result = new ArrayList<>(); + for (final JPAPath path : this.resolvedPathMap.values()) { + if (!path.ignore() && path.getAlias().startsWith(selectItemPath.getAlias())) + result.add(path); + } + return result; + } + @Override public List getProtections() throws ODataJPAModelException { lazyBuildCompleteProtectionList(); 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 ba95ea1b1..8c1cfe60b 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 @@ -44,6 +44,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEtagValidator; 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.JPAProtectionInfo; @@ -107,7 +108,7 @@ void setup() throws ODataJPAModelException { } @Test - void checkEntityTypeCanBeCreated() throws ODataJPAModelException { + void checkEntityTypeCanBeCreated() { assertNotNull(new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema)); @@ -445,33 +446,61 @@ void checkHasStreamFalse() throws ODataJPAModelException { } @Test - void checkHasETagTrue() throws ODataJPAModelException { + void checkHasEtagTrue() throws ODataJPAModelException { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); assertTrue(et.hasEtag()); } @Test - void checkHasETagTrueIfInherited() throws ODataJPAModelException { + void checkHasEtagTrueIfInherited() throws ODataJPAModelException { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(Organization.class), schema); assertTrue(et.hasEtag()); } @Test - void checkHasETagFalse() throws ODataJPAModelException { + void checkHasEtagFalse() throws ODataJPAModelException { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AdministrativeDivision.class), schema); assertFalse(et.hasEtag()); } @Test - void checkIgnoreIfAsEntitySet() throws ODataJPAModelException { + void checkIgnoreIfAsEntitySet() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BestOrganization.class), schema); assertTrue(et.ignore()); } + @Test + void checkEtagValidatorNullWithoutEtag() throws ODataJPAModelException { + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(AdministrativeDivision.class), schema); + assertNull(et.getEtagValidator()); + } + + @Test + void checkEtagValidatorIsStrongForLong() throws ODataJPAModelException { + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(BusinessPartner.class), schema); + assertEquals(JPAEtagValidator.STRONG, et.getEtagValidator()); + } + + @Test + void checkEtagValidatorIsWeakForTimestamp() throws ODataJPAModelException { + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(DeepProtectedExample.class), schema); + assertEquals(JPAEtagValidator.WEAK, et.getEtagValidator()); + } + + @Test + void checkEtagValidatorIsInherited() throws ODataJPAModelException { + final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( + PUNIT_NAME), getEntityType(Person.class), schema); + assertEquals(JPAEtagValidator.STRONG, et.getEtagValidator()); + } + @Test void checkAnnotationSet() throws ODataJPAModelException { IntermediateModelElement.setPostProcessor(new PostProcessorSetIgnore()); @@ -628,21 +657,21 @@ void checkComplexAndInheritedProtectedProperty() throws ODataJPAModelException { } @Test - void checkEmbeddedIdKeyIsCompound() throws ODataJPAModelException { + void checkEmbeddedIdKeyIsCompound() { final IntermediateEntityType et = new IntermediateEntityType<>( new JPADefaultEdmNameBuilder(PUNIT_NAME), getEntityType(AdministrativeDivisionDescription.class), schema); assertTrue(et.hasCompoundKey()); } @Test - void checkMultipleKeyIsCompound() throws ODataJPAModelException { + void checkMultipleKeyIsCompound() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(AdministrativeDivision.class), schema); assertTrue(et.hasCompoundKey()); } @Test - void checkIdIsNotCompound() throws ODataJPAModelException { + void checkIdIsNotCompound() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); assertFalse(et.hasCompoundKey()); @@ -687,7 +716,7 @@ void checkTransientWithReferenceIgnore() throws ODataJPAModelException { } @Test - void checkTransientThrowsExceptionWithReferenceUnknown() throws ODataJPAModelException { + void checkTransientThrowsExceptionWithReferenceUnknown() { final EntityType jpaEt = errorEmf.getMetamodel().entity(TeamWithTransientError.class); final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( ERROR_PUNIT), jpaEt, errorSchema); @@ -707,7 +736,7 @@ void checkEntityWithMappedSuperClassContainsAllTransient() throws ODataJPAModelE } @Test - void checkAsSingletonReturnsTrueIfTypeIsAnnotated() throws ODataJPAModelException { + void checkAsSingletonReturnsTrueIfTypeIsAnnotated() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder(PUNIT_NAME), getEntityType(Singleton.class), schema); assertTrue(et.asSingleton()); @@ -729,14 +758,14 @@ void checkAnnotatedAsEntityType() { } @Test - void checkAsEntitySetWithoutAnnotation() throws ODataJPAModelException { + void checkAsEntitySetWithoutAnnotation() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(Organization.class), schema); assertTrue(et.asEntitySet()); } @Test - void checkAsSingletonOnly() throws ODataJPAModelException { + void checkAsSingletonOnly() { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( ERROR_PUNIT), getEntityType(CurrentUser.class), schema); assertTrue(et.asSingleton()); @@ -861,7 +890,7 @@ private void assertComplexDeep(final List act) { } @Test - void checkConvertStringToPathWithSimplePath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToPathWithSimplePath() throws ODataPathNotFoundException { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final ODataPropertyPath act = et.convertStringToPath("type"); @@ -870,7 +899,7 @@ void checkConvertStringToPathWithSimplePath() throws ODataJPAModelException, ODa } @Test - void checkConvertStringToPathWithComplexPath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToPathWithComplexPath() throws ODataPathNotFoundException { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final ODataPropertyPath act = et.convertStringToPath("administrativeInformation/updated/by"); @@ -879,7 +908,7 @@ void checkConvertStringToPathWithComplexPath() throws ODataJPAModelException, OD } @Test - void checkConvertStringToPathWithSimpleCollectionPath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToPathWithSimpleCollectionPath() throws ODataPathNotFoundException { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(Organization.class), schema); final ODataPropertyPath act = et.convertStringToPath("comment"); @@ -888,7 +917,7 @@ void checkConvertStringToPathWithSimpleCollectionPath() throws ODataJPAModelExce } @Test - void checkConvertStringToPathWithComplexCollectionPath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToPathWithComplexCollectionPath() throws ODataPathNotFoundException { final IntermediateEntityType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(Collection.class), schema); final ODataPropertyPath act = et.convertStringToPath("nested"); @@ -897,14 +926,14 @@ void checkConvertStringToPathWithComplexCollectionPath() throws ODataJPAModelExc } @Test - void checkConvertStringToPathThrowsExceptionUnknownPart() throws ODataJPAModelException { + void checkConvertStringToPathThrowsExceptionUnknownPart() { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); assertThrows(ODataPathNotFoundException.class, () -> et.convertStringToPath("administrativeInformation/test/by")); } @Test - void checkConvertStringToPathThrowsExceptionFirstUnknown() throws ODataJPAModelException { + void checkConvertStringToPathThrowsExceptionFirstUnknown() { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final ODataPathNotFoundException act = assertThrows(ODataPathNotFoundException.class, () -> et.convertStringToPath( @@ -913,7 +942,7 @@ void checkConvertStringToPathThrowsExceptionFirstUnknown() throws ODataJPAModelE } @Test - void checkConvertStringToPathThrowsExceptionPartIsNotComplex() throws ODataJPAModelException { + void checkConvertStringToPathThrowsExceptionPartIsNotComplex() { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(CollectionDeep.class), schema); final ODataPathNotFoundException act = assertThrows(ODataPathNotFoundException.class, () -> et.convertStringToPath( @@ -922,7 +951,7 @@ void checkConvertStringToPathThrowsExceptionPartIsNotComplex() throws ODataJPAMo } @Test - void checkConvertStringToNavigationPathWithSimplePath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToNavigationPathWithSimplePath() throws ODataPathNotFoundException { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final ODataNavigationPath act = et.convertStringToNavigationPath("roles"); @@ -931,7 +960,7 @@ void checkConvertStringToNavigationPathWithSimplePath() throws ODataJPAModelExce } @Test - void checkConvertStringToPathNavigationWithComplexPath() throws ODataJPAModelException, ODataPathNotFoundException { + void checkConvertStringToPathNavigationWithComplexPath() throws ODataPathNotFoundException { final IntermediateStructuredType et = new IntermediateEntityType<>(new JPADefaultEdmNameBuilder( PUNIT_NAME), getEntityType(BusinessPartner.class), schema); final ODataNavigationPath act = et.convertStringToNavigationPath("address/administrativeDivision"); @@ -940,7 +969,7 @@ void checkConvertStringToPathNavigationWithComplexPath() throws ODataJPAModelExc } @Test - void checkConvertStringToPathNavigationThrowsExceptionOnMultipleNavigations() throws ODataJPAModelException { + void checkConvertStringToPathNavigationThrowsExceptionOnMultipleNavigations() { final IntermediateStructuredType et = new IntermediateEntityType<>( new JPADefaultEdmNameBuilder(PUNIT_NAME), getEntityType(AdministrativeDivision.class), schema); final ODataPathNotFoundException act = assertThrows(ODataPathNotFoundException.class, () -> et @@ -950,7 +979,7 @@ void checkConvertStringToPathNavigationThrowsExceptionOnMultipleNavigations() th } @Test - void checkGetJoinColumnsThrowsIfNotExist() throws ODataJPAModelException { + void checkGetJoinColumnsThrowsIfNotExist() { final IntermediateStructuredType et = new IntermediateEntityType<>( new JPADefaultEdmNameBuilder(PUNIT_NAME), getEntityType(AdministrativeDivision.class), schema); final ODataJPAModelException act = assertThrows(ODataJPAModelException.class, () -> et.getJoinColumns("test")); diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSchemaTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSchemaTest.java index 2b078d10c..67b22996f 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSchemaTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSchemaTest.java @@ -28,6 +28,7 @@ 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.AccessRights; +import com.sap.olingo.jpa.processor.core.testmodel.UserType; import com.sap.olingo.jpa.processor.core.util.TestDataConstants; class IntermediateSchemaTest extends TestMappingRoot { @@ -39,7 +40,8 @@ void setup() { reflections = mock(Reflections.class); when(reflections.getTypesAnnotatedWith(EdmEnumeration.class)).thenReturn(new HashSet<>(Arrays.asList( ABCClassification.class, - AccessRights.class))); + AccessRights.class, + UserType.class))); annotationInfo = new IntermediateAnnotationInformation(new ArrayList<>()); } 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 2ec8db7f5..1f5640747 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 @@ -79,6 +79,7 @@ import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.testmodel.PersonImage; import com.sap.olingo.jpa.processor.core.testmodel.PostalAddressData; +import com.sap.olingo.jpa.processor.core.testmodel.User; class IntermediateSimplePropertyTest extends TestMappingRoot { private TestHelper helper; @@ -146,8 +147,7 @@ void checkGetPropertyEnumTypeWithoutConverter() throws ODataJPAModelException { void checkGetPropertyEnumTypeWithoutConverterMustNotHaveMapper() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Organization.class), "aBCClass"); final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, - helper.schema); + jpaAttribute, helper.schema); assertNull(property.getEdmItem().getMapping()); } @@ -155,17 +155,24 @@ void checkGetPropertyEnumTypeWithoutConverterMustNotHaveMapper() throws ODataJPA void checkGetPropertyEnumTypeWithConverter() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(Person.class), "accessRights"); final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, - helper.schema); + jpaAttribute, helper.schema); assertEquals("com.sap.olingo.jpa.AccessRights", property.getEdmItem().getType(), "Wrong type"); } + @Test + void checkGetPropertyEnumTypeWithEnumerated() throws ODataJPAModelException { + final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(User.class), "userType"); + final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), + jpaAttribute, helper.schema); + assertEquals(String.class, property.getDbType()); + assertNotNull(property.getEdmItem()); + } + @Test void checkGetPropertyIgnoreFalse() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "type"); final IntermediatePropertyAccess property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, - helper.schema); + jpaAttribute, helper.schema); assertFalse(property.ignore()); } @@ -174,8 +181,7 @@ void checkGetPropertyIgnoreTrue() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customString1"); final IntermediatePropertyAccess property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, - helper.schema); + jpaAttribute, helper.schema); assertTrue(property.ignore()); } @@ -184,8 +190,7 @@ void checkGetPropertyFacetsNullableTrue() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType(BusinessPartner.class), "customString1"); final var property = new IntermediateSimpleProperty(new JPADefaultEdmNameBuilder(PUNIT_NAME), - jpaAttribute, - helper.schema); + jpaAttribute, helper.schema); assertTrue(property.getEdmItem().isNullable()); } @@ -1007,11 +1012,11 @@ private void createAnnotation() { 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 typeDefintion = mock(CsdlTypeDefinition.class); + when(typeDefintion.getName()).thenReturn("Tag"); + when(typeDefintion.getUnderlyingType()).thenReturn("Edm.Boolean"); final var terms = AnnotationTestHelper.addTermToCoreReferences(reference, "ComputedDefaultValue", "Tag", - typeDefinition); + typeDefintion); when(reference.convertAlias("Core")).thenReturn("Org.OData.Core.V1"); when(reference.getTerms("Core", Applicability.PROPERTY)) diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/TestHelper.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/TestHelper.java index f72133c11..b8a886119 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/TestHelper.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/TestHelper.java @@ -27,6 +27,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.testmodel.ABCClassification; import com.sap.olingo.jpa.processor.core.testmodel.AccessRights; +import com.sap.olingo.jpa.processor.core.testmodel.UserType; public class TestHelper { final private Metamodel jpaMetamodel; @@ -42,7 +43,7 @@ public TestHelper(final Metamodel metamodel, final String namespace, final Reflections reflections = mock(Reflections.class); when(reflections.getTypesAnnotatedWith(EdmEnumeration.class)).thenReturn(new HashSet<>(Arrays.asList( - ABCClassification.class, AccessRights.class))); + ABCClassification.class, AccessRights.class, UserType.class))); annotationInfo = new IntermediateAnnotationInformation(new ArrayList<>(), mock(IntermediateReferences.class)); this.jpaMetamodel = metamodel; diff --git a/jpa/odata-jpa-odata-vocabularies/pom.xml b/jpa/odata-jpa-odata-vocabularies/pom.xml index 2fb36bdd4..76fb4562e 100644 --- a/jpa/odata-jpa-odata-vocabularies/pom.xml +++ b/jpa/odata-jpa-odata-vocabularies/pom.xml @@ -3,7 +3,7 @@ com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-odata-vocabularies odata-jpa-odata-vocabularies diff --git a/jpa/odata-jpa-processor-cb/pom.xml b/jpa/odata-jpa-processor-cb/pom.xml index 0e1235d67..0084deef5 100644 --- a/jpa/odata-jpa-processor-cb/pom.xml +++ b/jpa/odata-jpa-processor-cb/pom.xml @@ -3,9 +3,9 @@ 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 + com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-processor-cb 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 346a50df5..30504eb67 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 @@ -561,7 +561,7 @@ public In>> in(final List>> paths, */ @Override public In in(final Path path) { - return new PredicateImpl.In<>(path, parameter); + return new PredicateImpl.In<>(path, parameter); } /** 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 3eb308f37..8a96da6cd 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 @@ -91,11 +91,19 @@ public Object get(final String alias) { final JPAAttribute attribute = selection.get(index).getValue(); if (values[index] == null) return null; + final Object value = convert(values[index], attribute.getDbType()); if (attribute.isEnum() && attribute.getConverter() == null) { - final int value = (Integer) convert(values[index], Integer.class); - return attribute.getType().getEnumConstants()[value]; + if (value instanceof String) { + for (final var enumValue : attribute.getType().getEnumConstants()) { + if (enumValue.toString().equals(value)) + return enumValue; + } + } else if (value instanceof final Integer ordinal) { + return attribute.getType().getEnumConstants()[ordinal]; + } else { + throw new IllegalArgumentException("Unsupported tagets type: " + attribute.getDbType()); + } } - final Object value = convert(values[index], attribute.getDbType()); if (attribute.getRawConverter() != null) return attribute.getRawConverter().convertToEntityAttribute(value); return value; 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/PredicateImplTest.java similarity index 94% rename from jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PrerdicateImplTest.java rename to jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImplTest.java index 37e369c88..75e60c11a 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/PredicateImplTest.java @@ -126,9 +126,9 @@ void testInAsSqlGeneratePath() { when(((SqlConvertible) subQuery).asSQL(statement)).thenAnswer(new Answer() { @Override public StringBuilder answer(final InvocationOnMock invocation) throws Throwable { - final StringBuilder statement = ((StringBuilder) invocation.getArgument(0)); - statement.append(""); - return statement; + final StringBuilder stmt = ((StringBuilder) invocation.getArgument(0)); + stmt.append(""); + return stmt; } }); final In act = new PredicateImpl.In<>(paths, subQuery); @@ -145,9 +145,9 @@ void testInAsSqlGenerateSubQuery() { @Override public StringBuilder answer(final InvocationOnMock invocation) throws Throwable { - final StringBuilder statement = ((StringBuilder) invocation.getArgument(0)); - statement.append("Test"); - return statement; + final StringBuilder stmt = ((StringBuilder) invocation.getArgument(0)); + stmt.append("Test"); + return stmt; } }); final In act = new PredicateImpl.In<>(Collections.emptyList(), subQuery); diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TupleImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TupleImplTest.java index cddb45144..ba54db87b 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TupleImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/TupleImplTest.java @@ -6,10 +6,12 @@ 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; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.sql.Timestamp; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -17,7 +19,6 @@ import java.util.Map; import java.util.Map.Entry; -import jakarta.persistence.AttributeConverter; import jakarta.persistence.Tuple; import jakarta.persistence.TupleElement; @@ -28,63 +29,50 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.processor.cb.ProcessorSelection; +import com.sap.olingo.jpa.processor.cb.testobjects.UserType; import com.sap.olingo.jpa.processor.core.testmodel.DateTimeConverter; class TupleImplTest { + private static final int NO_ELEMENTS = 7; private static final String SECOND_VALUE = "Second"; private static final String THIRD_VALUE = "Third"; private static final String FIRST_VALUE = "First"; private static final String TIME_VALUE = "Timestamp"; + private static final String ENUM_VALUE_STRING = "EnumeratedString"; + private static final String ENUM_VALUE_ORDINAL = "EnumeratedOrdinal"; + private static final String ENUM_VALUE_ERROR = "EnumeratedError"; private Tuple cut; - private final Object[] values = { "Hello", "World", 3, Timestamp.valueOf("2019-01-25 14:00:25") }; + private final Object[] values = { "Hello", "World", 3, Timestamp.valueOf("2019-01-25 14:00:25"), "INTERACTIVE", 0, + "2019-01-25" }; private Map selectionIndex; private List> selPath; @BeforeEach void setup() { - selPath = new ArrayList<>(3); - selectionIndex = new HashMap<>(3); + selPath = new ArrayList<>(NO_ELEMENTS); + selectionIndex = new HashMap<>(NO_ELEMENTS); selectionIndex.put(FIRST_VALUE, 0); selectionIndex.put(SECOND_VALUE, 1); selectionIndex.put(THIRD_VALUE, 2); selectionIndex.put(TIME_VALUE, 3); + selectionIndex.put(ENUM_VALUE_STRING, 4); + selectionIndex.put(ENUM_VALUE_ORDINAL, 5); + selectionIndex.put(ENUM_VALUE_ERROR, 6); selPath.add(new ProcessorSelection.SelectionAttribute(FIRST_VALUE, mockAttribute(FIRST_VALUE, String.class))); selPath.add(new ProcessorSelection.SelectionAttribute(SECOND_VALUE, mockAttribute(SECOND_VALUE, String.class))); selPath.add(new ProcessorSelection.SelectionAttribute(THIRD_VALUE, mockAttribute(THIRD_VALUE, Integer.class))); selPath.add(new ProcessorSelection.SelectionAttribute(TIME_VALUE, mockAttributeWithConverter(TIME_VALUE))); + selPath.add(new ProcessorSelection.SelectionAttribute(ENUM_VALUE_STRING, mockAttributeEnumerated(ENUM_VALUE_STRING, + String.class))); + selPath.add(new ProcessorSelection.SelectionAttribute(ENUM_VALUE_ORDINAL, mockAttributeEnumerated( + ENUM_VALUE_ORDINAL, Integer.class))); + selPath.add(new ProcessorSelection.SelectionAttribute(ENUM_VALUE_ERROR, mockAttributeEnumerated(ENUM_VALUE_ERROR, + LocalDate.class))); cut = new TupleImpl(values, selPath, selectionIndex); } - private JPAAttribute mockAttribute(final String alias, final Class clazz) { - final JPAAttribute a = mock(JPAAttribute.class); - when(a.getType()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return clazz; - } - }); - return a; - } - - private JPAAttribute mockAttributeWithConverter(final String alias) { - final JPAAttribute attribute = mockAttribute(alias, Timestamp.class); - when(attribute.getConverter()).thenAnswer(new Answer>() { - @Override - public AttributeConverter answer(final InvocationOnMock invocation) throws Throwable { - return new DateTimeConverter(); - } - }); - when(attribute.getRawConverter()).thenAnswer(new Answer>() { - @Override - public AttributeConverter answer(final InvocationOnMock invocation) throws Throwable { - return new DateTimeConverter(); - } - }); - return attribute; - } - @Test void testToArrayReturnsCopyOf() { assertArrayEquals(values, cut.toArray()); @@ -99,7 +87,7 @@ void testGetByIndexReturnsCorrectValue() { @Test void testGetByIndexThrowsExceptionOnInvalidIndex() { - assertThrows(IllegalArgumentException.class, () -> cut.get(5)); + assertThrows(IllegalArgumentException.class, () -> cut.get(NO_ELEMENTS)); assertThrows(IllegalArgumentException.class, () -> cut.get(-1)); } @@ -127,7 +115,7 @@ void testGetByIndexWithCastReturnsCorrectValue() { @Test void testGetByIndexWithCastThrowsExceptionOnInvalidIndex() { - assertThrows(IllegalArgumentException.class, () -> cut.get(5, Number.class)); + assertThrows(IllegalArgumentException.class, () -> cut.get(NO_ELEMENTS, Number.class)); assertThrows(IllegalArgumentException.class, () -> cut.get(-1, Number.class)); } @@ -150,7 +138,7 @@ void testGetByAliasWithCastThrowsExceptionOnInvalidCast() { void testGetTupleElements() { final List> act = cut.getElements(); boolean secondFound = false; - assertEquals(4, act.size()); + assertEquals(NO_ELEMENTS, act.size()); for (final TupleElement t : act) { if (SECOND_VALUE.equals(t.getAlias())) { assertEquals(String.class, t.getJavaType()); @@ -166,4 +154,51 @@ void testTupleReturnsConvertedValue() { cut = new TupleImpl(values, selPath, selectionIndex); assertTrue(cut.get(TIME_VALUE) instanceof LocalDateTime); } + + @Test + void testTupleReturnsEnumeratedStringConvertedValue() { + cut = new TupleImpl(values, selPath, selectionIndex); + final var act = cut.get(ENUM_VALUE_STRING); + assertTrue(act instanceof UserType); + assertEquals("INTERACTIVE", ((UserType) act).toString()); + } + + @Test + void testTupleReturnsEnumeratedOrdinalConvertedValue() { + cut = new TupleImpl(values, selPath, selectionIndex); + final var act = cut.get(ENUM_VALUE_ORDINAL); + assertTrue(act instanceof UserType); + assertEquals("BATCH", ((UserType) act).toString()); + } + + @Test + void testTupleThrowsExceptionEnumeratedWrongType() { + cut = new TupleImpl(values, selPath, selectionIndex); + assertThrows(IllegalArgumentException.class, () -> cut.get(ENUM_VALUE_ERROR)); + } + + private JPAAttribute mockAttribute(final String alias, final Class clazz) { + final JPAAttribute a = mock(JPAAttribute.class); + when(a.getType()).thenAnswer(new Answer>() { + @Override + public Class answer(final InvocationOnMock invocation) throws Throwable { + return clazz; + } + }); + return a; + } + + private JPAAttribute mockAttributeWithConverter(final String alias) { + final JPAAttribute attribute = mockAttribute(alias, Timestamp.class); + doReturn(new DateTimeConverter()).when(attribute).getConverter(); + doReturn(new DateTimeConverter()).when(attribute).getRawConverter(); + return attribute; + } + + private JPAAttribute mockAttributeEnumerated(final String alias, final Class dbType) { + final JPAAttribute attribute = mockAttribute(alias, UserType.class); + when(attribute.isEnum()).thenReturn(true); + doReturn(dbType).when(attribute).getDbType(); + return attribute; + } } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/testobjects/UserType.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/testobjects/UserType.java new file mode 100644 index 000000000..7f80bd596 --- /dev/null +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/testobjects/UserType.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.cb.testobjects; + +public enum UserType { + BATCH, INTERACTIVE; +} diff --git a/jpa/odata-jpa-processor-ext/pom.xml b/jpa/odata-jpa-processor-ext/pom.xml index f66a3fed1..98b6ccc26 100644 --- a/jpa/odata-jpa-processor-ext/pom.xml +++ b/jpa/odata-jpa-processor-ext/pom.xml @@ -1,10 +1,10 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.sap.olingo + com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-processor-ext odata-jpa-processor-ext @@ -19,4 +19,4 @@ test - + diff --git a/jpa/odata-jpa-processor-parallel/README.md b/jpa/odata-jpa-processor-parallel/README.md index 0ac160d45..59fcce3d7 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 \ No newline at end of file +It shall be mentioned that the OData specification would allow a parallel processing only if the clients sends a `continue-on-error` header, see: [Preference continue-on-error (odata.continue-on-error)](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31358874) diff --git a/jpa/odata-jpa-processor-parallel/pom.xml b/jpa/odata-jpa-processor-parallel/pom.xml index 05dbfa765..ddf433f8e 100644 --- a/jpa/odata-jpa-processor-parallel/pom.xml +++ b/jpa/odata-jpa-processor-parallel/pom.xml @@ -6,7 +6,7 @@ com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-processor-parallel 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 2b8708832..22344cff0 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 @@ -66,7 +66,7 @@ public IntegrationTestHelper(final EntityManagerFactory emf, final String urlPat final ODataHttpHandler handler = odata.createHandler(odata.createServiceMetadata(edmProvider, new ArrayList<>())); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(customContext, - sessionContext); + sessionContext, odata); handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); handler.register(new JPAODataBatchProcessor(sessionContext, requestContext)); handler.process(req, resp); @@ -157,7 +157,6 @@ public HttpServletRequest getRequestMock(final String uri, final StringBuilder b public HttpServletResponse getResponseMock() throws IOException { final HttpServletResponse response = mock(HttpServletResponse.class, Answers.RETURNS_MOCKS); when(response.getOutputStream()).thenReturn(new OutPutStream()); - // when(response.getBufferSize()).thenReturn(((OutPutStream) response.getOutputStream()).getSize()); return response; } diff --git a/jpa/odata-jpa-processor/.project b/jpa/odata-jpa-processor/.project index 0dcfc02bf..644577220 100644 --- a/jpa/odata-jpa-processor/.project +++ b/jpa/odata-jpa-processor/.project @@ -15,14 +15,8 @@ - - org.eclipse.wst.validation.validationbuilder - - - - 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-processor/pom.xml b/jpa/odata-jpa-processor/pom.xml index 266a4f4ef..ba3bdf7e0 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.3-SNAPSHOT + com.sap.olingo + odata-jpa + 2.1.3 odata-jpa-processor @@ -100,4 +100,4 @@ - + \ No newline at end of file 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 index 33a170ad4..b101fa14c 100644 --- 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 @@ -4,12 +4,14 @@ import jakarta.persistence.EntityManager; +import org.apache.olingo.commons.api.http.HttpStatusCode; 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.exception.ODataJPAQueryException; import com.sap.olingo.jpa.processor.core.query.JPACountQuery; class JPADefaultPagingProvider implements JPAODataPagingProvider { @@ -25,9 +27,25 @@ public Optional getFirstPage(final JPARequestParameterMap requestP 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; + final var skipValue = uriInfo.getSkipOption() != null ? determineSkipValue(uriInfo) : 0; + final var topValue = uriInfo.getTopOption() != null ? determineTopValue(uriInfo) : Integer.MAX_VALUE; return Optional.of(new JPAODataPage(uriInfo, skipValue, topValue, null)); } + private int determineTopValue(final UriInfo uriInfo) throws ODataJPAQueryException { + final var value = uriInfo.getTopOption().getValue(); + if (value < 0) + throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE, + HttpStatusCode.BAD_REQUEST, Integer.toString(value), "$skip"); + return value; + } + + private int determineSkipValue(final UriInfo uriInfo) throws ODataJPAQueryException { + final var value = uriInfo.getSkipOption().getValue(); + if (value < 0) + throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE, + HttpStatusCode.BAD_REQUEST, Integer.toString(value), "$skip"); + return value; + } + } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataEtagHelper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataEtagHelper.java new file mode 100644 index 000000000..5fe090d96 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataEtagHelper.java @@ -0,0 +1,84 @@ +package com.sap.olingo.jpa.processor.core.api; + +import java.util.Collection; + +import javax.annotation.Nonnull; + +import org.apache.olingo.server.api.etag.PreconditionException; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +public interface JPAODataEtagHelper { + + /** + *

+ * Checks the preconditions of a read request with a given ETag value + * against the If-Match and If-None-Match HTTP headers. + *

+ *

+ * If the given ETag value is not matched by the ETag information in the If-Match headers, + * and there are ETags in the headers to be matched, a "Precondition Failed" exception is + * thrown. + *

+ *

+ * If the given ETag value is matched by the ETag information in the If-None-Match headers, + * true is returned, and applications are supposed to return an empty response + * with a "Not Modified" status code and the ETag header, false otherwise. + *

+ *

+ * All matching uses weak comparison as described in + * RFC 7232, section 2.3.2. + *

+ *

+ * This method does not nothing and returns false if the ETag value is + * null. + *

+ * @param eTag the ETag value to match + * @param ifMatchHeaders the If-Match header values + * @param ifNoneMatchHeaders the If-None-Match header values + * @return whether a "Not Modified" response should be used + */ + public boolean checkReadPreconditions(String etag, + Collection ifMatchHeaders, Collection ifNoneMatchHeaders) + throws PreconditionException; + + /** + *

+ * Checks the preconditions of a change request (with HTTP methods PUT, PATCH, or DELETE) + * with a given ETag value against the If-Match and If-None-Match HTTP headers. + *

+ *

+ * If the given ETag value is not matched by the ETag information in the If-Match headers, + * and there are ETags in the headers to be matched, or + * if the given ETag value is matched by the ETag information in the If-None-Match headers, + * a "Precondition Failed" exception is thrown. + *

+ *

+ * All matching uses weak comparison as described in + * RFC 7232, section 2.3.2. + *

+ *

+ * This method does not nothing if the ETag value is null. + *

+ * @param eTag the ETag value to match + * @param ifMatchHeaders the If-Match header values + * @param ifNoneMatchHeaders the If-None-Match header values + */ + public void checkChangePreconditions(String etag, + Collection ifMatchHeaders, Collection ifNoneMatchHeaders) + throws PreconditionException; + + /** + * Converts the value of an ETag into a the corresponding string.
+ * A value is converted by calling the toString method with one exception. In case + * the value is an instance of type {@link java.sql.Timestamp}, the time stamp is first converted into + * a {@link java.time.Instant} to get a RFC 7232 compliant string. + * + * @param entityType the ETag belongs to + * @param value raw value of the ETag + * @return ETag string. It is empty if the value is null and null if the entity type has no ETag property + * @throws ODataJPAQueryException + */ + public String asEtag(@Nonnull final JPAEntityType entityType, final Object value) throws ODataJPAQueryException; +} 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 f190bb19e..a5391e9a1 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 @@ -71,4 +71,6 @@ public Optional getQueryEnhancement(@Nonnull final JP public List getProvidedLocale(); public JPAODataQueryDirectives getQueryDirectives(); + + public JPAODataEtagHelper getEtagHelper(); } 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 b84f0e336..9006533c2 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 @@ -53,7 +53,7 @@ public JPAODataRequestHandler(final JPAODataSessionContextAccess serviceContext, final OData odata) { this.emf = serviceContext.getEntityManagerFactory(); this.serviceContext = (JPAODataServiceContext) serviceContext; - this.requestContext = new JPAODataInternalRequestContext(requestContext, serviceContext); + this.requestContext = new JPAODataInternalRequestContext(requestContext, serviceContext, odata); this.odata = odata; } 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 28b791cc0..bc4ce5266 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 @@ -5,8 +5,6 @@ import com.sap.olingo.jpa.processor.core.exception.ODataJPATransactionException; /** - * 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/example/JPAExamplePagingProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/example/JPAExamplePagingProvider.java index 3c1ebd87b..3a0df6da4 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 @@ -128,5 +128,4 @@ private void addToCache(final JPAODataPage page, final Long count) { } private static record CacheEntry(Long maxTop, JPAODataPage page) {} - } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java index c80d48b73..03711e22b 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverter.java @@ -1,29 +1,42 @@ package com.sap.olingo.jpa.processor.core.converter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.uri.UriHelper; +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.JPAServiceDocument; +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.processor.core.api.JPAODataEtagHelper; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; public class JPAEntityResultConverter extends JPAStructuredResultConverter { private final EdmEntityType edmEntityType; private final UriHelper odataUriHelper; + private final JPAODataEtagHelper etagHelper; public JPAEntityResultConverter(final UriHelper uriHelper, final JPAServiceDocument sd, final List jpaQueryResult, - final EdmEntityType returnType) throws ODataJPAModelException { + final EdmEntityType returnType, final JPAODataEtagHelper etagHelper) throws ODataJPAModelException { super(jpaQueryResult, sd.getEntity(returnType)); this.edmEntityType = returnType; this.odataUriHelper = uriHelper; + this.etagHelper = etagHelper; } @Override @@ -36,10 +49,32 @@ public EntityCollection getResult() throws ODataApplicationException, Serializer odataEntity.setType(this.jpaTopLevelType.getExternalFQN().getFullQualifiedNameAsString()); final List properties = odataEntity.getProperties(); convertProperties(row, properties, jpaTopLevelType); + odataEntity.setETag(createEtag(row, jpaTopLevelType)); odataEntity.setId(new URI(odataUriHelper.buildKeyPredicate(edmEntityType, odataEntity))); odataResults.add(odataEntity); } return odataEntityCollection; } + @CheckForNull + private String createEtag(final Object row, final JPAStructuredType jpaType) throws ODataJPAQueryException { + if (jpaType instanceof final JPAEntityType et) { + try { + if (et.hasEtag()) { + Object value = row; + for (final JPAElement part : et.getEtagPath().getPath()) { + final Map methodMap = getMethods(value.getClass()); + final Method getMethod = getGetter(part.getInternalName(), methodMap); + value = getMethod.invoke(value); + } + return etagHelper.asEtag(et, value); + } + } catch (final ODataJPAModelException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + } + return null; + + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java index 8cfc20a29..5505443e8 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPAStructuredResultConverter.java @@ -124,14 +124,18 @@ private void convertPrimitiveProperty(final Object row, final List properties, final JPAAttribute attribute, final Method getMethod) throws ODataJPAModelException, ODataJPAQueryException, IllegalAccessException, InvocationTargetException { - final ComplexValue complexValue = new ComplexValue(); - properties.add(new Property( - attribute.getStructuredType().getExternalFQN().getFullQualifiedNameAsString(), - attribute.getExternalName(), - ValueType.COMPLEX, - complexValue)); - final List values = complexValue.getValue(); - convertProperties(getMethod.invoke(row), values, attribute.getStructuredType()); + + final var complexResult = getMethod.invoke(row); + if (complexResult != null) { + final ComplexValue complexValue = new ComplexValue(); + properties.add(new Property( + attribute.getStructuredType().getExternalFQN().getFullQualifiedNameAsString(), + attribute.getExternalName(), + ValueType.COMPLEX, + complexValue)); + final List values = complexValue.getValue(); + convertProperties(complexResult, values, attribute.getStructuredType()); + } } private void convertCollectionProperty(final Object row, final List properties, @@ -155,7 +159,7 @@ private void convertCollectionProperty(final Object row, final List pr collection)); } - private Method getGetter(final String attributeName, final Map methodMap) + Method getGetter(final String attributeName, final Map methodMap) throws ODataJPAQueryException { final String getterName = ACCESS_MODIFIER_GET + JPADefaultEdmNameBuilder.firstToUpper(attributeName); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java index 72f45305a..1279c79bf 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleChildConverter.java @@ -251,7 +251,7 @@ private void createEtag(@Nonnull final JPAEntityType rowEntity, final Tuple row, final String etagAlias = rowEntity.getEtagPath().getAlias(); final Object etag = row.get(etagAlias); if (etag != null) { - odataEntity.setETag(etag.toString()); + odataEntity.setETag(requestContext.getEtagHelper().asEtag(rowEntity, etag)); } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java index 6a134a447..0a742771c 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/converter/JPATupleResultConverter.java @@ -250,11 +250,11 @@ void convertPrimitiveAttribute(final Object throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } } + } else if (attribute != null && value != null && attribute.isEnum() && !value.getClass().isArray()) { + odataValue = ((Enum) value).ordinal(); } else if (attribute != null && attribute.getConverter() != null) { final AttributeConverter converter = attribute.getConverter(); odataValue = converter.convertToDatabaseColumn((T) value); - } else if (attribute != null && value != null && attribute.isEnum()) { - odataValue = ((Enum) value).ordinal(); } else if (attribute != null && value != null && attribute.isCollection()) { return; } else if (attribute != null && value != null && attribute.getType() == Duration.class) { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java index 259aaeb5f..62ffe3163 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java @@ -18,6 +18,8 @@ import org.apache.olingo.server.api.uri.queryoption.SearchOption; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; +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.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; @@ -101,14 +103,15 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin @SuppressWarnings("unchecked") @Override - public List executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException { + public Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); if (last.getKind() == UriResourceKind.count) { final List countResult = new ArrayList<>(); countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return (List) countResult; + return countResult; } if (last.getKind() == UriResourceKind.function) return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java index 8c6fbf874..d72a910bd 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunction.java @@ -1,5 +1,6 @@ package com.sap.olingo.jpa.processor.core.database; +import java.util.Collections; import java.util.List; import jakarta.persistence.EntityManager; @@ -7,10 +8,46 @@ import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriResource; +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.api.JPADataBaseFunction; public interface JPAODataDatabaseTableFunction { - List executeFunctionQuery(final List uriResourceParts, final JPADataBaseFunction jpaFunction, - final EntityManager em) throws ODataApplicationException; + /** + * + * @param + * @param uriResourceParts + * @param jpaFunction + * @param em + * @return + * @throws ODataApplicationException + * + * @deprecated implement + * {@link #executeFunctionQuery(List, JPADataBaseFunction, EntityManager, JPAHttpHeaderMap, JPARequestParameterMap)} + * instead + */ + @Deprecated(since = "2.1.2", forRemoval = true) + default List executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException { + return Collections.emptyList(); + } + + /** + * + * @param As of now only {@link java.util.List} is supported + * @param uriResourceParts + * @param jpaFunction + * @param em + * @param headers + * @param parameters + * @return A primitive type, a complex type, an entity type or a list of one of those. According to the defined return + * type + * @throws ODataApplicationException + */ + default Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { + return executeFunctionQuery(uriResourceParts, jpaFunction, em); + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java index 4ebc1e1e0..4111fed76 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java @@ -17,6 +17,8 @@ import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; +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.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; @@ -37,17 +39,17 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin /** * See: Derby: Function Invocation */ - @SuppressWarnings("unchecked") @Override - public java.util.List executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException { + public Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); if (last.getKind() == UriResourceKind.count) { final List countResult = new ArrayList<>(); countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return (List) countResult; + return countResult; } if (last.getKind() == UriResourceKind.function) return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java index 84b5358f9..ef2d5e39e 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java @@ -17,6 +17,8 @@ import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; +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.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; @@ -35,16 +37,16 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin HttpStatusCode.NOT_IMPLEMENTED); } - @SuppressWarnings("unchecked") @Override - public List executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException { + public Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); if (last.getKind() == UriResourceKind.count) { final List countResult = new ArrayList<>(); countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return (List) countResult; + return countResult; } if (last.getKind() == UriResourceKind.function) return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java index d0ccede8b..5dadc6cbb 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java @@ -17,6 +17,8 @@ import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; +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.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; @@ -45,17 +47,17 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin HttpStatusCode.NOT_IMPLEMENTED); } - @SuppressWarnings("unchecked") @Override - public List executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em) throws ODataApplicationException { + public Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); if (last.getKind() == UriResourceKind.count) { final List countResult = new ArrayList<>(); countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return (List) countResult; + return countResult; } if (last.getKind() == UriResourceKind.function) return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java index e87ef7b58..1f74e1089 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAProcessorException.java @@ -51,6 +51,7 @@ public enum MessageKeys implements ODataJPAMessageKey { EXPAND_NON_SUPPORTED_AT_ALL, EXPAND_EXCEEDS_MAX_LEVEL, + VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS, COUNT_NON_SUPPORTED_COUNT; @Override @@ -85,7 +86,8 @@ public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatus super(messageKey.getKey(), statusCode, params); } - public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatusCode statusCode, final Throwable exception) { + public ODataJPAProcessorException(final MessageKeys messageKey, final HttpStatusCode statusCode, + final Throwable exception) { super(messageKey.getKey(), statusCode, exception); } 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 3e61a4530..b04c67845 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 @@ -37,6 +37,7 @@ public enum MessageKeys implements ODataJPAMessageKey { QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING, QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED, + QUERY_PREPARATION_NO_PAGE_FOUND, 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/JPAExistsOperation.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java index c23a4de31..f092c4e65 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPAExistsOperation.java @@ -16,6 +16,7 @@ import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceComplexProperty; import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.UriResourceProperty; @@ -71,30 +72,30 @@ protected List determineAssociations(final JPAService StringBuilder associationName = null; UriResourcePartTyped navigation = null; - if (resourceParts != null && (Utility.hasNavigation(resourceParts) || hasCollection(resourceParts))) { + if (Utility.hasNavigation(resourceParts) || Utility.hasCollection(resourceParts)) { for (int i = resourceParts.size() - 1; i >= 0; i--) { final UriResource resourcePart = resourceParts.get(i); - if (resourcePart instanceof UriResourceNavigation) { + if (resourcePart instanceof final UriResourceNavigation nextNavigation) { if (navigation != null) pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociationPath(sd, - ((UriResourcePartTyped) resourceParts.get(i)), associationName), null)); - navigation = (UriResourceNavigation) resourceParts.get(i); + nextNavigation, associationName), null)); + navigation = nextNavigation; associationName = new StringBuilder(); - associationName.insert(0, ((UriResourceNavigation) navigation).getProperty().getName()); + associationName.insert(0, nextNavigation.getProperty().getName()); } if (navigation != null) { if (resourceParts.get(i) instanceof final UriResourceComplexProperty complexProperty) { associationName.insert(0, JPAPath.PATH_SEPARATOR); associationName.insert(0, complexProperty.getProperty().getName()); - } - if (resourcePart instanceof UriResourceEntitySet) + } else if (resourcePart instanceof final UriResourceEntitySet entitySet) pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociationPath(sd, - ((UriResourcePartTyped) resourceParts.get(i)), associationName), null)); + entitySet, associationName), null)); + else if (resourcePart instanceof final UriResourceLambdaVariable lambdaVariable) + pathList.add(new JPANavigationPropertyInfo(sd, navigation, Utility.determineAssociation(sd, + lambdaVariable.getType(), associationName), null)); } - /* placing this condition at the end makes sure that other conditions are not entered for the resource - that's being processed once navigation object gets populated */ - if (isCollection(resourcePart)) { - navigation = (UriResourcePartTyped) resourceParts.get(i); + if (Utility.isCollection(resourcePart)) { + navigation = (UriResourcePartTyped) resourcePart; associationName = new StringBuilder(); associationName.insert(0, ((UriResourceProperty) navigation).getProperty().getName()); } @@ -103,20 +104,5 @@ protected List determineAssociations(final JPAService return pathList; } - public boolean hasCollection(final List resourceParts) { - if (resourceParts != null) { - for (int i = resourceParts.size() - 1; i >= 0; i--) { - if (isCollection(resourceParts.get(i))) - return true; - } - } - return false; - } - - public boolean isCollection(final UriResource resourcePart) { - - return (resourcePart instanceof final UriResourceProperty resourceProperty && resourceProperty.isCollection()); - } - - protected static record SubQueryItem(List>> jpaPath, Subquery>> query) {} + protected record SubQueryItem(List>> jpaPath, Subquery>> query) {} } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java index 6a52a2661..49b402e62 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/filter/JPALambdaOperation.java @@ -25,8 +25,8 @@ abstract class JPALambdaOperation extends JPAExistsOperation { protected final UriInfoResource member; - JPALambdaOperation(final JPAFilterComplierAccess jpaComplier, final Member member) { - super(jpaComplier); + JPALambdaOperation(final JPAFilterComplierAccess jpaCompiler, final Member member) { + super(jpaCompiler); this.member = member.getResourcePath(); } @@ -38,7 +38,11 @@ protected SubQueryItem getExistsQuery() throws ODataApplicationException { @SuppressWarnings("unchecked") protected final Subquery getSubQuery(final Expression expression) throws ODataApplicationException { - final List allUriResourceParts = new ArrayList<>(uriResourceParts); + // Add association root, which is only available for the first lambda expression + final List allUriResourceParts = new ArrayList<>(); + if (uriResourceParts != null) + allUriResourceParts.addAll(uriResourceParts); + // Add association path allUriResourceParts.addAll(member.getUriResourceParts()); // 1. Determine all relevant associations diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java index 0704a3b98..1bc16cebc 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAAbstractRequestProcessor.java @@ -1,8 +1,11 @@ package com.sap.olingo.jpa.processor.core.processor; +import javax.annotation.Nullable; + import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; +import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.http.HttpHeader; @@ -43,10 +46,29 @@ abstract class JPAAbstractRequestProcessor { } protected final void createSuccessResponse(final ODataResponse response, final ContentType responseFormat, - final SerializerResult serializerResult) { + final SerializerResult serializerResult, @Nullable final EntityCollection entityCollection) { response.setContent(serializerResult.getContent()); response.setStatusCode(successStatusCode); response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + createETagHeader(response, entityCollection); + } + + protected final void createNotModifiedResponse(final ODataResponse response, + final EntityCollection entityCollection) { + response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode()); + createETagHeader(response, entityCollection); + } + + protected final void createPreconditionFailedResponse(final ODataResponse response) { + response.setStatusCode(HttpStatusCode.PRECONDITION_FAILED.getStatusCode()); + } + + private void createETagHeader(final ODataResponse response, final EntityCollection entityCollection) { + if (entityCollection != null && entityCollection.getEntities().size() == 1) { + final var etag = entityCollection.getEntities().get(0).getETag(); + if (etag != null) + response.setHeader(HttpHeader.ETAG, etag); + } } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java index 410184a64..3b3010457 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessor.java @@ -61,7 +61,8 @@ public void performAction(final ODataRequest request, final ODataResponse respon final JPAAction jpaAction = sd.getAction(resource.getAction()); if (jpaAction == null) throw new ODataJPAProcessorException(ACTION_UNKNOWN, BAD_REQUEST, resource.getAction().getName()); - final Object instance = createInstance(em, jpaAction); + final Object instance = createInstance(jpaAction, em, requestContext.getHeader(), requestContext + .getRequestParameter()); final ODataDeserializer deserializer = odata.createDeserializer(requestFormat); final Map actionParameter = diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java index 738d15148..e6b94a67d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java @@ -497,7 +497,7 @@ private void createCreateResponse(final ODataRequest request, final ODataRespons final Entity createdEntity = convertEntity(et, result, request.getAllHeaders()); final EntityCollection entities = new EntityCollection(); entities.getEntities().add(createdEntity); - createSuccessResponse(response, responseFormat, serializer.serialize(request, entities)); + createSuccessResponse(response, responseFormat, serializer.serialize(request, entities), entities); response.setHeader(HttpHeader.LOCATION, location); } } @@ -688,7 +688,7 @@ private void createUpdateResponse(final ODataRequest request, final ODataRespons } final EntityCollection entities = new EntityCollection(); entities.getEntities().add(updatedEntity); - createSuccessResponse(response, responseFormat, serializer.serialize(request, entities)); + createSuccessResponse(response, responseFormat, serializer.serialize(request, entities), entities); } } 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 140e4cb6c..d0e7138f8 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 @@ -137,6 +137,7 @@ long getCurrentThreadMemoryConsumption() { } private long getMemoryConsumption() { + final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); if (threadMXBean instanceof final com.sun.management.ThreadMXBean sunMXBean) { return sunMXBean.getThreadAllocatedBytes(Thread.currentThread().getId()); 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 c9e748cf2..bada88e1e 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 @@ -35,7 +35,7 @@ public void retrieveData(final ODataRequest request, final ODataResponse respons if (uriResource instanceof UriResourceEntitySet || uriResource instanceof UriResourceSingleton) { final var result = countEntities(); - createSuccessResponse(response, ContentType.TEXT_PLAIN, serializer.serialize(request, result)); + createSuccessResponse(response, ContentType.TEXT_PLAIN, serializer.serialize(request, result), null); } else { throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE, HttpStatusCode.NOT_IMPLEMENTED, uriResource.getKind().toString()); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java new file mode 100644 index 000000000..5d3785d22 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAETagValidationResult.java @@ -0,0 +1,19 @@ +package com.sap.olingo.jpa.processor.core.processor; + +import org.apache.olingo.commons.api.http.HttpStatusCode; + +enum JPAETagValidationResult { + NOT_MODIFIED(HttpStatusCode.NOT_MODIFIED), + SUCCESS(HttpStatusCode.OK), + PRECONDITION_FAILED(HttpStatusCode.PRECONDITION_FAILED); + + private final HttpStatusCode statusCode; + + JPAETagValidationResult(final HttpStatusCode statusCode) { + this.statusCode = statusCode; + } + + HttpStatusCode getStatusCode() { + return statusCode; + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java index 1b556cb88..5121d96c0 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessor.java @@ -50,9 +50,11 @@ public void retrieveData(final ODataRequest request, final ODataResponse respons .getFunction().getName()); Object result = null; if (jpaFunction.getFunctionType() == EdmFunctionType.JavaClass) { - result = new JPAJavaFunctionProcessor(sd, uriResourceFunction, (JPAJavaFunction) jpaFunction, em).process(); + result = new JPAJavaFunctionProcessor(sd, uriResourceFunction, (JPAJavaFunction) jpaFunction, + em, requestContext.getHeader(), requestContext.getRequestParameter()).process(); } else if (jpaFunction.getFunctionType() == EdmFunctionType.UserDefinedFunction) { - result = dbProcessor.executeFunctionQuery(uriInfo.getUriResourceParts(), (JPADataBaseFunction) jpaFunction, em); + result = dbProcessor.executeFunctionQuery(uriInfo.getUriResourceParts(), (JPADataBaseFunction) jpaFunction, em, + requestContext.getHeader(), requestContext.getRequestParameter()); } final EdmType returnType = uriResourceFunction.getFunction().getReturnType().getType(); final Annotatable annotatable = convertResult(result, returnType, jpaFunction); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java index 082d30780..1bafa1afd 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessor.java @@ -4,14 +4,20 @@ import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.ODATA_MAXPAGESIZE_NOT_A_NUMBER; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.QUERY_PREPARATION_ERROR; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.QUERY_RESULT_CONV_ERROR; +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; +import jakarta.persistence.Tuple; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; @@ -19,20 +25,24 @@ import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.etag.PreconditionException; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; +import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; 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.exception.ODataJPAModelException; 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.converter.JPAExpandResult; @@ -40,8 +50,10 @@ import com.sap.olingo.jpa.processor.core.exception.ODataJPANotImplementedException; 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.query.JPACollectionItemInfo; import com.sap.olingo.jpa.processor.core.query.JPACollectionJoinQuery; +import com.sap.olingo.jpa.processor.core.query.JPAConvertibleResult; import com.sap.olingo.jpa.processor.core.query.JPAExpandItemInfo; import com.sap.olingo.jpa.processor.core.query.JPAExpandItemInfoFactory; import com.sap.olingo.jpa.processor.core.query.JPAExpandQueryFactory; @@ -52,6 +64,7 @@ import com.sap.olingo.jpa.processor.core.query.Utility; public final class JPANavigationRequestProcessor extends JPAAbstractGetRequestProcessor { + private static final Log LOGGER = LogFactory.getLog(JPANavigationRequestProcessor.class); private final ServiceMetadata serviceMetadata; private final UriResource lastItem; private final JPAODataPage page; @@ -83,15 +96,19 @@ public > void retrieveData(final ODataRequest request, f } final var result = query.execute(); + // Validate If-Match and If-None-Match headers + final var conditionValidationResult = validateEntityTag(result, requestContext.getHeader()); // Read Expand and Collection - final var keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(), - page); - 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; + if (conditionValidationResult == JPAETagValidationResult.SUCCESS) { + final var keyBoundary = result.getKeyBoundary(requestContext, query.getNavigationInfo(), + page); + 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 try (var converterMeasurement = debugger.newMeasurement(this, "convertResult")) { entityCollection = result.asEntityCollection(new JPATupleChildConverter(sd, odata.createUriHelper(), serviceMetadata, requestContext)).get(ROOT_RESULT_KEY); @@ -132,7 +149,11 @@ public > void retrieveData(final ODataRequest request, f * of * the related single entity. If no entity is related, the service returns 204 No Content. */ - if (hasNoContent(entityCollection.getEntities())) + if (conditionValidationResult == JPAETagValidationResult.NOT_MODIFIED) + createNotModifiedResponse(response, entityCollection); + else if (conditionValidationResult == JPAETagValidationResult.PRECONDITION_FAILED) + createPreconditionFailedResponse(response); + else if (hasNoContent(entityCollection.getEntities())) response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); else if (doesNotExists(entityCollection.getEntities())) response.setStatusCode(HttpStatusCode.NOT_FOUND.getStatusCode()); @@ -140,7 +161,7 @@ else if (doesNotExists(entityCollection.getEntities())) else if (entityCollection.getEntities() != null) { try (var serializerMeasurement = debugger.newMeasurement(this, "serialize")) { final var serializerResult = serializer.serialize(request, entityCollection); - createSuccessResponse(response, responseFormat, serializerResult); + createSuccessResponse(response, responseFormat, serializerResult, entityCollection); } } else { // A request returns 204 No Content if the requested resource has the null value, or if the service applies a @@ -150,11 +171,69 @@ else if (entityCollection.getEntities() != null) { } } - private void checkRequestSupported() throws ODataJPAProcessException { + void checkRequestSupported() throws ODataJPAProcessException { if (uriInfo.getApplyOption() != null) throw new ODataJPANotImplementedException("$apply"); } + /** + * Validate + * If-Match and + * If-None-Match + * headers + * @param result Query result + * @param header List of all request headers + * @throws ODataJPAProcessorException + */ + JPAETagValidationResult validateEntityTag(final JPAConvertibleResult result, final JPAHttpHeaderMap header) + throws ODataJPAProcessorException { + + try { + if (result instanceof final JPAExpandResult expandResult) { + if (expandResult.getEntityType().hasEtag()) { + + final var results = expandResult.getResult(ROOT_RESULT_KEY); + final var ifNoneMatchEntityTags = getMatchHeader(header, HttpHeader.IF_NONE_MATCH); + final var ifMatchEntityTags = getMatchHeader(header, HttpHeader.IF_MATCH); + if (results.size() == 1) { + final var etagAlias = expandResult.getEntityType().getEtagPath().getAlias(); + final var etag = requestContext.getEtagHelper().asEtag(expandResult.getEntityType(), + results.get(0).get(etagAlias)); + if (requestContext.getEtagHelper().checkReadPreconditions(etag, ifMatchEntityTags, ifNoneMatchEntityTags)) + return JPAETagValidationResult.NOT_MODIFIED; + } else if (!preconditionCheckSupported(results, ifNoneMatchEntityTags, ifMatchEntityTags)) { + throw new ODataJPAProcessorException(VALIDATION_NOT_POSSIBLE_TOO_MANY_RESULTS, + HttpStatusCode.BAD_REQUEST); + } + } + } else { + LOGGER.warn("Result is not of type JPAExpandResult. ETag validation not possible"); + } + } catch (final ODataJPAModelException | ODataJPAQueryException e) { + throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + + catch (final PreconditionException e) { + return JPAETagValidationResult.PRECONDITION_FAILED; + } + return JPAETagValidationResult.SUCCESS; + } + + private List getMatchHeader(final JPAHttpHeaderMap headers, final String matchHeader) { + return Optional.ofNullable(headers.get(matchHeader)).orElseGet(() -> headers.get( + matchHeader.toLowerCase(Locale.ENGLISH))); + } + + private boolean preconditionCheckSupported(final List results, final List ifNoneMatchEntityTags, + final List ifMatchEntityTags) { + return results.size() <= 1 + || (isEmpty(ifMatchEntityTags) && isEmpty(ifNoneMatchEntityTags)); + } + + private boolean isEmpty(final List list) { + return list == null || list.isEmpty(); + } + private URI buildNextLink(final JPAODataPage page) throws ODataJPAProcessorException { if (page != null && page.skipToken() != null) { try { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java new file mode 100644 index 000000000..428e23803 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImpl.java @@ -0,0 +1,58 @@ +package com.sap.olingo.jpa.processor.core.processor; + +import java.sql.Timestamp; +import java.util.Collection; + +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.etag.ETagHelper; +import org.apache.olingo.server.api.etag.PreconditionException; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEtagValidator; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +final class JPAODataEtagHelperImpl implements JPAODataEtagHelper { + + private final ETagHelper olingoHelper; + + JPAODataEtagHelperImpl(final OData odata) { + this.olingoHelper = odata.createETagHelper(); + } + + @Override + public boolean checkReadPreconditions(final String etag, final Collection ifMatchHeaders, + final Collection ifNoneMatchHeaders) throws PreconditionException { + return olingoHelper.checkReadPreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + } + + @Override + public void checkChangePreconditions(final String etag, final Collection ifMatchHeaders, + final Collection ifNoneMatchHeaders) throws PreconditionException { + olingoHelper.checkChangePreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + } + + @Override + public String asEtag(final JPAEntityType entityType, final Object value) throws ODataJPAQueryException { + + try { + if (entityType.hasEtag()) { + final var etag = new StringBuilder(); + if (value != null) { + if (entityType.getEtagValidator() == JPAEtagValidator.WEAK) + etag.append("W/"); + etag.append("\"") + .append(value instanceof final Timestamp t ? t.toInstant().toString() : value.toString()) + .append("\""); + } + return etag.toString(); + } + return null; + } catch (final ODataJPAModelException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + } + +} 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 65fe065d4..15c2638b2 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 @@ -17,6 +17,7 @@ import jakarta.persistence.EntityManager; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; @@ -32,6 +33,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataDatabaseProcessor; import com.sap.olingo.jpa.processor.core.api.JPAODataDefaultTransactionFactory; +import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper; 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; @@ -68,13 +70,15 @@ public final class JPAODataInternalRequestContext implements JPAODataRequestCont private Optional edmProvider; private JPAODataDatabaseOperations operationConverter; private JPAODataQueryDirectives queryDirectives; + private JPAODataEtagHelper etagHelper; public JPAODataInternalRequestContext(@Nonnull final JPAODataRequestContext requestContext, - @Nonnull final JPAODataSessionContextAccess sessionContext) { + @Nonnull final JPAODataSessionContextAccess sessionContext, final OData odata) { this.header = new JPAHttpHeaderHashMap(Collections.emptyMap()); copyRequestContext(requestContext, sessionContext); this.hookFactory = new JPAHookFactory(em, header, customParameter); initDebugger(); + etagHelper = new JPAODataEtagHelperImpl(odata); } /** @@ -216,6 +220,11 @@ public List getProvidedLocale() { return locales; } + @Override + public JPAODataEtagHelper getEtagHelper() { + return etagHelper; + } + public void setEntityManager(@Nonnull final EntityManager em) { this.em = Objects.requireNonNull(em); } @@ -275,6 +284,7 @@ private void copyContextValues(final JPAODataRequestContextAccess context) this.edmProvider = Optional.ofNullable(context.getEdmProvider()); this.operationConverter = context.getOperationConverter(); this.queryDirectives = context.getQueryDirectives(); + this.etagHelper = context.getEtagHelper(); } private void copyRequestContext(@Nonnull final JPAODataRequestContext requestContext, diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java index 3357a90a8..76e00b58a 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPAOperationRequestProcessor.java @@ -2,13 +2,12 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import jakarta.persistence.EntityManager; - import org.apache.olingo.commons.api.data.Annotatable; import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.EntityCollection; @@ -106,18 +105,19 @@ private ComplexValue createComplexValue(final EdmComplexType returnType, final O @SuppressWarnings({ "rawtypes", "unchecked" }) private EntityCollection createEntityCollection(final EdmEntityType returnType, final Object result, - final UriHelper createUriHelper, final JPAOperation jpaFunction) + final UriHelper createUriHelper, final JPAOperation jpaOperation) throws ODataApplicationException { final List resultList = new ArrayList(); - if (jpaFunction.getResultParameter().isCollection()) + if (jpaOperation.getResultParameter().isCollection()) resultList.addAll((Collection) result); else if (result == null) return null; else resultList.add(result); try { - return new JPAEntityResultConverter(createUriHelper, sd, resultList, returnType).getResult(); + return new JPAEntityResultConverter(createUriHelper, sd, resultList, returnType, requestContext.getEtagHelper()) + .getResult(); } catch (SerializerException | ODataJPAModelException | URISyntaxException e) { throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.QUERY_RESULT_CONV_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR, e); @@ -131,22 +131,33 @@ protected void serializeResult(final EdmType returnType, final ODataResponse res if (result != null && !(result instanceof final EntityCollection collection && collection.getEntities().isEmpty())) { + final EntityCollection collection = result instanceof EntityCollection // NOSONAR + ? (EntityCollection) result + : new EntityCollection(); final SerializerResult serializerResult = ((JPAOperationSerializer) serializer).serialize(result, returnType, request); - createSuccessResponse(response, responseFormat, serializerResult); + createSuccessResponse(response, responseFormat, serializerResult, collection); } else { response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); } } - protected Object createInstance(final EntityManager em, final JPAJavaOperation jpaOperation) + protected Object createInstance(final JPAJavaOperation jpaOperation, final Object... parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException { final Constructor constructor = jpaOperation.getConstructor(); - if (constructor.getParameterCount() == 1) - return constructor.newInstance(em); - else - return constructor.newInstance(); + final Object[] paramValues = new Object[constructor.getParameters().length]; + int i = 0; + for (final Parameter p : constructor.getParameters()) { + for (final Object o : parameters) { + if (p.getType().isAssignableFrom(o.getClass())) { + paramValues[i] = o; + break; + } + } + i++; + } + return constructor.newInstance(paramValues); } } 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 4b16cce07..0cdc79cc3 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 @@ -9,6 +9,8 @@ import java.util.Map.Entry; import java.util.Optional; +import javax.annotation.Nonnull; + import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.http.HttpHeader; @@ -100,7 +102,8 @@ public JPARequestProcessor createProcessor(final UriInfo uriInfo, final ContentT checkNavigationPathSupported(resourceParts); yield new JPANavigationRequestProcessor(odata, serviceMetadata, requestContext); } - default -> throw new ODataJPAProcessorException(ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE, + default -> throw new ODataJPAProcessorException( + ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_RESOURCE_TYPE, HttpStatusCode.NOT_IMPLEMENTED, lastItem.getKind().toString()); }; } catch (final ODataJPAIllegalAccessException e) { @@ -128,6 +131,7 @@ private void checkNavigationPathSupported(final List resourceParts) } } + @Nonnull private JPAODataPage getPage(final Map> headers, final UriInfo uriInfo, final JPAODataRequestContextAccess requestContext, final JPAODataPathInformation pathInformation) throws ODataException { 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 c03eefdf0..789b7802d 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 @@ -44,8 +44,6 @@ import org.apache.olingo.server.api.uri.UriResourceProperty; import org.apache.olingo.server.api.uri.queryoption.SelectItem; import org.apache.olingo.server.api.uri.queryoption.SelectOption; -import org.apache.olingo.server.api.uri.queryoption.SkipOption; -import org.apache.olingo.server.api.uri.queryoption.TopOption; import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmQueryExtensionProvider; @@ -77,24 +75,14 @@ public abstract class JPAAbstractJoinQuery extends JPAAbstractQuery implements J protected static final String ALIAS_SEPARATOR = "."; protected final UriInfoResource uriResource; protected final CriteriaQuery cq; - protected Root root; // Start of an navigation - protected From target; // The entity that shall be returned by the query protected final JPAODataPage page; protected final List navigationInfo; protected final JPANavigationPropertyInfo lastInfo; protected final JPAODataRequestContextAccess requestContext; + protected Root root; // Start of a navigation + protected From target; // The entity that shall be returned by the query protected Optional entitySet; - protected static Optional determineTargetEntitySet(final JPAODataRequestContextAccess requestContext) - throws ODataException { - - final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo() - .getUriResourceParts()); - if (bindingTarget instanceof EdmEntitySet) - return requestContext.getEdmProvider().getServiceDocument().getEntitySet(bindingTarget.getName()); - return Optional.empty(); - } - JPAAbstractJoinQuery(final OData odata, final JPAEntityType jpaEntityType, final JPAODataRequestContextAccess requestContext, final List navigationInfo) throws ODataException { @@ -117,6 +105,16 @@ protected static Optional determineTargetEntitySet(final JPAODataR this.entitySet = Optional.empty(); } + protected static Optional determineTargetEntitySet(final JPAODataRequestContextAccess requestContext) + throws ODataException { + + final EdmBindingTarget bindingTarget = Utility.determineBindingTarget(requestContext.getUriInfo() + .getUriResourceParts()); + if (bindingTarget instanceof EdmEntitySet) + return requestContext.getEdmProvider().getServiceDocument().getEntitySet(bindingTarget.getName()); + return Optional.empty(); + } + @SuppressWarnings("unchecked") @Override public AbstractQuery getQuery() { @@ -204,6 +202,7 @@ else if (select == null || select.getSelectItems().isEmpty() || select.getSelect *
  • A stream is requested and the property contains the mime type * * Not included are collection properties. + * * @param uriResource * @return * @throws ODataApplicationException @@ -253,12 +252,10 @@ protected > jakarta.persistence.criteria.Express } /** - * * @param orderByTarget - * @param descriptionFields List of the requested fields that of type description + * @param selectionPath * @param query * @param lastInfo - * @param queryRoot * @return * @throws ODataApplicationException * @throws JPANoSelectionException @@ -368,31 +365,31 @@ protected final boolean determineTargetIsCollection(final UriInfoResource uriRes return (last instanceof final UriResourceProperty property && property.isCollection()); } - protected void expandPath(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList, + protected void expandPath(final JPAStructuredType st, final SelectionPathInfo jpaPathList, final String selectItem, final boolean targetIsCollection) throws ODataJPAModelException, ODataJPAProcessException { - final JPAPath selectItemPath = jpaEntity.getPath(selectItem); + final JPAPath selectItemPath = st.getPath(selectItem); if (selectItemPath == null) throw new ODataJPAQueryException(QUERY_PREPARATION_INVALID_SELECTION_PATH, BAD_REQUEST); if (selectItemPath.getLeaf().isComplex()) { - expandComplexPath(jpaEntity, jpaPathList, targetIsCollection, selectItemPath); + expandComplexPath(st, jpaPathList, targetIsCollection, selectItemPath); } else if (selectItemPath.isTransient()) { - addTransientAttribute(jpaEntity, jpaPathList, selectItemPath); + addTransientAttribute(st, jpaPathList, selectItemPath); } else if (!selectItemPath.getLeaf().isCollection() || targetIsCollection) {// Primitive Type jpaPathList.getODataSelections().add(selectItemPath); } } - private void expandComplexPath(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList, + private void expandComplexPath(final JPAStructuredType st, final SelectionPathInfo jpaPathList, final boolean targetIsCollection, final JPAPath selectItemPath) throws ODataJPAModelException, ODataJPAProcessorException { - final List child = jpaEntity.searchChildPath(selectItemPath); + final List child = st.searchChildPath(selectItemPath); if (targetIsCollection) { for (final JPAPath p : child) { if (p.isTransient()) - addTransientAttribute(jpaEntity, jpaPathList, p); + addTransientAttribute(st, jpaPathList, p); else jpaPathList.getODataSelections().add(p); } @@ -401,9 +398,9 @@ private void expandComplexPath(final JPAEntityType jpaEntity, final SelectionPat } } - private void addTransientAttribute(final JPAEntityType jpaEntity, final SelectionPathInfo jpaPathList, + private void addTransientAttribute(final JPAStructuredType st, final SelectionPathInfo jpaPathList, final JPAPath path) throws ODataJPAModelException, ODataJPAProcessorException { - buildRequiredSelections(jpaEntity, path, jpaPathList.getRequiredSelections()); + buildRequiredSelections(st, path, jpaPathList.getRequiredSelections()); jpaPathList.getTransientSelections().add(path); } @@ -450,29 +447,26 @@ private void addCollection(final Map> joinTables, final JPAPa } private void addSkip(final TypedQuery typedQuery) throws ODataJPAQueryException { - final SkipOption skipOption = uriResource.getSkipOption(); - if (skipOption != null || page != null) { - int skipNumber = skipOption != null ? skipOption.getValue() : page.skip(); - skipNumber = skipOption != null && page != null ? Math.max(skipOption.getValue(), page.skip()) : skipNumber; - if (skipNumber >= 0) - typedQuery.setFirstResult(skipNumber); - else - throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE, - HttpStatusCode.BAD_REQUEST, Integer.toString(skipNumber), "$skip"); + // Paging provider has to respect $skip. With the JPADefaultPagingProvider there shall be + // always a page + if (page != null) { + if (page.skip() > 0) + typedQuery.setFirstResult(page.skip()); + } else { + throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NO_PAGE_FOUND, + HttpStatusCode.INTERNAL_SERVER_ERROR, "$skip"); } } - private void addTop(final TypedQuery tupleQuery) throws ODataJPAQueryException { - final TopOption topOption = uriResource.getTopOption(); - if (topOption != null || page != null) { - int topNumber = topOption != null ? topOption.getValue() : page.top(); - topNumber = topOption != null && page != null ? Math.min(topOption.getValue(), page.top()) - : topNumber; - if (topNumber >= 0) - tupleQuery.setMaxResults(topNumber); - else - throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_INVALID_VALUE, - HttpStatusCode.BAD_REQUEST, Integer.toString(topNumber), "$top"); + private void addTop(final TypedQuery typedQuery) throws ODataJPAQueryException { + // Paging provider has to respect $top. With the JPADefaultPagingProvider there shall be + // always a page + if (page != null) { + if (page.top() < Integer.MAX_VALUE) + typedQuery.setMaxResults(page.top()); + } else { + throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NO_PAGE_FOUND, + HttpStatusCode.INTERNAL_SERVER_ERROR, "$top"); } } @@ -493,7 +487,7 @@ private List buildPathValue(final JPAEntityType jpaEntity) return jpaPathList; } - private void buildRequiredSelections(final JPAEntityType et, final JPAPath transientAttributePath, + private void buildRequiredSelections(final JPAStructuredType et, final JPAPath transientAttributePath, final Set requitedSelections) throws ODataJPAModelException, ODataJPAProcessorException { final StringBuilder pathName = new StringBuilder(); @@ -511,8 +505,7 @@ private void buildRequiredSelections(final JPAEntityType et, final JPAPath trans .orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, HttpStatusCode.INTERNAL_SERVER_ERROR, internalName)) .getExternalName(); - final StringBuilder requiredPathName = new StringBuilder(pathName.toString()).append(externalName); - requitedSelections.add(et.getPath(requiredPathName.toString(), false)); + requitedSelections.add(et.getPath(pathName + externalName, false)); } } @@ -524,9 +517,10 @@ private void buildSelectionAddETag(final JPAEntityType jpaEntity, final Collecti } /** - * In order to be able to link the result of a expand query with the super-ordinate query it is necessary to ensure + * In order to be able to link the result of an expand query with the super-ordinate query it is necessary to ensure * that the join columns are selected.
    * The same columns are required for the count query, for select as well as order by. + * * @param uriResource * @param jpaPathList * @throws ODataApplicationException @@ -606,6 +600,7 @@ private void convertSelectIntoPath(final SelectOption select, final SelectionPat /** * Skips all those properties that are or belong to a collection property or marked as transient. E.g * (Organization)Comment or (Person)InhouseAddress/Room + * * @param selectablePathList * @param allPathList * @throws ODataJPAProcessorException @@ -739,6 +734,7 @@ protected boolean isCollectionPropertyQuery() { /** * Completes NavigationInfo and add Joins for navigation parts e.g. from ../Organizations('3')/Roles + * * @param joinTables * @throws ODataJPAQueryException * @throws ODataJPAProcessorException @@ -779,6 +775,7 @@ protected final void createFromClauseNavigationJoins(final HashMap * 4.11 Addressing Derived Types * + * * @param baseType * @param potentialDerivedType * @return true if potentialDerivedType is indeed a derived type of baseType @@ -809,6 +806,7 @@ protected final void addFilterCompiler(final JPANavigationPropertyInfo navigatio /** * Start point of a Join Query e.g. triggered by ../Organizations or * ../Organizations('3')/Roles + * * @param query * @param joinTables * @throws ODataJPAQueryException diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java index a895046fe..e368e9daf 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java @@ -24,6 +24,8 @@ import java.util.Optional; import java.util.Set; +import javax.annotation.Nonnull; + import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.AbstractQuery; import jakarta.persistence.criteria.CriteriaBuilder; @@ -153,10 +155,11 @@ protected jakarta.persistence.criteria.Expression addWhereClause( final jakarta.persistence.criteria.Expression additionalExpression) { if (additionalExpression != null) { - if (whereCondition == null) + if (whereCondition == null) { whereCondition = additionalExpression; - else + } else { whereCondition = cb.and(whereCondition, additionalExpression); + } } return whereCondition; } @@ -184,20 +187,27 @@ protected void createFromClauseOrderBy(final List orderByTar final Map> joinTables, final From from) { for (final JPAAssociationPath orderBy : orderByTarget) { From join = from; - for (final JPAElement o : orderBy.getPath()) + for (final JPAElement o : orderBy.getPath()) { join = join.join(o.getInternalName(), JoinType.LEFT); + } // Take on condition from JPA metadata; no explicit on joinTables.put(orderBy.getAlias(), join); } } protected List> createGroupBy(final Map> joinTables, // NOSONAR - final From from, final Collection selectionPathList) { + final From from, final Collection selectionPathList, @Nonnull Set> orderByPaths) { try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "createGroupBy")) { final List> groupBy = new ArrayList<>(); for (final JPAPath jpaPath : selectionPathList) { - groupBy.add(ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath.getPath())); + var path = ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath.getPath()); + orderByPaths.remove(path); + groupBy.add(path); + } + + for (var path : orderByPaths) { + groupBy.add(path); } return groupBy; } @@ -209,10 +219,11 @@ protected Join createJoinFromPath(final String alias, final List join = null; JoinType joinType; for (int i = 0; i < pathList.size(); i++) { - if (i == pathList.size() - 1) + if (i == pathList.size() - 1) { joinType = finalJoinType; - else + } else { joinType = JoinType.INNER; + } if (i == 0) { join = root.join(pathList.get(i).getInternalName(), joinType); join.alias(alias); @@ -272,8 +283,9 @@ protected jakarta.persistence.criteria.Expression createProtectionWhere final Map> dummyJoinTables = new HashMap<>(1); for (final JPAProtectionInfo protection : et.getProtections()) { // look for protected attributes final List> values = claimsProvider.get().get(protection.getClaimName()); // NOSONAR - if (values.isEmpty()) + if (values.isEmpty()) { throw new ODataJPAQueryException(MISSING_CLAIM, HttpStatusCode.FORBIDDEN); + } if (!(containsAll(values))) { final Path path = ExpressionUtility.convertToCriteriaPath(dummyJoinTables, from, protection.getPath() .getPath()); @@ -358,9 +370,11 @@ protected List createWhereKeyInPathList(final JPAAssociationPath associationP protected final List extractDescriptionAttributes(final Collection jpaPathList) { final List result = new ArrayList<>(); - for (final JPAPath p : jpaPathList) - if (p.getLeaf() instanceof JPADescriptionAttribute) + for (final JPAPath p : jpaPathList) { + if (p.getLeaf() instanceof JPADescriptionAttribute) { result.add(p); + } + } return result; } @@ -390,9 +404,10 @@ protected List extractOrderByNavigationAttributes(final Orde .isCollection()) { pathString.append(((UriResourceProperty) uriResource).getProperty().getName()); final JPAPath jpaPath = jpaEntity.getPath(pathString.toString()); - if (jpaPath.isTransient()) + if (jpaPath.isTransient()) { throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, jpaPath .getLeaf().toString()); + } navigationAttributes.add(((JPACollectionAttribute) jpaPath.getLeaf()).asAssociation()); } else if (uriResource instanceof final UriResourceProperty resourceProperty) { @@ -403,10 +418,11 @@ protected List extractOrderByNavigationAttributes(final Orde throw new ODataJPAQueryException(QUERY_RESULT_CONV_ERROR, INTERNAL_SERVER_ERROR, e); } } - } else + } else { // TODO Support methods like tolower for order by as well throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, BAD_REQUEST, expression.toString()); + } } } debugger.trace(this, "The following navigation attributes in order by were found: %s", navigationAttributes @@ -421,10 +437,11 @@ protected void generateDescriptionJoin(final Map> joinTables, final JPADescriptionAttribute descriptionField = ((JPADescriptionAttribute) descriptionFieldPath.getLeaf()); final Join join = createJoinFromPath(descriptionFieldPath.getAlias(), descriptionFieldPath.getPath(), target, JoinType.LEFT); - if (descriptionField.isLocationJoin()) + if (descriptionField.isLocationJoin()) { join.on(createOnCondition(join, descriptionField, getLocale().toString())); - else + } else { join.on(createOnCondition(join, descriptionField, getLocale().getLanguage())); + } joinTables.put(descriptionField.getInternalName(), join); } } @@ -436,10 +453,11 @@ protected jakarta.persistence.criteria.Expression orWhereClause( final jakarta.persistence.criteria.Expression additionalExpression) { if (additionalExpression != null) { - if (whereCondition == null) + if (whereCondition == null) { whereCondition = additionalExpression; - else + } else { whereCondition = cb.or(whereCondition, additionalExpression); + } } return whereCondition; } @@ -454,8 +472,9 @@ Set determineAllDescriptionPath(final List descriptionFields, final Set allPath = new HashSet<>(descriptionFields); if (filter != null) { for (final JPAPath path : filter.getMember()) { - if (path.getLeaf() instanceof JPADescriptionAttribute) + if (path.getLeaf() instanceof JPADescriptionAttribute) { allPath.add(path); + } } } return allPath; @@ -472,8 +491,9 @@ private Expression createOnCondition(final Join join, final JPADe final String localValue) { final Predicate existingOn = join.getOn(); Expression result = cb.equal(determineLocalePath(join, descriptionField.getLocaleFieldName()), localValue); - if (existingOn != null) + if (existingOn != null) { result = cb.and(existingOn, result); + } for (final JPAPath value : descriptionField.getFixedValueAssignment().keySet()) { result = cb.and(result, cb.equal(determineLocalePath(join, value), descriptionField.getFixedValueAssignment().get(value))); @@ -489,16 +509,18 @@ private jakarta.persistence.criteria.Expression createProtectionWhereFo jakarta.persistence.criteria.Expression attributeRestriction = null; for (final JPAClaimsPair value : values) { // for each given claim value if (value.hasUpperBoundary) { - if (wildcardsSupported && containsWildcard((String) value.min)) + if (wildcardsSupported && containsWildcard((String) value.min)) { throw new ODataJPAQueryException(WILDCARD_UPPER_NOT_SUPPORTED, HttpStatusCode.BAD_REQUEST); - else + } else { attributeRestriction = orWhereClause(attributeRestriction, createBetween(value, path)); + } } else { - if (wildcardsSupported && containsWildcard((String) value.min)) + if (wildcardsSupported && containsWildcard((String) value.min)) { attributeRestriction = orWhereClause(attributeRestriction, cb.like((Path) path, ((String) value.min).replace('*', '%').replace('+', '_'))); - else + } else { attributeRestriction = orWhereClause(attributeRestriction, cb.equal(path, value.min)); + } } } return attributeRestriction; diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java index 58b9be219..c3e5c04f0 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapper.java @@ -15,21 +15,34 @@ import org.apache.olingo.server.api.uri.queryoption.IdOption; import org.apache.olingo.server.api.uri.queryoption.OrderByOption; import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.apache.olingo.server.api.uri.queryoption.SelectItem; import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.api.uri.queryoption.SkipOption; import org.apache.olingo.server.api.uri.queryoption.SkipTokenOption; +import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; import org.apache.olingo.server.api.uri.queryoption.TopOption; +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; public class JPACollectionExpandWrapper implements JPAExpandItem { private final JPAEntityType jpaEntityType; private final UriInfoResource uriInfo; + private final SelectOption selectOption; - JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo) { + JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo, + final JPAAssociationPath associationPath) { super(); this.jpaEntityType = jpaEntityType; this.uriInfo = uriInfo; + this.selectOption = buildSelectOption(uriInfo.getSelectOption(), associationPath); + } + + public JPACollectionExpandWrapper(final JPAEntityType jpaEntityType, final UriInfoResource uriInfo) { + this.jpaEntityType = jpaEntityType; + this.uriInfo = uriInfo; + this.selectOption = uriInfo.getSelectOption(); } @Override @@ -79,7 +92,7 @@ public SearchOption getSearchOption() { @Override public SelectOption getSelectOption() { - return uriInfo.getSelectOption(); + return selectOption; } @Override @@ -108,7 +121,7 @@ public List getUriResourceParts() { } @Override - public String getValueForAlias(String alias) { + public String getValueForAlias(final String alias) { return null; } @@ -117,4 +130,56 @@ public JPAEntityType getEntityType() { return jpaEntityType; } -} + private SelectOption buildSelectOption(final SelectOption selectOption, final JPAAssociationPath associationPath) { + final JPACollectionAttribute collectionAttribute = (JPACollectionAttribute) associationPath.getLeaf(); + final List itemsOfProperty = selectOption.getSelectItems().stream() + .filter(item -> containsCollectionProperty(collectionAttribute, item.getResourcePath())) + .toList(); + return new SelectOptionImpl(selectOption.getKind(), selectOption.getName(), selectOption.getText(), + itemsOfProperty); + } + + private boolean containsCollectionProperty(final JPACollectionAttribute collectionAttribute, + final UriInfoResource resourcePath) { + return resourcePath.getUriResourceParts().stream() + .anyMatch(part -> part.getSegmentValue().equals(collectionAttribute.getExternalName())); + } + + private static class SelectOptionImpl implements SelectOption { + + private final SystemQueryOptionKind kind; + private final String name; + private final String text; + private final List items; + + public SelectOptionImpl(final SystemQueryOptionKind kind, final String name, final String text, + final List itemsOfProperty) { + this.kind = kind; + this.name = name; + this.text = text; + this.items = itemsOfProperty; + } + + @Override + public SystemQueryOptionKind getKind() { + return kind; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getText() { + return text; + } + + @Override + public List getSelectItems() { + return items; + } + + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java index 5c71270f3..e2f6f72a2 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionItemInfo.java @@ -8,11 +8,11 @@ public final class JPACollectionItemInfo extends JPAInlineItemInfo { JPACollectionItemInfo(final JPAServiceDocument sd, final JPAExpandItem uriInfo, - final JPAAssociationPath expandAssociation, final List hops) { + final JPAAssociationPath expandAssociation, final List parentHops) { - super(uriInfo, expandAssociation, hops); + super(uriInfo, expandAssociation, parentHops); - for (JPANavigationPropertyInfo predecessor : hops) + for (final JPANavigationPropertyInfo predecessor : parentHops) this.hops.add(new JPANavigationPropertyInfo(predecessor)); this.hops.get(this.hops.size() - 1).setAssociationPath(expandAssociation); this.hops.add(new JPANavigationPropertyInfo(sd, expandAssociation, uriInfo, uriInfo.getEntityType())); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java index 5774b2826..6d926e484 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java @@ -35,6 +35,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +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.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; @@ -76,7 +77,7 @@ public JPACollectionQueryResult execute() throws ODataApplicationException { protected SelectionPathInfo buildSelectionPathList(final UriInfoResource uriResource) throws ODataApplicationException { final SelectionPathInfo jpaPathList = new SelectionPathInfo<>(); - final String pathPrefix = ""; + final String pathPrefix = Utility.determinePropertyNavigationPrefix(uriResource.getUriResourceParts()); final SelectOption select = uriResource.getSelectOption(); // Following situations have to be handled: // - .../Organizations --> Select all collection attributes @@ -86,23 +87,22 @@ protected SelectionPathInfo buildSelectionPathList(final UriInfoResourc // - .../Persons('99')/InhouseAddress?$select=Building --> Select attributes of complex collection given by select // clause try { + if (SelectOptionUtil.selectAll(select)) // If the collection is part of a navigation take all the attributes - expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix - + JPAPath.PATH_SEPARATOR + this.association.getAlias(), true); + expandPath(jpaEntity, jpaPathList, this.association.getAlias(), true); else { + final var st = jpaEntity; for (final SelectItem sItem : select.getSelectItems()) { - final JPAPath selectItemPath = selectItemAsPath(pathPrefix, sItem); + final JPAPath selectItemPath = selectItemAsPath(st, pathPrefix, sItem); if (pathContainsCollection(selectItemPath)) { if (selectItemPath.getLeaf().isComplex()) { - final JPAAttribute attribute = selectItemPath.getLeaf(); - expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? attribute.getExternalName() : pathPrefix - + JPAPath.PATH_SEPARATOR + attribute.getExternalName(), true); + expandPath(st, jpaPathList, selectItemPath.getAlias(), true); } else { jpaPathList.getODataSelections().add(selectItemPath); } } else if (selectItemPath.getLeaf().isComplex()) { - expandPath(jpaEntity, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix + expandPath(st, jpaPathList, pathPrefix.isEmpty() ? this.association.getAlias() : pathPrefix + JPAPath.PATH_SEPARATOR + this.association.getAlias(), true); } } @@ -113,15 +113,15 @@ protected SelectionPathInfo buildSelectionPathList(final UriInfoResourc return jpaPathList; } - private JPAPath selectItemAsPath(final String pathPrefix, final SelectItem selectionItem) - throws ODataJPAModelException, - ODataJPAQueryException { + private JPAPath selectItemAsPath(final JPAStructuredType st, final String pathPrefix, final SelectItem selectionItem) + throws ODataJPAModelException, ODataJPAQueryException { - String pathItem = selectionItem.getResourcePath().getUriResourceParts().stream().map(path -> (path - .getSegmentValue())).collect(Collectors.joining(JPAPath.PATH_SEPARATOR)); + String pathItem = selectionItem.getResourcePath().getUriResourceParts().stream() + .map(path -> (path.getSegmentValue())) + .collect(Collectors.joining(JPAPath.PATH_SEPARATOR)); pathItem = pathPrefix == null || pathPrefix.isEmpty() ? pathItem : pathPrefix + JPAPath.PATH_SEPARATOR + pathItem; - final JPAPath selectItemPath = jpaEntity.getPath(pathItem); + final JPAPath selectItemPath = st.getPath(pathItem); if (selectItemPath == null) throw new ODataJPAQueryException(QUERY_PREPARATION_INVALID_SELECTION_PATH, BAD_REQUEST); return selectItemPath; @@ -157,6 +157,7 @@ protected List> createSelectClause(final Map> jo /** * Splits up a expand results, so it is returned as a map that uses a concatenation of the field values know by the * parent. + * * @param intermediateResult * @param associationPath * @param skip 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 8c32d16c4..2a2f3a94b 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 @@ -11,9 +11,11 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -127,13 +129,14 @@ public Subquery getSubQuery(@Nullable final Subquery childQuery, .getOrderByOption()); createRoots(childQuery, queries, nextQuery); buildJoinTable(orderByAttributes, emptyList(), childQuery); + final Set> orderByPaths = new HashSet<>(); final List selections = selectionPathIn(); nextQuery.where(createWhere(childQuery)); nextQuery.multiselect(selectIn(childQuery, selections)); - nextQuery.orderBy(createOrderBy(childQuery)); + nextQuery.orderBy(createOrderBy(childQuery, orderByPaths)); nextQuery.setFirstResult(getSkipValue(childQuery)); nextQuery.setMaxResults(getTopValue(childQuery)); - nextQuery.groupBy(createGroupBy(childQuery, orderByAttributes, selections)); + nextQuery.groupBy(createGroupBy(joinTables, queryRoot, selections, orderByPaths)); return nextQuery; } } @@ -156,18 +159,19 @@ protected final JPAFilterCrossComplier addFilterCompiler(final JPANavigationProp protected Expression applyAdditionalFilter(final Expression where) throws ODataApplicationException { - if (navigationInfo.getFilterCompiler() != null && aggregationType == null) + if (navigationInfo.getFilterCompiler() != null && aggregationType == null) { try { return addWhereClause(where, navigationInfo.getFilterCompiler().compile()); } catch (final ExpressionVisitException e) { throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } + + } return where; } void buildJoinTable(final List orderByAttributes, final Collection selectionPath, - final Subquery childQuery) - throws ODataApplicationException { + final Subquery childQuery) throws ODataApplicationException { createFromClauseJoinTable(joinTables, childQuery); createFromClauseOrderBy(orderByAttributes, joinTables, queryRoot); createFromClauseDescriptionFields(selectionPath, joinTables, queryRoot, singletonList(navigationInfo)); @@ -190,25 +194,17 @@ private void createFromClauseJoinTable(final Map> joinTables, void setFilter(final JPANavigationPropertyInfo navigationInfo) throws ODataJPAModelException, ODataJPAProcessorException, ODataJPAQueryException { - if (navigationInfo.getFilterCompiler() == null) + if (navigationInfo.getFilterCompiler() == null) { navigationInfo.setFilterCompiler(addFilterCompiler(navigationInfo)); - } - - private List> createGroupBy(final Subquery childQuery, - final List orderByAttributes, final List selections) { - if (!orderByAttributes.isEmpty()) { - - return selections.stream() - .map(path -> mapOnToSelection(path, queryRoot, childQuery)) - .collect(toList()); // NOSONAR } - return emptyList(); } - private List createOrderBy(final Subquery childQuery) throws ODataApplicationException { + private List createOrderBy(final Subquery childQuery, final Set> orderByPaths) + throws ODataApplicationException { if (!hasRowLimit(childQuery)) { final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, queryRoot, cb, groups); - return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage()); + return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage(), + orderByPaths); } return emptyList(); } @@ -234,18 +230,20 @@ 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())); - else + } else { this.queryRoot = subQuery.from(this.jpaEntity.getTypeClass()); + } navigationInfo.setFromClause(queryRoot); } private Expression createWhere(final Subquery childQuery) throws ODataApplicationException { - if (hasRowLimit(childQuery)) + if (hasRowLimit(childQuery)) { return createWhereByRowNumber(queryRoot, navigationInfo); + } return createWhereSubQuery(childQuery, false); } @@ -255,9 +253,10 @@ Expression createWhereSubQuery(@Nullable final Subquery childQuery, Expression whereCondition = createWhereByKey(queryRoot, this.keyPredicates, jpaEntity); whereCondition = addWhereClause(whereCondition, createProtectionWhereForEntityType(claimsProvider, jpaEntity, queryRoot)); - if (queryJoinTable != null) + if (queryJoinTable != null) { whereCondition = addWhereClause(whereCondition, createWhereTableJoin(queryJoinTable, queryRoot, association, useInverse)); + } if (childQuery != null) { whereCondition = addWhereClause(whereCondition, @@ -269,18 +268,22 @@ 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) + if (navigationInfo.getPage() != null) { + return navigationInfo.getPage().skip() > 0 ? navigationInfo.getPage().skip() : null; + } + if (navigationInfo.getUriInfo().getSkipOption() != null && childQuery == null) { return navigationInfo.getUriInfo().getSkipOption().getValue(); + } return null; } private Integer getTopValue(@Nullable final Subquery childQuery) { - if (navigationInfo.getPage() != null) - return navigationInfo.getPage().top(); - if (navigationInfo.getUriInfo().getTopOption() != null && childQuery == null) + if (navigationInfo.getPage() != null) { + return navigationInfo.getPage().top() < Integer.MAX_VALUE ? navigationInfo.getPage().top() : null; + } + if (navigationInfo.getUriInfo().getTopOption() != null && childQuery == null) { return navigationInfo.getUriInfo().getTopOption().getValue(); + } return null; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java index 774dcf749..07e90ac41 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java @@ -65,6 +65,8 @@ public List buildExpandItemInfo(final JPAServiceDocument sd, * ../Organizations('1')/Comment or
    * ../CollectionDeeps?$select=FirstLevel/SecondLevel or
    * ../CollectionDeeps/FirstLevel + * ../CollectionDeeps/FirstLevel/SecondLevel?$select=...,... + * * @param sd * @param uriResourceInfo * @param optional @@ -93,11 +95,12 @@ public List buildCollectionItemInfo(final JPAServiceDocum // Moved } } else { - + // Organizations('1')?$select=Comment + // Et/St?$select=Cp if (SelectOptionUtil.selectAll(select)) { // No navigation, extract all collection attributes final JPAStructuredType st = (JPAStructuredType) pathInfo[ST_INDEX]; - final Set collectionProperties = new HashSet<>(); + final Set collectionProperties = new HashSet<>(); for (final JPAPath path : st.getPathList()) { final StringBuilder pathName = new StringBuilder(pathInfo[PATH_INDEX].toString()); for (final JPAElement pathElement : path.getPath()) { @@ -107,27 +110,31 @@ public List buildCollectionItemInfo(final JPAServiceDocum && !attribute.isTransient()) { final JPAPath collectionPath = ((JPAEntityType) pathInfo[ET_INDEX]) .getPath(pathName.deleteCharAt(pathName.length() - 1).toString()); - collectionProperties.add(collectionPath.getLeaf()); + collectionProperties.add((JPACollectionAttribute) collectionPath.getLeaf()); } break; } } } - for (final JPAElement pathElement : collectionProperties) { + for (final var pathElement : collectionProperties) { final JPACollectionExpandWrapper item = new JPACollectionExpandWrapper((JPAEntityType) pathInfo[ET_INDEX], uriResourceInfo); - itemList.add(new JPACollectionItemInfo(sd, item, ((JPACollectionAttribute) pathElement) - .asAssociation(), grandParentHops)); + itemList.add(new JPACollectionItemInfo(sd, item, pathElement.asAssociation(), grandParentHops)); } } else { + // Et?$select=Cp + // Et/St?$select=Cp,Cp + // Et/St/St?$select=Cp + // Et/St?$select=St/Cp final JPAStructuredType st = (JPAStructuredType) pathInfo[ST_INDEX]; final Set selectOptions = getCollectionAttributesFromSelection(st, uriResourceInfo .getSelectOption()); - for (final JPAPath path : selectOptions) { + final Map collectionPaths = Utility.determineAssociations(sd, + startResourceList, selectOptions); + for (final JPAAssociationPath path : collectionPaths.values()) { final JPACollectionExpandWrapper item = new JPACollectionExpandWrapper((JPAEntityType) pathInfo[ET_INDEX], - uriResourceInfo); - itemList.add(new JPACollectionItemInfo(sd, item, ((JPACollectionAttribute) path.getLeaf()) - .asAssociation(), grandParentHops)); + uriResourceInfo, path); + itemList.add(new JPACollectionItemInfo(sd, item, path, grandParentHops)); } } } @@ -171,7 +178,7 @@ private Object[] determineNavigationElements(final JPAServiceDocument sd, return result; } - protected Set getCollectionAttributesFromSelection(final JPAStructuredType jpaEntity, + private Set getCollectionAttributesFromSelection(final JPAStructuredType jpaEntity, final SelectOption select) throws ODataApplicationException, ODataJPAModelException { final Set collectionAttributes = new HashSet<>(); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java index 6f83ac863..58e1de561 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java @@ -8,9 +8,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; @@ -105,14 +107,16 @@ public JPAExpandQueryResult execute() throws ODataApplicationException { } private long determineTop() { - if (uriResource.getTopOption() != null) + if (uriResource.getTopOption() != null) { return uriResource.getTopOption().getValue(); + } return Long.MAX_VALUE; } private long determineSkip() { - if (uriResource.getSkipOption() != null) + if (uriResource.getSkipOption() != null) { return uriResource.getSkipOption().getValue(); + } return 0; } @@ -225,23 +229,27 @@ final Map count() throws ODataApplicationException { private JPAQueryCreationResult createTupleQuery() throws ODataApplicationException, JPANoSelectionException { try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createTupleQuery")) { - final List orderByAttributes = extractOrderByNavigationAttributes(uriResource.getOrderByOption()); + final List orderByAttributes = extractOrderByNavigationAttributes(uriResource + .getOrderByOption()); final SelectionPathInfo selectionPath = buildSelectionPathList(this.uriResource); final Map> joinTables = createFromClause(orderByAttributes, selectionPath.joinedPersistent(), cq, lastInfo); // TODO handle Join Column is ignored cq.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), target, groups)).distinct(true); final jakarta.persistence.criteria.Expression whereClause = createWhere(); - if (whereClause != null) + if (whereClause != null) { cq.where(whereClause); + } + final Set> orderByPaths = new HashSet<>(); final List orderBy = createOrderByJoinCondition(association); orderBy.addAll(new JPAOrderByBuilder(jpaEntity, target, cb, groups).createOrderByList(joinTables, uriResource, - page)); + page, orderByPaths)); cq.orderBy(orderBy); - if (!orderByAttributes.isEmpty()) - cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent())); + if (!orderByAttributes.isEmpty()) { + cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(), orderByPaths)); + } final TypedQuery query = em.createQuery(cq); 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 b51c4c3dc..db76cc838 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 @@ -12,10 +12,12 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; @@ -233,7 +235,7 @@ private void createFromClauseRoot(final CriteriaQuery query, final HashMap createOrderBy(final Map> joinTables) throws ODataApplicationException { + private List createOrderBy(final Map> joinTables, Set> orderByPaths) throws ODataApplicationException { if (association.hasJoinTable() && hasRowLimit(lastInfo)) { try { final List orders = new ArrayList<>(); @@ -251,7 +253,7 @@ private List createOrderBy(final Map> joinTables) thro return orderByBuilder.createOrderByListAlias(joinTables, uriResource.getOrderByOption(), association); } else { final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, root, cb, groups); - return orderByBuilder.createOrderByList(joinTables, uriResource.getOrderByOption(), association); + return orderByBuilder.createOrderByList(joinTables, uriResource.getOrderByOption(), association, orderByPaths); } } @@ -291,10 +293,12 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat subQuery); tupleQuery.where(createWhere(subQuery, lastInfo)); tupleQuery.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), groups)); - tupleQuery.orderBy(createOrderBy(joinTables)); + final Set> orderByPaths = new HashSet<>(); + tupleQuery.orderBy(createOrderBy(joinTables, orderByPaths)); tupleQuery.distinct(orderByAttributes.isEmpty()); - if (!orderByAttributes.isEmpty()) - cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent())); + if (!orderByAttributes.isEmpty()) { + cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(),orderByPaths)); + } final TypedQuery query = em.createQuery(tupleQuery); return new JPAQueryCreationResult(query, selectionPath); } 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 64d1e8f1b..20c213707 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 @@ -7,14 +7,17 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.AbstractQuery; +import jakarta.persistence.criteria.Path; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.server.api.OData; @@ -42,8 +45,9 @@ private static JPAEntityType determineTargetEntityType(final JPAODataRequestCont final var resources = requestContext.getUriInfo().getUriResourceParts(); final var bindingTarget = Utility.determineBindingTarget(resources); - if (bindingTarget instanceof EdmBoundCast) + if (bindingTarget instanceof EdmBoundCast) { return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getEntityType()); + } return requestContext.getEdmProvider().getServiceDocument().getEntity(bindingTarget.getName()); } @@ -87,13 +91,16 @@ public JPAConvertibleResult execute() throws ODataApplicationException { .distinct(determineDistinct()); final var whereClause = createWhere(); - if (whereClause != null) + if (whereClause != null) { cq.where(whereClause); + } - cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, uriResource, page)); + final Set> orderByPaths = new HashSet<>(); + cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, uriResource, page, orderByPaths)); - if (!orderByNavigationAttributes.isEmpty()) - cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent())); + if (!orderByNavigationAttributes.isEmpty()) { + cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent(), orderByPaths)); + } final TypedQuery typedQuery = em.createQuery(cq); addTopSkip(typedQuery); @@ -111,8 +118,9 @@ public JPAConvertibleResult execute() throws ODataApplicationException { } private JPAOrderByBuilder createOrderByBuilder() throws ODataJPAQueryException { - if (entitySet.isPresent()) + if (entitySet.isPresent()) { return new JPAOrderByBuilder(entitySet.get(), jpaEntity, target, cb, groups); + } return new JPAOrderByBuilder(jpaEntity, target, cb, groups); } @@ -143,8 +151,9 @@ private boolean determineDistinct() { private JPAConvertibleResult returnEmptyResult(final Collection selectionPath) { if (lastInfo.getAssociationPath() != null - && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) + && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) { return new JPACollectionQueryResult(jpaEntity, lastInfo.getAssociationPath(), selectionPath); + } return new JPAExpandQueryResult(jpaEntity, selectionPath); } @@ -152,8 +161,9 @@ private JPAConvertibleResult returnResult(@Nonnull final Collection sel final HashMap> result) throws ODataApplicationException { final var odataEntityType = determineODataTargetEntityType(requestContext); if (lastInfo.getAssociationPath() != null - && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) + && (lastInfo.getAssociationPath().getLeaf() instanceof JPACollectionAttribute)) { return new JPACollectionQueryResult(result, null, odataEntityType, lastInfo.getAssociationPath(), selectionPath); + } return new JPAExpandQueryResult(result, Collections.emptyMap(), odataEntityType, selectionPath); } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java index 969f6e0dc..b988ae3cb 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java @@ -104,11 +104,12 @@ final class JPAOrderByBuilder { * [...ComplexProperty,...PrimitiveProperty]
    * .../Organizations?$orderby=Roles/$count --> one item, two resourcePaths [...NavigationProperty,...Count]
    * .../Organizations?$orderby=Roles/$count desc,Address/Country asc -->two items + * .../AdminustrativeDivision?$orderby=Parent/DivisionCode *

    * SQL example to order by number of entities *

    * - * SELECT t0."BusinessPartnerID" ,COUNT(t1."BusinessPartnerID")
    + * SELECT t0."BusinessPartnerID" ,COUNT(t1."BusinessPartnerID") *

    FROM "OLINGO"."org.apache.olingo.jpa::BusinessPartner" t0 
    * LEFT OUTER JOIN "OLINGO"."org.apache.olingo.jpa::BusinessPartnerRole" t1
    * ON (t1."BusinessPartnerID" = t0."BusinessPartnerID")} @@ -119,25 +120,27 @@ final class JPAOrderByBuilder { * @since 1.0.0 * @param joinTables * @param uriResource + * @param page + * @param orderByPaths: A collection of paths to the properties within the ORDER BY clause * @return A list of generated orderby clauses * @throws ODataApplicationException */ @Nonnull List createOrderByList(@Nonnull final Map> joinTables, - @Nonnull final UriInfoResource uriResource, @Nullable final JPAODataPage page) throws ODataApplicationException { + @Nonnull final UriInfoResource uriResource, @Nullable final JPAODataPage page, final Set> orderByPaths) + throws ODataApplicationException { final List result = new ArrayList<>(); - final Set> orderBys = new HashSet<>(); try { if (uriResource.getOrderByOption() != null) { LOGGER.trace(LOG_ORDER_BY); - addOrderByFromUriResource(joinTables, result, orderBys, uriResource.getOrderByOption()); + addOrderByFromUriResource(joinTables, result, orderByPaths, uriResource.getOrderByOption()); watchDog.watch(result); } if (uriResource.getTopOption() != null || uriResource.getSkipOption() != null || (page != null && page.top() != Integer.MAX_VALUE)) { LOGGER.trace("Determined $top/$skip or page: add primary key to Order By"); - addOrderByPrimaryKey(result, orderBys); + addOrderByPrimaryKey(result, orderByPaths); } } catch (final ODataJPAModelException e) { throw new ODataJPAQueryException(e, BAD_REQUEST); @@ -157,11 +160,10 @@ List createOrderByList(final Map> joinTables) { */ @Nonnull List createOrderByList(@Nonnull final Map> joinTables, - @Nullable final OrderByOption orderBy, @Nonnull final JPAAssociationPath association) + @Nullable final OrderByOption orderBy, @Nonnull final JPAAssociationPath association, @Nonnull final Set> orderByPaths) throws ODataApplicationException { final List result = new ArrayList<>(); - final Set> orderByPaths = new HashSet<>(); try { LOGGER.trace("Determined relationship and add corresponding to OrderBy"); addOrderByJoinCondition(association, result); @@ -259,8 +261,9 @@ From determineParentFrom(final JPAAss final List navigationInfo) throws ODataJPAQueryException { for (final JPANavigationPropertyInfo item : navigationInfo) { - if (item.getAssociationPath() == association) + if (item.getAssociationPath() == association) { return (From) item.getFromClause(); + } } throw new ODataJPAQueryException(ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_FILTER_ERROR, HttpStatusCode.BAD_REQUEST); @@ -269,10 +272,11 @@ From determineParentFrom(final JPAAss private void addOrderByExpression(final List orders, final OrderByItem orderByItem, final jakarta.persistence.criteria.Expression expression) { - if (orderByItem.isDescending()) + if (orderByItem.isDescending()) { orders.add(cb.desc(expression)); - else + } else { orders.add(cb.asc(expression)); + } } private void addOrderByExpression(final List orders, final OrderByItem orderByItem, @@ -294,7 +298,7 @@ private void addOrderByFromUriResource(final Map> joinTables, final UriInfoResource resourcePath = member.getResourcePath(); JPAStructuredType type = jpaEntity; Path path = target; - final StringBuilder externalPath = new StringBuilder(); + StringBuilder externalPath = new StringBuilder(); for (final UriResource uriResourceItem : resourcePath.getUriResourceParts()) { if (isPrimitiveSimpleProperty(uriResourceItem)) { path = convertPropertyPath(type, uriResourceItem, path); @@ -304,13 +308,19 @@ private void addOrderByFromUriResource(final Map> joinTables, addPathByAttribute(externalPath, attribute); path = path.get(attribute.getInternalName()); type = attribute.getStructuredType(); - } else if (uriResourceItem instanceof UriResourceNavigation + } else if ((uriResourceItem instanceof final UriResourceNavigation navigation + && navigation.isCollection()) || (uriResourceItem instanceof final UriResourceProperty property && property.isCollection())) { // In case the orderby contains a navigation or collection a $count has to follow. This is ensured by Olingo appendPathByCollection(externalPath, uriResourceItem); final From join = joinTables.get(externalPath.toString()); addOrderByExpression(orders, orderByItem, cb.count(join)); + } else if (uriResourceItem instanceof UriResourceNavigation) { + appendPathByCollection(externalPath, uriResourceItem); + type = type.getAssociationPath(externalPath.toString()).getTargetType(); + path = joinTables.get(externalPath.toString()); + externalPath = new StringBuilder(); } else if (!(uriResourceItem instanceof UriResourceCount)) { throw new ODataJPANotImplementedException("orderby using " + uriResourceItem.getKind().name()); } @@ -324,16 +334,19 @@ private Path convertPropertyPath(final JPAStructuredType type, throws ODataJPAQueryException, ODataJPAProcessorException, ODataJPAModelException { final JPAPath attributePath = type.getPath(((UriResourceProperty) uriResourceItem).getProperty().getName()); - if (attributePath == null) + if (attributePath == null) { throw new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, uriResourceItem.getSegmentValue()); - if (!attributePath.isPartOfGroups(groups)) + } + if (!attributePath.isPartOfGroups(groups)) { throw new ODataJPAQueryException(QUERY_PREPARATION_NOT_ALLOWED_MEMBER, FORBIDDEN, attributePath.getAlias()); + } Path path = startPath; for (final JPAElement pathElement : attributePath.getPath()) { - if (pathElement instanceof final JPAAttribute attribute && attribute.isTransient()) + if (pathElement instanceof final JPAAttribute attribute && attribute.isTransient()) { throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, pathElement.getExternalName()); + } path = path.get(pathElement.getInternalName()); } path.alias(attributePath.getAlias()); @@ -358,10 +371,11 @@ private void addPathByAttribute(final StringBuilder externalPath, final JPAAttri } private void appendPathByCollection(final StringBuilder externalPath, final UriResource uriResourceItem) { - if (uriResourceItem instanceof final UriResourceNavigation navigation) + if (uriResourceItem instanceof final UriResourceNavigation navigation) { externalPath.append(navigation.getProperty().getName()); - else + } else { externalPath.append(((UriResourceProperty) uriResourceItem).getProperty().getName()); + } } private JPAAttribute getAttribute(final JPAStructuredType type, final UriResource uriResourceItem) @@ -370,9 +384,10 @@ private JPAAttribute getAttribute(final JPAStructuredType type, final UriResourc final JPAAttribute attribute = type.getAttribute((UriResourceProperty) uriResourceItem).orElseThrow( () -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, uriResourceItem.getSegmentValue())); - if (attribute.isTransient()) + if (attribute.isTransient()) { throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, attribute.getExternalName()); + } return attribute; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java index 37ad95bb4..76410e368 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.annotation.Nonnull; @@ -42,6 +43,7 @@ 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.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; @@ -50,19 +52,28 @@ public final class Utility { - private static final String FOUND_CAST_FROM = "Found cast from "; public static final String VALUE_RESOURCE = "$VALUE"; + private static final String FOUND_CAST_FROM = "Found cast from "; private static final Log LOGGER = LogFactory.getLog(Utility.class); + private Utility() { + // suppress instance creation + } + public static JPAAssociationPath determineAssociation(final JPAServiceDocument sd, final EdmType navigationStart, final StringBuilder associationName) throws ODataApplicationException { - final JPAEntityType navigationStartType; try { - navigationStartType = sd.getEntity(navigationStart); + final JPAEntityType navigationStartType = sd.getEntity(navigationStart); if (navigationStartType == null) throw new ODataJPAUtilException(UNKNOWN_ENTITY_TYPE, BAD_REQUEST); - return navigationStartType.getAssociationPath(associationName.toString()); + JPAAssociationPath path = navigationStartType.getAssociationPath(associationName.toString()); + if (path == null) { + final var collection = navigationStartType.getCollectionAttribute(associationName.toString()); + if (collection != null) + path = collection.asAssociation(); + } + return path; } catch (final ODataJPAModelException e) { throw new ODataJPAUtilException(UNKNOWN_NAVI_PROPERTY, BAD_REQUEST, e); } @@ -91,8 +102,7 @@ else if (entitySet.getTypeFilterOnCollection() != null) navigationStartType = sd.getEntity(navigation.getProperty().getType()); } JPAAssociationPath path = navigationStartType == null ? null : navigationStartType.getAssociationPath( - associationName - .toString()); + associationName.toString()); if (path == null && navigationStartType != null) { final JPACollectionAttribute collection = navigationStartType.getCollectionAttribute(associationName .toString()); @@ -135,6 +145,24 @@ public static Map determineAssociations(final return pathList; } + public static Map determineAssociations(final JPAServiceDocument sd, + final List startResourceList, final Set selectOptions) throws ODataApplicationException { + final Map pathList = new HashMap<>(); + final StringBuilder associationNamePrefix = new StringBuilder(); + final UriResource startResourceItem = createAssociationNamePrefix(startResourceList, associationNamePrefix); + + for (final var selectOption : selectOptions) { + + final StringBuilder associationName = associationNamePrefix.length() > 0 + ? new StringBuilder(associationNamePrefix).append(selectOption.getAlias()) + : new StringBuilder(selectOption.getAlias()); + pathList.put(selectOption, determineAssociationPath(sd, (UriResourcePartTyped) startResourceItem, + associationName)); + } + + return pathList; + } + private static UriResource createAssociationNamePrefix(final List startResourceList, final StringBuilder associationNamePrefix) { // Example1 : /Organizations('3')/AdministrativeInformation?$expand=Created/User @@ -156,13 +184,26 @@ private static void determineAssociations(final JPAServiceDocument sd, final Exp final Map pathList, final StringBuilder associationNamePrefix, final UriResource startResourceItem, final ExpandItem item) throws ODataApplicationException { - StringBuilder associationName; final List targetResourceList = item.getResourcePath().getUriResourceParts(); // Has Cast + final StringBuilder associationName = determinePathAlias(associationNamePrefix, targetResourceList); + if (item.getLevelsOption() != null) + pathList.put(new JPAExpandLevelWrapper(sd, expandOption, item), Utility.determineAssociation(sd, + ((UriResourcePartTyped) startResourceItem).getType(), associationName)); + else + pathList.put(new JPAExpandItemWrapper(sd, item), Utility.determineAssociation(sd, + ((UriResourcePartTyped) startResourceItem).getType(), associationName)); + } + + private static StringBuilder determinePathAlias(final StringBuilder pathAliasPrefix, + final List resourceList) { + StringBuilder associationName; associationName = new StringBuilder(); - associationName.append(associationNamePrefix); - UriResource targetResourceItem = null; - for (int i = 0; i < targetResourceList.size(); i++) { - targetResourceItem = targetResourceList.get(i); + associationName.append(pathAliasPrefix); + + for (final var targetResourceItem : resourceList) { + if (targetResourceItem.getKind() == UriResourceKind.entitySet + || targetResourceItem.getKind() == UriResourceKind.singleton) + continue; if (targetResourceItem.getKind() != UriResourceKind.navigationProperty) { associationName.append(((UriResourceProperty) targetResourceItem).getProperty().getName()); associationName.append(PATH_SEPARATOR); @@ -171,12 +212,7 @@ private static void determineAssociations(final JPAServiceDocument sd, final Exp break; } } - if (item.getLevelsOption() != null) - pathList.put(new JPAExpandLevelWrapper(sd, expandOption, item), Utility.determineAssociation(sd, - ((UriResourcePartTyped) startResourceItem).getType(), associationName)); - else - pathList.put(new JPAExpandItemWrapper(sd, item), Utility.determineAssociation(sd, - ((UriResourcePartTyped) startResourceItem).getType(), associationName)); + return associationName; } private static void determineAssociationsStar(final JPAServiceDocument sd, final List startResourceList, @@ -303,6 +339,7 @@ public static EdmBindingTargetInfo determineModifyEntitySetAndKeys(@Nonnull fina * Converts the OData navigation list into a intermediate one. Direction is top - down usage e.g. join query. *

    * The method only supports queries that start with an entity set or singleton. + * * @param sd * @param resourceParts * @param filterOption @@ -426,6 +463,14 @@ public static EdmEntityType determineTargetEntityType(final List re return targetEdmEntity; } + public static JPAStructuredType determineTargetStructuredType(final JPAStructuredType st, + final List resources) throws ODataJPAModelException { + final var path = determinePropertyNavigationPrefix(resources); + if (path != null && !path.isEmpty()) + return st.getPath(path).getLeaf().getStructuredType(); + return st; + } + public static boolean hasNavigation(final List uriResourceParts) { if (uriResourceParts != null) { for (int i = uriResourceParts.size() - 1; i >= 0; i--) { @@ -436,6 +481,20 @@ public static boolean hasNavigation(final List uriResourceParts) { return false; } + public static boolean hasCollection(final List resourceParts) { + if (resourceParts != null) { + for (int i = resourceParts.size() - 1; i >= 0; i--) { + if (isCollection(resourceParts.get(i))) + return true; + } + } + return false; + } + + public static boolean isCollection(final UriResource resourcePart) { + return (resourcePart instanceof final UriResourceProperty resourceProperty && resourceProperty.isCollection()); + } + private static EdmBindingTarget determineBindingTargetOfEntitySet(final UriResourceEntitySet resourceItem) { EdmBindingTarget targetEdmBindingTarget; if (resourceItem.getTypeFilterOnCollection() != null) { @@ -478,8 +537,4 @@ private static EdmNavigationProperty findNavigationProperty(final EdmBindingTarg // Is this sufficient for path via complex types? return bindingTarget.getEntityType().getNavigationProperty(path.getAlias()); } - - private Utility() { - // suppress instance creation - } } 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 c778eb6f0..c4272a12f 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 @@ -79,6 +79,7 @@ ODataJPAQueryException.QUERY_PREPARATION_NOT_ALLOWED_MEMBER = Not authorized to 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.QUERY_PREPARATION_NO_PAGE_FOUND=No page found while converting '%1$s' 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 index 5d215b6aa..ad7e28516 100644 --- 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 @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; 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.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; @@ -14,6 +15,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + class JPADefaultPagingProviderTest { private JPAODataPagingProvider cut; @@ -76,4 +79,17 @@ void testGetFirstPageReturnsTopAndSkipAsRequested() throws ODataApplicationExcep assertNull(act.get().skipToken()); } + @Test + void testGetFirstPageThrowsExceptionSkipNegative() { + when(skipOption.getValue()).thenReturn(-99); + when(uriInfo.getSkipOption()).thenReturn(skipOption); + assertThrows(ODataJPAQueryException.class, () -> cut.getFirstPage(null, null, uriInfo, null, null, null)); + } + + @Test + void testGetFirstPageThrowsExceptionTopNegative() { + when(topOption.getValue()).thenReturn(-99); + when(uriInfo.getTopOption()).thenReturn(topOption); + assertThrows(ODataJPAQueryException.class, () -> cut.getFirstPage(null, null, uriInfo, null, null, null)); + } } 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 df5caabbd..e17a169d6 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 @@ -34,7 +34,7 @@ public JPAODataContextAccessDouble(final JPAEdmProvider edmProvider, final DataS this.dataSource = dataSource; this.processor = new JPADefaultDatabaseProcessor(); this.packageNames = packages; - this.pagingProvider = provider; + this.pagingProvider = provider != null ? provider : new JPADefaultPagingProvider(); this.annotationProvider = annotationProvider; try { this.directives = JPAODataServiceContext.with().useQueryDirectives().maxValuesInInClause(3).build().build() 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 9f7577225..99f5d8fdb 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 @@ -108,11 +108,10 @@ void checkReturnsProvidedPagingProvider() throws ODataException { } @Test - void checkReturnsDefaultProvidedPagingIfNotProvider() throws ODataException { + void checkReturnsDefaultPagingProviderIfNotProvider() throws ODataException { cut = JPAODataServiceContext.with() .setDataSource(dataSource) .setPUnit(PUNIT_NAME) - .setPagingProvider(null) .build(); assertTrue(cut.getPagingProvider() instanceof JPADefaultPagingProvider); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAInstanceResultConverter.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverterTest.java similarity index 72% rename from jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAInstanceResultConverter.java rename to jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverterTest.java index 265b931a2..31c119239 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAInstanceResultConverter.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/converter/JPAEntityResultConverterTest.java @@ -1,37 +1,48 @@ -package com.sap.olingo.jpa.processor.core.query; +package com.sap.olingo.jpa.processor.core.converter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import org.apache.olingo.commons.api.data.EntityCollection; +import org.apache.olingo.commons.api.edm.EdmEntityType; 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.serializer.SerializerException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.sap.olingo.jpa.processor.core.converter.JPAEntityResultConverter; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; +import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.util.EdmEntityTypeDouble; 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.UriHelperDouble; -class TestJPAInstanceResultConverter extends TestBase { +class JPAEntityResultConverterTest extends TestBase { public static final int NO_POSTAL_ADDRESS_FIELDS = 8; public static final int NO_ADMIN_INFO_FIELDS = 2; private JPAEntityResultConverter cut; private List jpaQueryResult; private UriHelperDouble uriHelper; + private JPAODataEtagHelper etagHelper; @BeforeEach void setup() throws ODataException { helper = new TestHelper(emf, PUNIT_NAME); + etagHelper = mock(JPAODataEtagHelper.class); jpaQueryResult = new ArrayList<>(); final HashMap keyStrings = new HashMap<>(); keyStrings.put("BE21", "DivisionCode='BE21',CodeID='NUTS2',CodePublisher='Eurostat'"); @@ -40,7 +51,7 @@ void setup() throws ODataException { uriHelper = new UriHelperDouble(); uriHelper.setKeyPredicates(keyStrings, "DivisionCode"); cut = new JPAEntityResultConverter(uriHelper, helper.sd, - jpaQueryResult, new EdmEntityTypeDouble(nameBuilder, "AdministrativeDivision")); + jpaQueryResult, new EdmEntityTypeDouble(nameBuilder, "AdministrativeDivision"), etagHelper); } @Test @@ -94,6 +105,27 @@ void checkConvertsOneResultMultiElement() throws ODataApplicationException, Seri assertEquals("0", act.getEntities().get(0).getProperty("Population").getValue().toString()); } + @Test + void testEtagAdded() throws ODataJPAModelException, SerializerException, ODataApplicationException, + URISyntaxException { + + final var personEntityType = helper.getJPAEntityType(Person.class); + final var personEdmType = mock(EdmEntityType.class); + when(personEdmType.getNamespace()).thenReturn(personEntityType.getExternalFQN().getNamespace()); + when(personEdmType.getName()).thenReturn(personEntityType.getExternalFQN().getName()); + final var result = new Person(); + result.setID("123"); + result.setETag(12); + final List results = Arrays.asList(result); + when(etagHelper.asEtag(any(), eq(12L))).thenReturn("\"12\""); + + cut = new JPAEntityResultConverter(OData.newInstance().createUriHelper(), helper.sd, results, personEdmType, + etagHelper); + final var act = cut.getResult(); + assertEquals(1, act.getEntities().size()); + assertEquals("\"12\"", act.getEntities().get(0).getETag()); + } + AdministrativeDivision firstResult() { final AdministrativeDivision division = new AdministrativeDivision(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunctionTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunctionTest.java new file mode 100644 index 000000000..9038d1be1 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseTableFunctionTest.java @@ -0,0 +1,40 @@ +package com.sap.olingo.jpa.processor.core.database; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.apache.olingo.server.api.ODataApplicationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class JPAODataDatabaseTableFunctionTest { + private JPAODataDatabaseTableFunction cut; + + @BeforeEach + void setup() { + cut = new testClass(); + } + + @SuppressWarnings("removal") + @Test + void testExecuteFunctionQueryOldReturnsEmptyList() throws ODataApplicationException { + final var act = cut.executeFunctionQuery(null, null, null); + assertNotNull(act); + assertTrue(act.isEmpty()); + } + + @SuppressWarnings("rawtypes") + @Test + void testExecuteFunctionQueryNewReturnsEmptyList() throws ODataApplicationException { + final var act = cut.executeFunctionQuery(null, null, null, null, null); + assertNotNull(act); + assertTrue(act instanceof List); + assertTrue(((List) act).isEmpty()); + } + + private static class testClass implements JPAODataDatabaseTableFunction { + + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPA_XXX_DatabaseProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPA_XXX_DatabaseProcessorTest.java index fdf048584..3f0fafc0e 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPA_XXX_DatabaseProcessorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPA_XXX_DatabaseProcessorTest.java @@ -37,6 +37,8 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +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.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOperationResultParameter; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAParameter; @@ -65,6 +67,8 @@ public abstract class JPA_XXX_DatabaseProcessorTest { protected String oneParameterResult; protected String twoParameterResult; protected String countResult; + protected JPAHttpHeaderMap headers; + protected JPARequestParameterMap parameters; void initEach() { em = mock(EntityManager.class); @@ -78,6 +82,8 @@ void initEach() { uriResourceParts.add(uriFunction); uriParameters = new ArrayList<>(); firstUriParameter = mock(UriParameter.class); + headers = mock(JPAHttpHeaderMap.class); + parameters = mock(JPARequestParameterMap.class); jpaFunction = mock(JPADataBaseFunction.class); returnParameter = mock(JPAOperationResultParameter.class); @@ -106,7 +112,7 @@ void testAbortsOnNotImplementedChaining() throws ODataJPAModelException { when(uriResourceCount.getKind()).thenReturn(UriResourceKind.value); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(act.getStatusCode(), HttpStatusCode.NOT_IMPLEMENTED.getStatusCode()); } @@ -118,10 +124,11 @@ void testBoundConvertsExceptionOnParameterProblem() throws ODataJPAModelExceptio when(jpaFunction.getParameter()).thenThrow(ODataJPAModelException.class); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), act.getStatusCode()); } + @SuppressWarnings("unchecked") @Test void testBoundFunctionWithOneParameterCount() throws ODataApplicationException, ODataJPAModelException { @@ -132,7 +139,8 @@ void testBoundFunctionWithOneParameterCount() throws ODataApplicationException, when(uriResourceCount.getKind()).thenReturn(UriResourceKind.count); when(functionQuery.getSingleResult()).thenReturn(5L); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, + parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(countResult))); verify(functionQuery, times(1)).setParameter(1, "5"); @@ -143,12 +151,14 @@ void testBoundFunctionWithOneParameterCount() throws ODataApplicationException, assertEquals(5L, act.get(0)); } + @SuppressWarnings("unchecked") @Test - void testBoundFunctionWithOneParameterReturnsBuPas() throws ODataApplicationException, + void testBoundFunctionWithOneParameterReturnsBusinessPartners() throws ODataApplicationException, ODataJPAModelException { createBoundFunctionWithOneParameter(); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, + em, headers, parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(oneParameterResult)), eq( BusinessPartner.class)); @@ -157,13 +167,15 @@ void testBoundFunctionWithOneParameterReturnsBuPas() throws ODataApplicationExce assertEquals(2, act.size()); } + @SuppressWarnings("unchecked") @Test - void testBoundFunctionWithTwoParameterReturnsBuPas() throws ODataApplicationException, + void testBoundFunctionWithTwoParameterReturnsBusinessPartners() throws ODataApplicationException, ODataJPAModelException { createBoundFunctionWithOneParameter(); addSecondBoundParameter(); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, + em, headers, parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(twoParameterResult)), eq( BusinessPartner.class)); verify(functionQuery, times(1)).setParameter(1, "5"); @@ -179,7 +191,7 @@ void testBoundRaisesExceptionOnMissingParameter() throws ODataJPAModelException when(uriEntitySet.getKeyPredicates()).thenReturn(new ArrayList<>()); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), act.getStatusCode()); } @@ -195,7 +207,7 @@ void testCheckRaiseExceptionOnProblemValueToString() throws ODataJPAModelExcepti .thenThrow(EdmPrimitiveTypeException.class); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), act.getStatusCode()); @@ -208,7 +220,7 @@ void testCheckRaisesExceptionOnIsBound() throws ODataJPAModelException { when(jpaFunction.isBound()).thenThrow(ODataJPAModelException.class); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), act.getStatusCode()); } @@ -219,10 +231,11 @@ void testUnboundConvertsExceptionOnParameterProblem() throws ODataJPAModelExcept when(jpaFunction.getParameter()).thenThrow(ODataJPAModelException.class); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), act.getStatusCode()); } + @SuppressWarnings("unchecked") @Test void testUnboundFunctionWithOneParameterCount() throws ODataApplicationException, ODataJPAModelException { @@ -233,7 +246,8 @@ void testUnboundFunctionWithOneParameterCount() throws ODataApplicationException when(uriResourceCount.getKind()).thenReturn(UriResourceKind.count); when(functionQuery.getSingleResult()).thenReturn(5L); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, + parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(countResult))); verify(functionQuery, times(1)).setParameter(1, "5"); @@ -244,13 +258,15 @@ void testUnboundFunctionWithOneParameterCount() throws ODataApplicationException assertEquals(5L, act.get(0)); } + @SuppressWarnings("unchecked") @Test - void testUnboundFunctionWithOneParameterReturnsBuPas() throws ODataApplicationException, + void testUnboundFunctionWithOneParameterReturnsBusinessPartners() throws ODataApplicationException, ODataJPAModelException { createFunctionWithOneParameter(); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, + em, headers, parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(oneParameterResult)), eq( BusinessPartner.class)); verify(functionQuery, times(1)).setParameter(1, "5"); @@ -258,14 +274,16 @@ void testUnboundFunctionWithOneParameterReturnsBuPas() throws ODataApplicationEx assertEquals(2, act.size()); } + @SuppressWarnings("unchecked") @Test - void testUnboundFunctionWithTwoParameterReturnsBuPas() throws ODataApplicationException, + void testUnboundFunctionWithTwoParameterReturnsBusinessPartners() throws ODataApplicationException, ODataJPAModelException { createFunctionWithOneParameter(); addSecondParameter(); - final List act = cut.executeFunctionQuery(uriResourceParts, jpaFunction, em); + final List act = (List) cut.executeFunctionQuery(uriResourceParts, jpaFunction, + em, headers, parameters); verify(em, times(1)).createNativeQuery((String) argThat(new Equals(twoParameterResult)), eq( BusinessPartner.class)); verify(functionQuery, times(1)).setParameter(1, "5"); @@ -281,7 +299,7 @@ void testUnboundRaisesExceptionOnMissingParameter() throws ODataJPAModelExceptio when(uriFunction.getParameters()).thenReturn(new ArrayList<>()); final ODataApplicationException act = assertThrows(ODataApplicationException.class, - () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em)); + () -> cut.executeFunctionQuery(uriResourceParts, jpaFunction, em, headers, parameters)); assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), act.getStatusCode()); } 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 545fb182d..5974b07b8 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 @@ -6,6 +6,7 @@ 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.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.IOException; @@ -20,6 +21,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -33,77 +35,6 @@ class TestJPAQueryWhereClause extends TestBase { - @Test - void testFilterOneEquals() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$filter=ID eq '3'"); - helper.assertStatus(200); - - final ArrayNode organizations = helper.getValues(); - assertEquals(1, organizations.size()); - assertEquals("3", organizations.get(0).get("ID").asText()); - } - - @Test - void testFilterOneEqualsDateTime() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$filter=CreationDateTime eq 2016-01-20T09:21:23Z"); - helper.assertStatus(200); - // This test shall ensure that the Date Time value is mapped correct. - // Unfortunately the query returns an empty result locally, but 10 rows on Jenkins - final ArrayNode organizations = helper.getValues(); - assertNotNull(organizations); - } - - @Test - void testFilterOneDescriptionEquals() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$filter=LocationName eq 'Deutschland'"); - helper.assertStatus(200); - - final ArrayNode organizations = helper.getValues(); - assertEquals(1, organizations.size()); - assertEquals("10", organizations.get(0).get("ID").asText()); - } - - @Test - void testFilterOneDescriptionEqualsFieldNotSelected() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$filter=LocationName eq 'Deutschland'&$select=ID"); - helper.assertStatus(200); - - final ArrayNode organizations = helper.getValues(); - assertEquals(1, organizations.size()); - assertEquals("10", organizations.get(0).get("ID").asText()); - } - - @Test - void testFilterOneEnumEquals() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$filter=ABCClass eq com.sap.olingo.jpa.ABCClassification'A'"); - helper.assertStatus(200); - - final ArrayNode organizations = helper.getValues(); - assertEquals(1, organizations.size()); - assertEquals("1", organizations.get(0).get("ID").asText()); - } - - @Test - void testFilterOneEqualsInvert() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$filter='3' eq ID"); - helper.assertStatus(200); - - final ArrayNode organizations = helper.getValues(); - assertEquals(1, organizations.size()); - assertEquals("3", organizations.get(0).get("ID").asText()); - } - static Stream getFilterQuery() { return Stream.of( // Simple filter @@ -163,6 +94,15 @@ 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("NavigationPropertyToManyNested2Level", + "AdministrativeDivisions?$filter=Children/any(c:c/Children/any(cc:cc/ParentDivisionCode eq 'BE25'))", 1), + arguments("NavigationPropertyToManyNested3Level", + "AdministrativeDivisions?$filter=Children/any(c:c/Children/any(cc:cc/Children/any(ccc:ccc/ParentDivisionCode eq 'BE251')))", + 1), + arguments("NavigationPropertyToManyNestedWithJoinTable", + "Organizations?$select=ID&$filter=SupportEngineers/any(s:s/AdministrativeInformation/Created/User/Roles/any(a:a/RoleCategory eq 'Y'))", + 2), + arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectAll", "Organizations?$filter=Address/RegionName eq 'Kalifornien'", 3), arguments("NavigationPropertyDescriptionViaComplexTypeWOSubselectSelectId", @@ -171,9 +111,12 @@ static Stream getFilterQuery() { "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), + arguments("CollectionPropertyViaCast", + "Organizations?$select=ID&$filter=SupportEngineers/any(s:s/InhouseAddress/any(a:a/Building eq '2'))", 2), // 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 @@ -211,6 +154,117 @@ static Stream getFilterQuery() { "Organizations?$filter=AdministrativeInformation/Created/User/LastName eq 'Mustermann'", 8)); } + static Stream getEnumQuery() { + return Stream.of( + arguments("OneEnumNotEqual", "Persons?$filter=AccessRights ne com.sap.olingo.jpa.AccessRights'WRITE'", "97"), + arguments("OneEnumEqualMultipleValues", + "Persons?$filter=AccessRights eq com.sap.olingo.jpa.AccessRights'READ,DELETE'", "97") + // @Disabled("Clarify if GT, LE .. not supported by OData or \"only\" by Olingo") + // arguments("OneEnumGreaterThan", "Persons?$filter=AccessRights gt com.sap.olingo.jpa.AccessRights'Read'", "99") + ); + } + + static Stream getFilterCollectionQuery() { + return Stream.of( + arguments("SimpleCount", "Persons?$filter=InhouseAddress/$count eq 2", 1), + arguments("SimpleCountViaJoinTable", + "JoinSources?$filter=OneToMany/$count gt 1&$select=SourceID", 1), + arguments("DeepSimpleCount", + "CollectionDeeps?$filter=FirstLevel/SecondLevel/Comment/$count eq 2&$select=ID", 1), + arguments("DeepComplexCount", + "CollectionDeeps?$filter=FirstLevel/SecondLevel/Address/$count eq 2&$select=ID", 1), + arguments("DeepSimpleCountZero", + "CollectionDeeps?$filter=FirstLevel/SecondLevel/Comment/$count eq 0&$select=ID", 1), + arguments("Any", "Organizations?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", 1), + arguments("AsPartOfComplexAny", + "CollectionDeeps?$filter=FirstLevel/SecondLevel/Address/any(s:s/TaskID eq 'DEV')", 1)); + } + + static Stream getFilterNavigationPropertyRequiresGroupsQuery() { + return Stream.of( + arguments("NavigationRequiresGroupsProvided", + "BusinessPartnerWithGroupss?$select=ID&$filter=Roles/any(d:d/Details eq 'A')", OK), + arguments("CollectionRequiresGroupsReturnsForbidden", + "BusinessPartnerWithGroupss?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", FORBIDDEN), + arguments("CollectionRequiresGroupsProvided", + "BusinessPartnerWithGroupss?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", OK), + arguments("CollectionProtectedPropertyRequiresGroupsReturnsForbidden", + "BusinessPartnerWithGroupss('99')/InhouseAddress?$filter=RoomNumber eq 1", FORBIDDEN), + arguments("CollectionProtectedPropertyRequiresGroupsProvided", + "BusinessPartnerWithGroupss('99')/InhouseAddress?$filter=RoomNumber eq 1", OK)); + } + + @Test + void testFilterOneEquals() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$filter=ID eq '3'"); + helper.assertStatus(200); + + final ArrayNode organizations = helper.getValues(); + assertEquals(1, organizations.size()); + assertEquals("3", organizations.get(0).get("ID").asText()); + } + + @Test + void testFilterOneEqualsDateTime() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$filter=CreationDateTime eq 2016-01-20T09:21:23Z"); + helper.assertStatus(200); + // This test shall ensure that the Date Time value is mapped correct. + // Unfortunately the query returns an empty result locally, but 10 rows on Jenkins + final ArrayNode organizations = helper.getValues(); + assertNotNull(organizations); + } + + @Test + void testFilterOneDescriptionEquals() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$filter=LocationName eq 'Deutschland'"); + helper.assertStatus(200); + + final ArrayNode organizations = helper.getValues(); + assertEquals(1, organizations.size()); + assertEquals("10", organizations.get(0).get("ID").asText()); + } + + @Test + void testFilterOneDescriptionEqualsFieldNotSelected() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$filter=LocationName eq 'Deutschland'&$select=ID"); + helper.assertStatus(200); + + final ArrayNode organizations = helper.getValues(); + assertEquals(1, organizations.size()); + assertEquals("10", organizations.get(0).get("ID").asText()); + } + + @Test + void testFilterOneEnumEquals() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$filter=ABCClass eq com.sap.olingo.jpa.ABCClassification'A'"); + helper.assertStatus(200); + + final ArrayNode organizations = helper.getValues(); + assertEquals(1, organizations.size()); + assertEquals("1", organizations.get(0).get("ID").asText()); + } + + @Test + void testFilterOneEqualsInvert() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$filter='3' eq ID"); + helper.assertStatus(200); + + final ArrayNode organizations = helper.getValues(); + assertEquals(1, organizations.size()); + assertEquals("3", organizations.get(0).get("ID").asText()); + } + @ParameterizedTest @MethodSource("getFilterQuery") // @Tag(Assertions.CB_ONLY_TEST) @@ -223,16 +277,6 @@ void testFilterOne(final String text, final String queryString, final int number assertEquals(numberOfResults, organizations.size(), text); } - static Stream getEnumQuery() { - return Stream.of( - arguments("OneEnumNotEqual", "Persons?$filter=AccessRights ne com.sap.olingo.jpa.AccessRights'WRITE'", "97"), - arguments("OneEnumEqualMultipleValues", - "Persons?$filter=AccessRights eq com.sap.olingo.jpa.AccessRights'READ,DELETE'", "97") - // @Disabled("Clarify if GT, LE .. not supported by OData or \"only\" by Olingo") - // arguments("OneEnumGreaterThan", "Persons?$filter=AccessRights gt com.sap.olingo.jpa.AccessRights'Read'", "99") - ); - } - @ParameterizedTest @MethodSource("getEnumQuery") void testFilterEnum(final String text, final String queryString, final String result) @@ -466,7 +510,7 @@ void testFilterNavigationPropertyContainsProtectedDeep() throws IOException, ODa helper.assertStatus(200); final ArrayNode organizations = helper.getValues(); assertEquals(0, organizations.size()); - }; + } @Test void testFilterNavigationPropertyEqualsProtectedDeep() throws IOException, ODataException { @@ -481,7 +525,7 @@ void testFilterNavigationPropertyEqualsProtectedDeep() throws IOException, OData helper.assertStatus(200); final ArrayNode organizations = helper.getValues(); assertEquals(3, organizations.size()); - }; + } @Test void testFilterNavigationPropertyAndExpandThatNavigationProperty() throws IOException, @@ -496,7 +540,7 @@ void testFilterNavigationPropertyAndExpandThatNavigationProperty() throws IOExce assertNotNull(admin.get(3).findValue("Parent")); assertFalse(admin.get(3).findValue("Parent") instanceof NullNode); assertEquals("BE2", admin.get(3).findValue("Parent").get("DivisionCode").asText()); - }; + } @Test void testFilterNavigationPropertyViaJoinTableSubtype() throws IOException, @@ -510,7 +554,7 @@ void testFilterNavigationPropertyViaJoinTableSubtype() throws IOException, assertEquals(2, admin.size()); assertEquals("98", admin.get(0).findValue("ID").asText()); - }; + } @Disabled // EclipseLinkProblem see https://bugs.eclipse.org/bugs/show_bug.cgi?id=529565 @Test @@ -525,7 +569,7 @@ void testFilterNavigationPropertyViaJoinTableCountSubType() throws IOException, assertEquals(2, admin.size()); assertEquals("98", admin.get(0).findValue("ID").asText()); - }; + } @Test void testFilterMappedNavigationPropertyViaJoinTableSubtype() throws IOException, @@ -539,7 +583,7 @@ void testFilterMappedNavigationPropertyViaJoinTableSubtype() throws IOException, assertEquals(1, admin.size()); assertEquals("First Org.", admin.get(0).findValue("Name1").asText()); - }; + } @Tag(Assertions.CB_ONLY_TEST) @Test @@ -554,7 +598,7 @@ void testFilterNavigationPropertyViaJoinTableCount() throws IOException, assertEquals(1, admin.size()); assertEquals("98", admin.get(0).findValue("ID").asText()); - }; + } @Test void testFilterMappedNavigationPropertyViaJoinTableFilter() throws IOException, @@ -566,7 +610,7 @@ void testFilterMappedNavigationPropertyViaJoinTableFilter() throws IOException, helper.assertStatus(200); final ArrayNode admin = helper.getValues(); assertEquals(2, admin.size()); - }; + } @Test void testFilterWithAllExpand() throws ODataException, IOException { @@ -581,6 +625,58 @@ void testFilterWithAllExpand() throws ODataException, IOException { assertEquals(3, organization.get(0).get("Roles").size()); } + @Test + void testExpandWithFilterOnCollectionAttribute() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$expand=SupportEngineers($filter=InhouseAddress/any(p:p/Building eq '2'))"); + helper.assertStatus(200); + final ArrayNode organizations = helper.getValues(); + for (final JsonNode organization : organizations) { + if (organization.get("ID").asText().equals("1") || organization.get("ID").asText().equals("2")) { + final ArrayNode supportEngineers = (ArrayNode) organization.get("SupportEngineers"); + assertEquals(1, supportEngineers.size()); + final ArrayNode address = (ArrayNode) supportEngineers.get(0).get("InhouseAddress"); + assertEquals(1, address.size()); + assertEquals("2", address.get(0).get("Building").asText()); + } else { + final ArrayNode supportEngineers = (ArrayNode) organization.get("SupportEngineers"); + assertEquals(0, supportEngineers.size()); + } + } + } + + @Test + void testAnyFilterOnExpandWithMultipleHops() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$expand=AdministrativeInformation/Created/User($filter=Jobs/any(p:p/Id eq '98'))"); + helper.assertStatus(200); + final ArrayNode organizations = helper.getValues(); + for (final JsonNode organization : organizations) { + final JsonNode user = organization.get("AdministrativeInformation").get("Created").get("User"); + if (organization.get("ID").asText().equals("4")) { + assertNotNull(user); + assertEquals("98", user.get("ID").asText()); + } else { + assert (user instanceof NullNode); + } + } + } + + @Test + void testExpandWithFilterOnNavigation() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$filter=startswith(DivisionCode,'BE2')&$expand=Children($filter=Children/any(c:c/ParentDivisionCode eq 'BE25'))"); + helper.assertStatus(200); + final ArrayNode divisions = helper.getValues(); + for (final var division : divisions) { + final ArrayNode children = (ArrayNode) division.get("Children"); + if (division.get("DivisionCode").asText().equals("BE2")) + assertFalse(children.isEmpty()); + else + assertTrue(children.isEmpty()); + } + } + @Test void testFilterNavigationTarget() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, @@ -602,22 +698,6 @@ void testFilterCollectionSimplePropertyThrowsError() throws IOException, ODataEx helper.assertStatus(400); // Olingo rejects a bunch of functions. } - static Stream getFilterCollectionQuery() { - return Stream.of( - arguments("SimpleCount", "Persons?$filter=InhouseAddress/$count eq 2", 1), - arguments("SimpleCountViaJoinTable", - "JoinSources?$filter=OneToMany/$count gt 1&$select=SourceID", 1), - arguments("DeepSimpleCount", - "CollectionDeeps?$filter=FirstLevel/SecondLevel/Comment/$count eq 2&$select=ID", 1), - arguments("DeepComplexCount", - "CollectionDeeps?$filter=FirstLevel/SecondLevel/Address/$count eq 2&$select=ID", 1), - arguments("DeepSimpleCountZero", - "CollectionDeeps?$filter=FirstLevel/SecondLevel/Comment/$count eq 0&$select=ID", 1), - arguments("Any", "Organizations?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", 1), - arguments("AsPartOfComplexAny", - "CollectionDeeps?$filter=FirstLevel/SecondLevel/Address/any(s:s/TaskID eq 'DEV')", 1)); - } - @ParameterizedTest @MethodSource("getFilterCollectionQuery") void testFilterCollectionProperty(final String text, final String queryString, final int result) @@ -705,20 +785,6 @@ void testFilterNavigationPropertyRequiresGroupsReturnsForbidden() throws IOExcep helper.assertStatus(403); } - static Stream getFilterNavigationPropertyRequiresGroupsQuery() { - return Stream.of( - arguments("NavigationRequiresGroupsProvided", - "BusinessPartnerWithGroupss?$select=ID&$filter=Roles/any(d:d/Details eq 'A')", OK), - arguments("CollectionRequiresGroupsReturnsForbidden", - "BusinessPartnerWithGroupss?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", FORBIDDEN), - arguments("CollectionRequiresGroupsProvided", - "BusinessPartnerWithGroupss?$select=ID&$filter=Comment/any(s:contains(s, 'just'))", OK), - arguments("CollectionProtectedPropertyRequiresGroupsReturnsForbidden", - "BusinessPartnerWithGroupss('99')/InhouseAddress?$filter=RoomNumber eq 1", FORBIDDEN), - arguments("CollectionProtectedPropertyRequiresGroupsProvided", - "BusinessPartnerWithGroupss('99')/InhouseAddress?$filter=RoomNumber eq 1", OK)); - } - @ParameterizedTest @MethodSource("getFilterNavigationPropertyRequiresGroupsQuery") void testFilterNavigationPropertyRequiresGroups(final String text, final String queryString, diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJavaFunctions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJavaFunctions.java index 1ea687fe1..a6a62511d 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJavaFunctions.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJavaFunctions.java @@ -16,22 +16,19 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; -import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; -import com.sap.olingo.jpa.processor.core.util.TestHelper; -public class TestJavaFunctions { +class TestJavaFunctions { protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; protected static EntityManagerFactory emf; - protected TestHelper helper; protected Map> headers; protected static JPADefaultEdmNameBuilder nameBuilder; protected static DataSource ds; @BeforeAll - public static void setupClass() throws ODataJPAModelException { + public static void setupClass() { ds = DataSourceHelper.createDataSource(DataSourceHelper.DB_HSQLDB); emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, ds); nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java new file mode 100644 index 000000000..91f096705 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java @@ -0,0 +1,114 @@ +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.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import javax.sql.DataSource; + +import jakarta.persistence.EntityManagerFactory; + +import org.apache.olingo.commons.api.ex.ODataException; +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; +import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; +import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; + +class TestScalarDbFunctions { + + protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; + protected static EntityManagerFactory emf; + protected Map> headers; + protected static JPADefaultEdmNameBuilder nameBuilder; + protected static DataSource ds; + + @BeforeAll + public static void setupClass() { + ds = DataSourceHelper.createDataSource(DataSourceHelper.DB_HSQLDB); + emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, ds); + nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); + } + + @AfterAll + public static void teardownClass() throws SQLException { + emf.close(); + ds.getConnection().close(); + } + + @Test + void testFilterOnFunctionAndProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=$it/Area,Population=$it/Population) mul 1000000 gt 1000 and ParentDivisionCode eq 'BE255'&orderBy=DivisionCode)"); + helper.assertStatus(200); + + final ArrayNode orgs = helper.getValues(); + assertEquals(2, orgs.size()); + assertEquals("35002", orgs.get(0).get("DivisionCode").asText()); + } + + @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); + } + + 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), + arguments("FunctionMixParamOrder", + "AdministrativeDivisions?$filter=com.sap.olingo.jpa.ConvertToQkm(Area=$it/Area) gt 100", + 4)); + } + + @ParameterizedTest + @MethodSource("provideFunctionQueries") + void testFilterOnFunctionAndMultiply(final String text, final String queryString, final int numberOfResults) + throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, queryString); + helper.assertStatus(200); + + final ArrayNode divisions = helper.getValues(); + assertEquals(numberOfResults, divisions.size(), text); + } + + @Test + void testFilterOnFunctionNested() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + """ + AdministrativeDivisions?$filter=\ + com.sap.olingo.jpa.PopulationDensity(Area=\ + com.sap.olingo.jpa.ConvertToQkm(Area=$it/Area),Population=$it/Population) gt 1000"""); + helper.assertStatus(200); + final ArrayNode jobs = helper.getValues(); + assertEquals(7, jobs.size()); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/modify/TestJPACreateResult.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/modify/TestJPACreateResult.java index e678905c7..511d2d57a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/modify/TestJPACreateResult.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/modify/TestJPACreateResult.java @@ -41,12 +41,14 @@ public abstract class TestJPACreateResult extends TestBase { protected JPAODataRequestContextAccess requestContext; protected JPAODataRequestContext context; protected JPAODataSessionContextAccess sessionContext; + protected OData odata; public TestJPACreateResult() { super(); context = mock(JPAODataRequestContext.class); sessionContext = mock(JPAODataSessionContextAccess.class); - requestContext = new JPAODataInternalRequestContext(context, sessionContext); + odata = mock(OData.class); + requestContext = new JPAODataInternalRequestContext(context, sessionContext, odata); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessorTest.java index dfcba1542..90bc8992e 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAActionRequestProcessorTest.java @@ -5,15 +5,17 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,16 +29,19 @@ import org.apache.olingo.commons.api.data.ValueType; import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.EdmReturnType; import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.CsdlReturnType; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; @@ -45,6 +50,7 @@ import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriHelper; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; @@ -60,6 +66,7 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +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.JPAOperationResultParameter; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAParameter; @@ -67,14 +74,16 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; 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.processor.core.api.JPAODataEtagHelper; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessException; import com.sap.olingo.jpa.processor.core.serializer.JPAOperationSerializer; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartner; import com.sap.olingo.jpa.processor.core.testmodel.CommunicationData; import com.sap.olingo.jpa.processor.core.testmodel.Organization; +import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.testobjects.FileAccess; +import com.sap.olingo.jpa.processor.core.testobjects.TestFunctionActionConstructor; import com.sap.olingo.jpa.processor.core.testobjects.TestJavaActionNoParameter; import com.sap.olingo.jpa.processor.core.testobjects.TestJavaActions; @@ -110,6 +119,10 @@ class JPAActionRequestProcessorTest { private CsdlReturnType returnType; @Mock private UriResourceEntitySet bindingEntity; + @Mock + private UriHelper uriHelper; + @Mock + private JPAODataEtagHelper etagHelper; @BeforeEach void setup() throws ODataException { @@ -131,6 +144,9 @@ void setup() throws ODataException { when(edmProvider.getServiceDocument()).thenReturn(sd); when(requestContext.getUriInfo()).thenReturn(uriInfo); when(requestContext.getSerializer()).thenReturn(serializer); + when(requestContext.getRequestParameter()).thenReturn(new JPARequestParameterHashMap()); + when(requestContext.getHeader()).thenReturn(new JPAHttpHeaderHashMap()); + when(requestContext.getEtagHelper()).thenReturn(etagHelper); when(serializer.serialize(any(Annotatable.class), any(EdmType.class), any(ODataRequest.class))) .thenReturn(serializerResult); when(serializer.getContentType()).thenReturn(ContentType.APPLICATION_JSON); @@ -140,6 +156,7 @@ void setup() throws ODataException { when(edmAction.isBound()).thenReturn(Boolean.FALSE); when(sd.getAction(edmAction)).thenReturn(action); when(odata.createDeserializer((ContentType) any())).thenReturn(deserializer); + when(odata.createUriHelper()).thenReturn(uriHelper); when(deserializer.actionParameters(request.getBody(), resource.getAction())).thenReturn(dResult); when(dResult.getActionParameters()).thenReturn(actionParameter); @@ -150,9 +167,7 @@ void setup() throws ODataException { } @Test - void testCallsConstructorWithoutParameter() throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, - SecurityException, ODataApplicationException { + void testCallsConstructorWithoutParameter() throws NoSuchMethodException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); setConstructorAndMethod("unboundReturnPrimitiveNoParameter"); @@ -164,16 +179,14 @@ void testCallsConstructorWithoutParameter() throws InstantiationException, @SuppressWarnings("unchecked") @Test - void testCallsConstructorWithParameter() throws ODataJPAProcessException, InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, - SecurityException, ODataApplicationException { + void testCallsConstructorWithParameter() throws NoSuchMethodException, SecurityException, ODataApplicationException { TestJavaActions.constructorCalls = 0; @SuppressWarnings("rawtypes") - final Constructor c = TestJavaActions.class.getConstructors()[0]; - final Method m = TestJavaActions.class.getMethod("unboundWithOutParameter"); - when(action.getConstructor()).thenReturn(c); - when(action.getMethod()).thenReturn(m); + final Constructor constructor = TestJavaActions.class.getConstructors()[0]; + final Method method = TestJavaActions.class.getMethod("unboundWithOutParameter"); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); when(action.getReturnType()).thenReturn(null); cut.performAction(request, response, requestFormat); @@ -182,14 +195,14 @@ void testCallsConstructorWithParameter() throws ODataJPAProcessException, Instan @SuppressWarnings("unchecked") @Test - void testCallsActionVoidNoParameterReturnNoContent() throws ODataJPAProcessException, NoSuchMethodException, - SecurityException, ODataApplicationException { + void testCallsActionVoidNoParameterReturnNoContent() throws NoSuchMethodException, SecurityException, + ODataApplicationException { @SuppressWarnings("rawtypes") - final Constructor c = TestJavaActions.class.getConstructors()[0]; - final Method m = TestJavaActions.class.getMethod("unboundWithOutParameter"); - when(action.getConstructor()).thenReturn(c); - when(action.getMethod()).thenReturn(m); + final Constructor constructor = TestJavaActions.class.getConstructors()[0]; + final Method method = TestJavaActions.class.getMethod("unboundWithOutParameter"); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); when(action.getReturnType()).thenReturn(null); cut.performAction(request, response, requestFormat); @@ -198,14 +211,14 @@ void testCallsActionVoidNoParameterReturnNoContent() throws ODataJPAProcessExcep @SuppressWarnings("unchecked") @Test - void testCallsActionPrimitiveNoParameterReturnValue() throws ODataJPAProcessException, NoSuchMethodException, - SecurityException, SerializerException, ODataApplicationException { + void testCallsActionPrimitiveNoParameterReturnValue() throws NoSuchMethodException, SecurityException, + SerializerException, ODataApplicationException { @SuppressWarnings("rawtypes") - final Constructor c = TestJavaActions.class.getConstructors()[0]; - final Method m = TestJavaActions.class.getMethod("unboundReturnFacetNoParameter"); - when(action.getConstructor()).thenReturn(c); - when(action.getMethod()).thenReturn(m); + final Constructor constructor = TestJavaActions.class.getConstructors()[0]; + final Method method = TestJavaActions.class.getMethod("unboundReturnFacetNoParameter"); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); final JPAOperationResultParameter rParam = mock(JPAOperationResultParameter.class); when(action.getResultParameter()).thenReturn(rParam); @@ -226,10 +239,10 @@ void testCallsActionEntityNoParameterReturnValue() throws NoSuchMethodException, SecurityException, SerializerException, ODataApplicationException { @SuppressWarnings("rawtypes") - final Constructor c = TestJavaActions.class.getConstructors()[0]; - final Method m = TestJavaActions.class.getMethod("returnEmbeddable"); - when(action.getConstructor()).thenReturn(c); - when(action.getMethod()).thenReturn(m); + final Constructor constructor = TestJavaActions.class.getConstructors()[0]; + final Method method = TestJavaActions.class.getMethod("returnEmbeddable"); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); final JPAOperationResultParameter rParam = mock(JPAOperationResultParameter.class); when(action.getResultParameter()).thenReturn(rParam); @@ -241,12 +254,7 @@ void testCallsActionEntityNoParameterReturnValue() throws NoSuchMethodException, final JPAStructuredType st = mock(JPAStructuredType.class); when(sd.getComplexType((EdmComplexType) any())).thenReturn(st); - when(st.getTypeClass()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return CommunicationData.class; - } - }); + doReturn(CommunicationData.class).when(st).getTypeClass(); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(200); @@ -254,13 +262,13 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { } @Test - void testCallsActionVoidOneParameterReturnNoContent() throws ODataJPAProcessException, NoSuchMethodException, - SecurityException, ODataJPAModelException, NumberFormatException, ODataApplicationException { + void testCallsActionVoidOneParameterReturnNoContent() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("unboundVoidOneParameter", Short.class); + final Method method = setConstructorAndMethod("unboundVoidOneParameter", Short.class); - addParameter(m, Short.valueOf("10"), "A", 0); + addParameter(method, Short.valueOf("10"), "A", 0); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -268,15 +276,14 @@ void testCallsActionVoidOneParameterReturnNoContent() throws ODataJPAProcessExce } @Test - void testCallsActionVoidOneEnumerationParameterReturnNoContent() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - ODataApplicationException { + void testCallsActionVoidOneEnumerationParameterReturnNoContent() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("unboundVoidOneEnumerationParameter", FileAccess.class); + final Method method = setConstructorAndMethod("unboundVoidOneEnumerationParameter", FileAccess.class); - addParameter(m, FileAccess.Create, "AccessRights", 0); + addParameter(method, FileAccess.Create, "AccessRights", 0); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -284,14 +291,14 @@ void testCallsActionVoidOneEnumerationParameterReturnNoContent() throws ODataJPA } @Test - void testCallsActionVoidTwoParameterReturnNoContent() throws ODataJPAProcessException, NoSuchMethodException, - SecurityException, ODataJPAModelException, NumberFormatException, ODataApplicationException { + void testCallsActionVoidTwoParameterReturnNoContent() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("unboundVoidTwoParameter", Short.class, Integer.class); + final Method method = setConstructorAndMethod("unboundVoidTwoParameter", Short.class, Integer.class); - addParameter(m, Short.valueOf("10"), "A", 0); - addParameter(m, Integer.valueOf("200000"), "B", 1); + addParameter(method, Short.valueOf("10"), "A", 0); + addParameter(method, Integer.valueOf("200000"), "B", 1); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -299,14 +306,13 @@ void testCallsActionVoidTwoParameterReturnNoContent() throws ODataJPAProcessExce } @Test - void testCallsActionVoidOneParameterNullableGivenNullReturnNoContent() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - ODataApplicationException { + void testCallsActionVoidOneParameterNullableGivenNullReturnNoContent() throws NoSuchMethodException, + SecurityException, ODataJPAModelException, NumberFormatException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("unboundVoidOneParameter", Short.class); + final Method method = setConstructorAndMethod("unboundVoidOneParameter", Short.class); - addParameter(m, null, "A", 0); + addParameter(method, null, "A", 0); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -314,16 +320,15 @@ void testCallsActionVoidOneParameterNullableGivenNullReturnNoContent() throws OD } @Test - void testCallsActionVoidOnlyBindingParameter() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - EdmPrimitiveTypeException, ODataApplicationException { + void testCallsActionVoidOnlyBindingParameter() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, EdmPrimitiveTypeException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("boundOnlyBinding", AdministrativeDivision.class); + final Method method = setConstructorAndMethod("boundOnlyBinding", AdministrativeDivision.class); when(edmAction.isBound()).thenReturn(Boolean.TRUE); uriResources.add(0, bindingEntity); - setBindingParameter(m); + setBindingParameter(method); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -332,24 +337,23 @@ void testCallsActionVoidOnlyBindingParameter() throws ODataJPAProcessException, } @Test - void testCallsActionVoidBindingParameterPlusTwoBothNull() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - EdmPrimitiveTypeException, ODataApplicationException { + void testCallsActionVoidBindingParameterPlusTwoBothNull() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, EdmPrimitiveTypeException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("boundBindingPlus", AdministrativeDivision.class, Short.class, + final Method method = setConstructorAndMethod("boundBindingPlus", AdministrativeDivision.class, Short.class, Integer.class); when(edmAction.isBound()).thenReturn(Boolean.TRUE); uriResources.add(0, bindingEntity); - setBindingParameter(m); + setBindingParameter(method); JPAParameter jpaParam = mock(JPAParameter.class); - when(action.getParameter(m.getParameters()[1])).thenReturn(jpaParam); + when(action.getParameter(method.getParameters()[1])).thenReturn(jpaParam); when(jpaParam.getName()).thenReturn("A"); jpaParam = mock(JPAParameter.class); - when(action.getParameter(m.getParameters()[2])).thenReturn(jpaParam); + when(action.getParameter(method.getParameters()[2])).thenReturn(jpaParam); when(jpaParam.getName()).thenReturn("B"); cut.performAction(request, response, requestFormat); @@ -361,23 +365,22 @@ void testCallsActionVoidBindingParameterPlusTwoBothNull() throws ODataJPAProcess } @Test - void testCallsActionVoidBindingParameterPlusTwoFirstNull() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - EdmPrimitiveTypeException, ODataApplicationException { + void testCallsActionVoidBindingParameterPlusTwoFirstNull() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, EdmPrimitiveTypeException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("boundBindingPlus", AdministrativeDivision.class, Short.class, + final Method method = setConstructorAndMethod("boundBindingPlus", AdministrativeDivision.class, Short.class, Integer.class); when(edmAction.isBound()).thenReturn(Boolean.TRUE); uriResources.add(0, bindingEntity); - setBindingParameter(m); + setBindingParameter(method); final JPAParameter jpaParam = mock(JPAParameter.class); - when(action.getParameter(m.getParameters()[1])).thenReturn(jpaParam); + when(action.getParameter(method.getParameters()[1])).thenReturn(jpaParam); when(jpaParam.getName()).thenReturn("A"); - addParameter(m, 20, "B", 2); + addParameter(method, 20, "B", 2); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); @@ -388,43 +391,83 @@ void testCallsActionVoidBindingParameterPlusTwoFirstNull() throws ODataJPAProces } @Test - void testCallsActionVoidBindingParameterAbstractCallBySubtype() throws ODataJPAProcessException, - NoSuchMethodException, SecurityException, ODataJPAModelException, NumberFormatException, - EdmPrimitiveTypeException, ODataApplicationException { + void testCallsActionVoidBindingParameterAbstractCallBySubtype() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, NumberFormatException, ODataApplicationException { TestJavaActionNoParameter.resetCalls(); - final Method m = setConstructorAndMethod("boundBindingSuperType", BusinessPartner.class); + final Method method = setConstructorAndMethod("boundBindingSuperType", BusinessPartner.class); when(edmAction.isBound()).thenReturn(Boolean.TRUE); uriResources.add(0, bindingEntity); - final JPAParameter bindingParam = addParameter(m, null, "Root", 0); - when(bindingParam.getType()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return BusinessPartner.class; - } - }); + final JPAParameter bindingParam = addParameter(method, null, "Root", 0); + doReturn(BusinessPartner.class).when(bindingParam).getType(); final JPAEntityType et = mock(JPAEntityType.class); when(sd.getEntity((EdmType) any())).thenReturn(et); - when(et.getTypeClass()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return Organization.class; - } - }); + doReturn(Organization.class).when(et).getTypeClass(); + cut.performAction(request, response, requestFormat); + verify(response, times(1)).setStatusCode(204); + } + + @Test + void testCallsWithConstructorParameterValue() throws NoSuchMethodException, ODataJPAModelException, + ODataApplicationException { + final Method method = setConstructorAndMethod(TestFunctionActionConstructor.class, "action", LocalDate.class); + addParameter(method, LocalDate.now(), "date", 0); cut.performAction(request, response, requestFormat); verify(response, times(1)).setStatusCode(204); } - private void setBindingParameter(final Method m) throws ODataJPAModelException, EdmPrimitiveTypeException { - final JPAParameter bindingParam = addParameter(m, null, "Root", 0); + @Test + void testEtagHeaderFilledForEntity() throws NoSuchMethodException, ODataApplicationException, ODataJPAModelException, + SerializerException { + setConstructorAndMethod(TestJavaActions.class, "returnsEntityWithETag"); + + final EdmReturnType edmReturnType = mock(EdmReturnType.class); + final EdmEntityType type = mock(EdmEntityType.class); + when(edmAction.getReturnType()).thenReturn(edmReturnType); + when(edmReturnType.getType()).thenReturn(type); + when(type.getKind()).thenReturn(EdmTypeKind.ENTITY); + + final JPAOperationResultParameter resultParameter = mock(JPAOperationResultParameter.class); + when(action.getResultParameter()).thenReturn(resultParameter); + when(resultParameter.isCollection()).thenReturn(false); + + final JPAEntityType jpaType = mock(JPAEntityType.class); + final JPAAttribute idAttribute = createJPAAttribute("iD", "Test", "ID"); + final JPAAttribute etagAttribute = createJPAAttribute("eTag", "Test", "ETag"); + final List attributes = Arrays.asList(idAttribute, etagAttribute); + final JPAPath etagPath = mock(JPAPath.class); + final JPAElement pathPart = mock(JPAElement.class); + when(sd.getEntity(type)).thenReturn(jpaType); + when(jpaType.getExternalFQN()).thenReturn(new FullQualifiedName("Test", "Person")); + doReturn(Person.class).when(jpaType).getTypeClass(); + when(jpaType.getAttributes()).thenReturn(attributes); + when(jpaType.hasEtag()).thenReturn(true); + when(jpaType.getEtagPath()).thenReturn(etagPath); + when(etagPath.getPath()).thenReturn(Arrays.asList(pathPart)); + when(pathPart.getInternalName()).thenReturn("eTag"); + when(etagHelper.asEtag(any(), any())).thenReturn("\"7\""); + + when(uriHelper.buildKeyPredicate(any(), any())).thenReturn("example.org"); + + cut.performAction(request, response, requestFormat); + verify(response, times(1)).setStatusCode(200); + verify(response, times(1)).setHeader(HttpHeader.ETAG, "\"7\""); + } + + private JPAAttribute createJPAAttribute(final String internalName, final String namespace, + final String externalName) { + final JPAAttribute attribute = mock(JPAAttribute.class); + when(attribute.getInternalName()).thenReturn(internalName); + when(attribute.getExternalName()).thenReturn(externalName); + when(attribute.getExternalFQN()).thenReturn(new FullQualifiedName(namespace, externalName)); + return attribute; + } + + private void setBindingParameter(final Method method) throws ODataJPAModelException, EdmPrimitiveTypeException { + final JPAParameter bindingParam = addParameter(method, null, "Root", 0); - when(bindingParam.getType()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return AdministrativeDivision.class; - } - }); + doReturn(AdministrativeDivision.class).when(bindingParam).getType(); final List keys = new ArrayList<>(); final UriParameter key1 = mock(UriParameter.class); when(bindingEntity.getKeyPredicates()).thenReturn(keys); @@ -440,20 +483,11 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { when(sd.getEntity((EdmType) any())).thenReturn(et); when(et.getPath("CodeID")).thenReturn(codePath); when(et.getAttribute("codeID")).thenReturn(Optional.of(code)); - when(et.getTypeClass()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return AdministrativeDivision.class; - } - }); + doReturn(AdministrativeDivision.class).when(et).getTypeClass(); + when(codePath.getLeaf()).thenReturn(code); when(code.getInternalName()).thenReturn("codeID"); - when(code.getType()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return String.class; - } - }); + doReturn(String.class).when(code).getType(); when(code.getProperty()).thenReturn(edmProperty); when(code.getEdmType()).thenReturn(EdmPrimitiveTypeKind.String); @@ -468,7 +502,7 @@ public String answer(final InvocationOnMock invocation) throws Throwable { }); } - private JPAParameter addParameter(final Method m, final Object value, final String name, final int index) + private JPAParameter addParameter(final Method method, final Object value, final String name, final int index) throws ODataJPAModelException { final Parameter param = mock(Parameter.class); @@ -478,7 +512,7 @@ private JPAParameter addParameter(final Method m, final Object value, final Stri actionParameter.put(name, param); final JPAParameter jpaParam = mock(JPAParameter.class); - when(action.getParameter(m.getParameters()[index])).thenReturn(jpaParam); + when(action.getParameter(method.getParameters()[index])).thenReturn(jpaParam); when(jpaParam.getName()).thenReturn(name); return jpaParam; } @@ -487,11 +521,24 @@ private JPAParameter addParameter(final Method m, final Object value, final Stri private Method setConstructorAndMethod(final String methodName, final Class... parameterTypes) throws NoSuchMethodException { @SuppressWarnings("rawtypes") - final Constructor c = TestJavaActionNoParameter.class.getConstructors()[0]; - final Method m = TestJavaActionNoParameter.class.getMethod(methodName, parameterTypes); - when(action.getConstructor()).thenReturn(c); - when(action.getMethod()).thenReturn(m); + final Constructor constructor = TestJavaActionNoParameter.class.getConstructors()[0]; + final Method method = TestJavaActionNoParameter.class.getMethod(methodName, parameterTypes); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); when(action.getReturnType()).thenReturn(null); - return m; + return method; } + + @SuppressWarnings("unchecked") + private Method setConstructorAndMethod(final Class clazz, final String methodName, + final Class... parameterTypes) throws NoSuchMethodException { + @SuppressWarnings("rawtypes") + final Constructor constructor = clazz.getConstructors()[0]; + final Method method = clazz.getMethod(methodName, parameterTypes); + when(action.getConstructor()).thenReturn(constructor); + when(action.getMethod()).thenReturn(method); + when(action.getReturnType()).thenReturn(null); + return method; + } + } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessorTest.java index 93604ab8b..9d633df9a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAFunctionRequestProcessorTest.java @@ -5,13 +5,15 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import jakarta.persistence.EntityManager; @@ -20,13 +22,19 @@ import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.Triple; import org.apache.olingo.commons.api.data.Annotatable; +import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmFunction; import org.apache.olingo.commons.api.edm.EdmParameter; import org.apache.olingo.commons.api.edm.EdmReturnType; import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.commons.core.edm.primitivetype.EdmBoolean; +import org.apache.olingo.commons.core.edm.primitivetype.EdmDate; import org.apache.olingo.commons.core.edm.primitivetype.EdmInt16; import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32; import org.apache.olingo.server.api.OData; @@ -36,6 +44,7 @@ import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriHelper; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; @@ -49,13 +58,20 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmFunctionType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +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.JPAJavaFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOperationResultParameter; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAParameter; +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.JPAODataEtagHelper; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.serializer.JPAOperationSerializer; +import com.sap.olingo.jpa.processor.core.testmodel.Person; +import com.sap.olingo.jpa.processor.core.testobjects.TestFunctionActionConstructor; import com.sap.olingo.jpa.processor.core.testobjects.TestFunctionReturnType; class JPAFunctionRequestProcessorTest { @@ -88,6 +104,10 @@ class JPAFunctionRequestProcessorTest { private EdmFunction edmFunction; @Captor ArgumentCaptor annotatableCaptor; + @Mock + private UriHelper uriHelper; + @Mock + private JPAODataEtagHelper etagHelper; @BeforeEach void setup() throws ODataException { @@ -106,8 +126,11 @@ void setup() throws ODataException { when(em.getCriteriaBuilder()).thenReturn(cb); when(requestContext.getEdmProvider()).thenReturn(edmProvider); when(edmProvider.getServiceDocument()).thenReturn(sd); + when(requestContext.getRequestParameter()).thenReturn(new JPARequestParameterHashMap()); + when(requestContext.getHeader()).thenReturn(new JPAHttpHeaderHashMap()); when(requestContext.getUriInfo()).thenReturn(uriInfo); when(requestContext.getSerializer()).thenReturn(serializer); + when(requestContext.getEtagHelper()).thenReturn(etagHelper); when(serializer.serialize(any(Annotatable.class), any(EdmType.class), any(ODataRequest.class))) .thenReturn(serializerResult); when(serializer.getContentType()).thenReturn(ContentType.APPLICATION_JSON); @@ -119,6 +142,7 @@ void setup() throws ODataException { when(function.getFunctionType()).thenReturn(EdmFunctionType.JavaClass); when(sd.getFunction(edmFunction)).thenReturn(function); when(odata.createDeserializer((ContentType) any())).thenReturn(deserializer); + when(odata.createUriHelper()).thenReturn(uriHelper); requestFormat = ContentType.APPLICATION_JSON; @@ -126,14 +150,13 @@ void setup() throws ODataException { } @Test - void testCallsWithParameterValue() throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, + void testCallsWithParameterValue() throws IllegalArgumentException, NoSuchMethodException, SecurityException, ODataApplicationException, ODataLibraryException, ODataJPAModelException { - final Method m = setConstructorAndMethod("primitiveValue", short.class); + final Method method = setConstructorAndMethod("primitiveValue", short.class); final Triple parameter = - createParameter("A", "5", m); + createParameter("A", "5", Short.class, method); final EdmReturnType returnType = mock(EdmReturnType.class); when(returnType.getType()).thenReturn(EdmInt32.getInstance()); @@ -151,14 +174,13 @@ void testCallsWithParameterValue() throws InstantiationException, } @Test - void testCallsWithParameterNull() throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, - SecurityException, ODataApplicationException, ODataLibraryException, ODataJPAModelException { + void testCallsWithParameterNull() throws NoSuchMethodException, ODataJPAModelException, ODataApplicationException, + ODataLibraryException { - final Method m = setConstructorAndMethod("primitiveValueNullable", Short.class); + final Method method = setConstructorAndMethod("primitiveValueNullable", Short.class); final Triple parameter = - createParameter("A", null, m); + createParameter("A", null, Short.class, method); final EdmReturnType returnType = mock(EdmReturnType.class); when(returnType.getType()).thenReturn(EdmInt32.getInstance()); @@ -175,20 +197,97 @@ void testCallsWithParameterNull() throws InstantiationException, assertTrue(annotatableCaptor.getValue().toString().contains("0")); } - @SuppressWarnings("unchecked") + @Test + void testCallsWithConstructorParameterValue() throws NoSuchMethodException, ODataJPAModelException, + ODataApplicationException, ODataLibraryException { + + final Method method = setConstructorAndMethod(TestFunctionActionConstructor.class, "func", LocalDate.class); + + final Triple parameter = + createParameter("date", "2024-10-03", LocalDate.class, method); + + final EdmReturnType returnType = mock(EdmReturnType.class); + when(returnType.getType()).thenReturn(EdmBoolean.getInstance()); + when(edmFunction.getReturnType()).thenReturn(returnType); + when(edmFunction.getParameter("date")).thenReturn(parameter.getMiddle()); + + final JPAOperationResultParameter resultParameter = mock(JPAOperationResultParameter.class); + when(function.getResultParameter()).thenReturn(resultParameter); + when(resultParameter.isCollection()).thenReturn(Boolean.FALSE); + + cut.retrieveData(request, response, requestFormat); + verify(response).setStatusCode(HttpStatusCode.OK.getStatusCode()); + verify(serializer).serialize(annotatableCaptor.capture(), eq(EdmBoolean.getInstance()), eq(request)); + assertTrue(annotatableCaptor.getValue().toString().contains("true")); + } + + @Test + void testEtagHeaderFilledForEntity() throws NoSuchMethodException, ODataApplicationException, ODataLibraryException, + ODataJPAModelException { + setConstructorAndMethod(TestFunctionReturnType.class, "convertBirthday"); + + final EdmReturnType returnType = mock(EdmReturnType.class); + final EdmEntityType type = mock(EdmEntityType.class); + when(returnType.getType()).thenReturn(type); + when(edmFunction.getReturnType()).thenReturn(returnType); + when(type.getKind()).thenReturn(EdmTypeKind.ENTITY); + + final JPAOperationResultParameter resultParameter = mock(JPAOperationResultParameter.class); + when(function.getResultParameter()).thenReturn(resultParameter); + when(resultParameter.isCollection()).thenReturn(Boolean.FALSE); + + final JPAEntityType jpaType = mock(JPAEntityType.class); + final JPAAttribute idAttribute = createJPAAttribute("iD", "Test", "ID"); + final JPAAttribute etagAttribute = createJPAAttribute("eTag", "Test", "ETag"); + final List attributes = Arrays.asList(idAttribute, etagAttribute); + final JPAPath etagPath = mock(JPAPath.class); + final JPAElement pathPart = mock(JPAElement.class); + when(sd.getEntity(type)).thenReturn(jpaType); + when(jpaType.getExternalFQN()).thenReturn(new FullQualifiedName("Test", "Person")); + doReturn(Person.class).when(jpaType).getTypeClass(); + when(jpaType.getAttributes()).thenReturn(attributes); + when(jpaType.hasEtag()).thenReturn(true); + when(jpaType.getEtagPath()).thenReturn(etagPath); + when(etagPath.getPath()).thenReturn(Arrays.asList(pathPart)); + when(pathPart.getInternalName()).thenReturn("eTag"); + when(etagHelper.asEtag(any(), any())).thenReturn("\"3\""); + + when(uriHelper.buildKeyPredicate(any(), any())).thenReturn("example.org"); + + cut.retrieveData(request, response, requestFormat); + verify(response, times(1)).setStatusCode(200); + verify(response, times(1)).setHeader(HttpHeader.ETAG, "\"3\""); + + } + private Method setConstructorAndMethod(final String methodName, final Class... parameterTypes) throws NoSuchMethodException { + return setConstructorAndMethod(TestFunctionReturnType.class, methodName, parameterTypes); + } + + private JPAAttribute createJPAAttribute(final String internalName, final String namespace, + final String externalName) { + final JPAAttribute attribute = mock(JPAAttribute.class); + when(attribute.getInternalName()).thenReturn(internalName); + when(attribute.getExternalName()).thenReturn(externalName); + when(attribute.getExternalFQN()).thenReturn(new FullQualifiedName(namespace, externalName)); + return attribute; + } + + @SuppressWarnings("unchecked") + private Method setConstructorAndMethod(final Class clazz, final String methodName, + final Class... parameterTypes) throws NoSuchMethodException { @SuppressWarnings("rawtypes") - final Constructor c = TestFunctionReturnType.class.getConstructors()[0]; - final Method m = TestFunctionReturnType.class.getMethod(methodName, parameterTypes); - when(function.getConstructor()).thenReturn(c); - when(function.getMethod()).thenReturn(m); + final Constructor constructor = clazz.getConstructors()[0]; + final Method method = clazz.getMethod(methodName, parameterTypes); + when(function.getConstructor()).thenReturn(constructor); + when(function.getMethod()).thenReturn(method); when(function.getReturnType()).thenReturn(null); - return m; + return method; } private Triple createParameter(final String name, final String value, - final Method m) throws ODataJPAModelException { + final Class parameterType, final Method method) throws ODataJPAModelException { final UriParameter uriParameter = mock(UriParameter.class); when(uriParameter.getName()).thenReturn(name); when(uriParameter.getText()).thenReturn(value); @@ -196,11 +295,13 @@ private Triple createParameter(final S final JPAParameter parameter = mock(JPAParameter.class); when(parameter.getName()).thenReturn(name); - doReturn(Short.class).when(parameter).getType(); - when(function.getParameter(m.getParameters()[0])).thenReturn(parameter); + doReturn(parameterType).when(parameter).getType(); + when(function.getParameter(method.getParameters()[0])).thenReturn(parameter); final EdmParameter edmParameter = mock(EdmParameter.class); - when(edmParameter.getType()).thenReturn(EdmInt16.getInstance()); + when(edmParameter.getType()).thenReturn(parameterType == LocalDate.class + ? EdmDate.getInstance() + : EdmInt16.getInstance()); return new ImmutableTriple<>(uriParameter, edmParameter, parameter); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAJavaFunctionProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAJavaFunctionProcessorTest.java index c2bbf2b3c..dad994de0 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAJavaFunctionProcessorTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAJavaFunctionProcessorTest.java @@ -62,7 +62,7 @@ class JPAJavaFunctionProcessorTest { private JPARequestParameterMap requestParameter; @BeforeEach - void setup() throws ODataJPAModelException { + void setup() { uriParameters = new ArrayList<>(); sd = mock(JPAServiceDocument.class); jpaFunction = mock(JPAJavaFunction.class); @@ -76,8 +76,8 @@ void setup() throws ODataJPAModelException { } @Test - void testConstructorQueryContextParameter() throws ODataApplicationException, NoSuchMethodException, - SecurityException, ODataJPAModelException, EdmPrimitiveTypeException { + void testConstructorQueryContextParameter() throws NoSuchMethodException, SecurityException, ODataJPAModelException, + EdmPrimitiveTypeException { final Constructor c = TestFunctionForFilter.class.getConstructor(JPAODataQueryContext.class); final Method m = TestFunctionForFilter.class.getMethod("at2", LocalDate.class); @@ -97,14 +97,14 @@ void testConstructorQueryContextParameter() throws ODataApplicationException, No void testConstructorWithThreeParameter() throws ODataApplicationException, NoSuchMethodException, SecurityException, ODataJPAModelException, EdmPrimitiveTypeException { - final Constructor c = TestFunctionActionConstructor.class.getConstructor( + final Constructor constructor = TestFunctionActionConstructor.class.getConstructor( EntityManager.class, JPAHttpHeaderMap.class, JPARequestParameterMap.class); - final Method m = TestFunctionActionConstructor.class.getMethod("func", LocalDate.class); - final Triple parameter = createParameter("date", m); + final Method method = TestFunctionActionConstructor.class.getMethod("func", LocalDate.class); + final Triple parameter = createParameter("date", method); setPrimitiveValue(LocalDate.now(), parameter); - doReturn(c).when(jpaFunction).getConstructor(); - doReturn(m).when(jpaFunction).getMethod(); + doReturn(constructor).when(jpaFunction).getConstructor(); + doReturn(method).when(jpaFunction).getMethod(); when(uriResourceFunction.getParameters()).thenReturn(uriParameters); when(edmFunction.getParameter("date")).thenReturn(parameter.getMiddle()); @@ -137,7 +137,7 @@ void testParameterNull() throws ODataApplicationException, NoSuchMethodException @Test void testParameterEnum() throws ODataApplicationException, NoSuchMethodException, SecurityException, - ODataJPAModelException, EdmPrimitiveTypeException { + ODataJPAModelException { final Constructor c = TestFunctionActionConstructor.class.getConstructor( EntityManager.class, JPAHttpHeaderMap.class, JPARequestParameterMap.class); @@ -157,8 +157,7 @@ void testParameterEnum() throws ODataApplicationException, NoSuchMethodException } @Test - void testThrowsExceptionEnumNotFound() throws ODataApplicationException, NoSuchMethodException, SecurityException, - ODataJPAModelException, EdmPrimitiveTypeException { + void testThrowsExceptionEnumNotFound() throws NoSuchMethodException, SecurityException, ODataJPAModelException { final Constructor c = TestFunctionActionConstructor.class.getConstructor( EntityManager.class, JPAHttpHeaderMap.class, JPARequestParameterMap.class); @@ -178,9 +177,7 @@ void testThrowsExceptionEnumNotFound() throws ODataApplicationException, NoSuchM } @Test - void testThrowsExceptionOnUnsupportedType() throws ODataApplicationException, NoSuchMethodException, - SecurityException, - ODataJPAModelException, EdmPrimitiveTypeException { + void testThrowsExceptionOnUnsupportedType() throws NoSuchMethodException, SecurityException, ODataJPAModelException { final Constructor c = TestFunctionActionConstructor.class.getConstructor( EntityManager.class, JPAHttpHeaderMap.class, JPARequestParameterMap.class); @@ -200,8 +197,8 @@ void testThrowsExceptionOnUnsupportedType() throws ODataApplicationException, No } @Test - void testRethrowsExceptionOnInvocationError() throws ODataApplicationException, NoSuchMethodException, - SecurityException, ODataJPAModelException, EdmPrimitiveTypeException { + void testRethrowsExceptionOnInvocationError() throws NoSuchMethodException, SecurityException, ODataJPAModelException, + EdmPrimitiveTypeException { final Constructor c = TestFunctionForFilter.class.getConstructor(JPAODataQueryContext.class); final Method m = TestFunctionForFilter.class.getMethod("at2", LocalDate.class); @@ -219,8 +216,8 @@ void testRethrowsExceptionOnInvocationError() throws ODataApplicationException, } @Test - void testRethrowsExceptionOnInvocationTargetError() throws ODataApplicationException, NoSuchMethodException, - SecurityException, ODataJPAModelException, EdmPrimitiveTypeException { + void testRethrowsExceptionOnInvocationTargetError() throws NoSuchMethodException, SecurityException, + ODataJPAModelException, EdmPrimitiveTypeException { final Constructor c = TestFunctionParameter.class.getConstructor(EntityManager.class); final Method m = TestFunctionParameter.class.getMethod("sumThrowsException", Integer.class); @@ -237,8 +234,8 @@ void testRethrowsExceptionOnInvocationTargetError() throws ODataApplicationExcep } @Test - void testRethrowsExceptionOnParameterError() throws ODataApplicationException, NoSuchMethodException, - SecurityException, ODataJPAModelException, EdmPrimitiveTypeException { + void testRethrowsExceptionOnParameterError() throws NoSuchMethodException, SecurityException, ODataJPAModelException, + EdmPrimitiveTypeException { final Constructor c = TestFunctionForFilter.class.getConstructor(JPAODataQueryContext.class); final Method m = TestFunctionForFilter.class.getMethod("at2", LocalDate.class); @@ -259,7 +256,7 @@ void testRethrowsExceptionOnParameterError() throws ODataApplicationException, N } private Triple createParameter(final String name, final Method m) - throws ODataJPAModelException, EdmPrimitiveTypeException { + throws ODataJPAModelException { final UriParameter uriParameter = mock(UriParameter.class); when(uriParameter.getName()).thenReturn(name); uriParameters.add(uriParameter); @@ -300,8 +297,7 @@ private void setEnumValue(final Object value, final Triple parameter) - throws EdmPrimitiveTypeException { + private void setComplexValue(final Object value, final Triple parameter) { when(parameter.getLeft().getText()).thenReturn(value == null ? null : value.toString()); final EdmComplexType edmType = mock(EdmComplexType.class); when(parameter.getMiddle().getType()).thenReturn(edmType); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessorTest.java new file mode 100644 index 000000000..e2628eb1b --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPANavigationRequestProcessorTest.java @@ -0,0 +1,180 @@ +package com.sap.olingo.jpa.processor.core.processor; + +import static com.sap.olingo.jpa.processor.core.converter.JPAExpandResult.ROOT_RESULT_KEY; +import static com.sap.olingo.jpa.processor.core.processor.JPAETagValidationResult.NOT_MODIFIED; +import static com.sap.olingo.jpa.processor.core.processor.JPAETagValidationResult.PRECONDITION_FAILED; +import static com.sap.olingo.jpa.processor.core.processor.JPAETagValidationResult.SUCCESS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +import jakarta.persistence.Tuple; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriResource; +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 com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; +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.converter.JPAExpandResult; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; +import com.sap.olingo.jpa.processor.core.query.JPAConvertibleResult; +import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; +import com.sap.olingo.jpa.processor.core.testmodel.Organization; +import com.sap.olingo.jpa.processor.core.util.TestBase; + +class JPANavigationRequestProcessorTest extends TestBase { + + private JPANavigationRequestProcessor cut; + private JPAODataRequestContextAccess requestContext; + private ServiceMetadata metadata; + private JPAExpandResult result; + private JPAHttpHeaderMap headers; + private UriInfoResource uriInfo; + private UriResource uriResource; + private OData odata; + + @BeforeEach + void setup() throws ODataException { + getHelper(); + requestContext = mock(JPAODataRequestContextAccess.class); + metadata = mock(ServiceMetadata.class); + uriInfo = mock(UriInfoResource.class); + uriResource = mock(UriResource.class); + result = mock(JPAExpandResult.class, withSettings().extraInterfaces(JPAConvertibleResult.class)); + headers = mock(JPAHttpHeaderMap.class); + odata = OData.newInstance(); + + when(requestContext.getEdmProvider()).thenReturn(helper.edmProvider); + when(requestContext.getEntityManager()).thenReturn(emf.createEntityManager()); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + when(requestContext.getEtagHelper()).thenReturn(new JPAODataEtagHelperImpl(odata)); + when(uriInfo.getUriResourceParts()).thenReturn(Collections.singletonList(uriResource)); + + cut = new JPANavigationRequestProcessor(OData.newInstance(), metadata, requestContext); + + doReturn(helper.getJPAEntityType(Organization.class)).when(result).getEntityType(); + } + + @Test + void testNoEtagPropertySuccess() throws ODataJPAProcessorException, ODataJPAModelException { + final Tuple tuple = mock(Tuple.class); + final List rootResult = Collections.singletonList(tuple); + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(rootResult); + when(headers.get(HttpHeader.IF_MATCH)).thenReturn(Arrays.asList("\"1\"")); + + doReturn(helper.getJPAEntityType(AdministrativeDivision.class)).when(result).getEntityType(); + assertEquals(JPAETagValidationResult.SUCCESS, cut.validateEntityTag((JPAConvertibleResult) result, headers)); + } + + @Test + void testValidWithoutHeader() throws ODataJPAProcessorException { + + assertEquals(JPAETagValidationResult.SUCCESS, cut.validateEntityTag((JPAConvertibleResult) result, headers)); + } + + @Test + void testValidIfMatchWithoutResult() throws ODataJPAProcessorException { + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(Collections.emptyList()); + when(headers.get(HttpHeader.IF_MATCH.toLowerCase(Locale.ENGLISH))).thenReturn(Arrays.asList("\"1\"")); + + assertEquals(JPAETagValidationResult.SUCCESS, cut.validateEntityTag((JPAConvertibleResult) result, headers)); + } + + @Test + void testValidIfNoneMatchWithoutResult() throws ODataJPAProcessorException { + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(Collections.emptyList()); + when(headers.get(HttpHeader.IF_NONE_MATCH.toLowerCase(Locale.ENGLISH))).thenReturn(Arrays.asList("\"0\"")); + + assertEquals(JPAETagValidationResult.SUCCESS, cut.validateEntityTag((JPAConvertibleResult) result, headers)); + } + + private static Stream provideIfMatchHeader() { + return Stream.of( + Arguments.of(Arrays.asList("\"0\""), PRECONDITION_FAILED, "Not matching eTag: 412 ecxpected"), + Arguments.of(Arrays.asList("\"0\"", "\"3\""), PRECONDITION_FAILED, "None matching eTag: 412 ecxpected"), + Arguments.of(Arrays.asList("\"1\""), SUCCESS, "Matching eTag: 200 ecxpected"), + Arguments.of(Arrays.asList("\"2\"", "\"1\""), SUCCESS, "One Matching eTag: 200 ecxpected"), + Arguments.of(Arrays.asList("*"), SUCCESS, "* eTag: 200 ecxpected")); + } + + @ParameterizedTest + @MethodSource("provideIfMatchHeader") + void testIfMatchHeader(final List etag, final JPAETagValidationResult exp, final String message) + throws ODataException { + + final Tuple tuple = mock(Tuple.class); + final List rootResult = Collections.singletonList(tuple); + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(rootResult); + when(headers.get(HttpHeader.IF_MATCH)).thenReturn(etag); + + when(tuple.get("ETag")).thenReturn(Integer.valueOf(1)); + + assertEquals(exp, cut.validateEntityTag((JPAConvertibleResult) result, headers), message); + + } + + private static Stream provideIfNoneMatchHeader() { + return Stream.of( + Arguments.of(Arrays.asList("\"1\""), NOT_MODIFIED, "Matching eTag: 304 ecxpected"), + Arguments.of(Arrays.asList("\"2\"", "\"1\""), NOT_MODIFIED, "One Matching eTag: 304 ecxpected"), + Arguments.of(Arrays.asList("\"0\""), SUCCESS, "Not matching eTag: 200 ecxpected"), + Arguments.of(Arrays.asList("\"0\"", "\"3\""), SUCCESS, "None matching eTag: 200 ecxpected"), + Arguments.of(Arrays.asList("*"), NOT_MODIFIED, "* matches any eTag: 304 ecxpected")); + } + + @ParameterizedTest + @MethodSource("provideIfNoneMatchHeader") + void testIfNoneMatchHeader(final List etag, final JPAETagValidationResult exp, final String message) + throws ODataException { + + final Tuple tuple = mock(Tuple.class); + final List rootResult = Collections.singletonList(tuple); + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(rootResult); + when(headers.get(HttpHeader.IF_NONE_MATCH)).thenReturn(etag); + + when(tuple.get("ETag")).thenReturn(Integer.valueOf(1)); + + assertEquals(exp, cut.validateEntityTag((JPAConvertibleResult) result, headers), message); + + } + + private static Stream provideHeaderException() { + return Stream.of( + Arguments.of(Arrays.asList("\"1\""), HttpHeader.IF_MATCH), + Arguments.of(Arrays.asList("\"0\""), HttpHeader.IF_NONE_MATCH)); + } + + @ParameterizedTest + @MethodSource("provideHeaderException") + void testThrowExceptionMultipleResults(final List etag, final String header) { + + final Tuple tuple1 = mock(Tuple.class); + final Tuple tuple2 = mock(Tuple.class); + final List rootResult = Arrays.asList(tuple1, tuple2); + when(result.getResult(ROOT_RESULT_KEY)).thenReturn(rootResult); + when(headers.get(header)).thenReturn(etag); + + assertThrows(ODataJPAProcessorException.class, + () -> cut.validateEntityTag((JPAConvertibleResult) result, headers)); + + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImplTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImplTest.java new file mode 100644 index 000000000..a82e45d76 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/JPAODataEtagHelperImplTest.java @@ -0,0 +1,117 @@ +package com.sap.olingo.jpa.processor.core.processor; + +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.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.etag.ETagHelper; +import org.apache.olingo.server.api.etag.PreconditionException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEtagValidator; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +class JPAODataEtagHelperImplTest { + // See org.apache.olingo.server.core.etag.ETagParser + private static final Pattern ETAG = Pattern.compile("\\s*(,\\s*)+|((?:W/)?\"[!#-~\\x80-\\xFF]*\")"); + + private JPAODataEtagHelperImpl cut; + private OData odata; + private ETagHelper olingoHelper; + + @BeforeEach + void setup() { + odata = mock(OData.class); + olingoHelper = mock(ETagHelper.class); + when(odata.createETagHelper()).thenReturn(olingoHelper); + cut = new JPAODataEtagHelperImpl(odata); + } + + @Test + void testCheckReadPreconditionsCallsOlingo() throws PreconditionException { + final String etag = ""; + final Collection ifMatchHeaders = Collections.emptyList(); + final Collection ifNoneMatchHeaders = Collections.emptyList(); + cut.checkReadPreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + verify(olingoHelper, times(1)).checkReadPreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + } + + @Test + void testCheckChangePreconditionsCallsOlingo() throws PreconditionException { + final String etag = ""; + final Collection ifMatchHeaders = Collections.emptyList(); + final Collection ifNoneMatchHeaders = Collections.emptyList(); + cut.checkChangePreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + verify(olingoHelper, times(1)).checkChangePreconditions(etag, ifMatchHeaders, ifNoneMatchHeaders); + } + + @Test + void testAsEtagStringStrong() throws ODataJPAModelException, ODataJPAQueryException { + final JPAEntityType et = createEntityType("Person", true, JPAEtagValidator.STRONG); + final var act = cut.asEtag(et, 12L); + assertEquals("\"12\"", act); + assertEtagConsistent(act); + } + + @Test + void testAsEtagStringWeak() throws ODataJPAModelException, ODataJPAQueryException { + final JPAEntityType et = createEntityType("Person", true, JPAEtagValidator.WEAK); + final var act = cut.asEtag(et, 12L); + assertEquals("W/\"12\"", act); + assertEtagConsistent(act); + } + + @Test + void testAsEtagStringTimestamp() throws ODataJPAModelException, ODataJPAQueryException { + final JPAEntityType et = createEntityType("Person", true, JPAEtagValidator.WEAK); + final Instant i = Instant.now(); + final var act = cut.asEtag(et, Timestamp.from(i)); + assertEquals("W/\"" + i.toString() + "\"", act); + assertEtagConsistent(act); + } + + @Test + void testAsEtagStringValueNullEmptyString() throws ODataJPAModelException, ODataJPAQueryException { + final JPAEntityType et = createEntityType("Person", true, JPAEtagValidator.WEAK); + final var act = cut.asEtag(et, null); + assertEquals("", act); + } + + @Test + void testAsEtagStringEntityTypeNoEtag() throws ODataJPAModelException, ODataJPAQueryException { + final JPAEntityType et = createEntityType("Person", false, JPAEtagValidator.STRONG); + final var act = cut.asEtag(et, null); + assertNull(act); + } + + private JPAEntityType createEntityType(final String name, final boolean hasEtag, final JPAEtagValidator validator) + throws ODataJPAModelException { + final var et = mock(JPAEntityType.class); + when(et.getExternalName()).thenReturn(name); + when(et.hasEtag()).thenReturn(hasEtag); + when(et.getEtagValidator()).thenReturn(validator); + return et; + } + + private void assertEtagConsistent(final String value) { + + final Matcher matcher = ETAG.matcher(value.trim()); + assertTrue(matcher.matches(), "Match: " + matcher.find()); + + } +} 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 d1318aa78..995e2a4aa 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 @@ -19,7 +19,9 @@ import jakarta.persistence.EntityManager; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.debug.DebugSupport; +import org.apache.olingo.server.api.etag.ETagHelper; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.junit.jupiter.api.BeforeEach; @@ -39,6 +41,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataDatabaseProcessor; import com.sap.olingo.jpa.processor.core.api.JPAODataDefaultTransactionFactory; +import com.sap.olingo.jpa.processor.core.api.JPAODataEtagHelper; 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; @@ -77,12 +80,17 @@ class JPAODataInternalRequestContextTest { private JPAEdmProvider edmProvider; private JPAODataDatabaseOperations operationConverter; private JPAODataQueryDirectives queryDirectives; + private JPAODataEtagHelper etagHelper; + private OData odata; + private ETagHelper olingoEtagHelper; @BeforeEach void setup() throws ODataException { contextAccess = mock(JPAODataRequestContextAccess.class); requestContext = mock(JPAODataRequestContext.class); sessionContext = mock(JPAODataSessionContextAccess.class); + odata = mock(OData.class); + olingoEtagHelper = mock(ETagHelper.class); uriInfoResource = mock(UriInfoResource.class); header = new HashMap<>(); @@ -100,8 +108,11 @@ void setup() throws ODataException { edmProvider = mock(JPAEdmProvider.class); operationConverter = mock(JPAODataDatabaseOperations.class); queryDirectives = new JPAODataQueryDirectives.JPAODataQueryDirectivesImpl(0); + etagHelper = mock(JPAODataEtagHelper.class); page = new JPAODataPage(uriInfo, 0, 0, claims); + when(odata.createETagHelper()).thenReturn(olingoEtagHelper); + when(contextAccess.getTransactionFactory()).thenReturn(transactionFactory); when(contextAccess.getRequestParameter()).thenReturn(customParameter); when(contextAccess.getEntityManager()).thenReturn(em); @@ -110,6 +121,7 @@ void setup() throws ODataException { when(contextAccess.getProvidedLocale()).thenReturn(locales); when(contextAccess.getDebugger()).thenReturn(debugger); when(contextAccess.getQueryDirectives()).thenReturn(queryDirectives); + when(contextAccess.getEtagHelper()).thenReturn(etagHelper); when(requestContext.getClaimsProvider()).thenReturn(claims); when(requestContext.getCUDRequestHandler()).thenReturn(cudHandler); @@ -145,10 +157,11 @@ void testCreateFromContextAccessWithDebugger() throws ODataJPAIllegalAccessExcep assertEquals(page, cut.getPage()); assertEquals(uriInfo, cut.getUriInfo()); assertEquals(queryDirectives, cut.getQueryDirectives()); + assertEquals(etagHelper, cut.getEtagHelper()); } @Test - void testCreateFromContextUriAccessWithDebugger() throws ODataJPAIllegalAccessException, ODataJPAProcessorException { + void testCreateFromContextUriAccessWithDebugger() throws ODataJPAProcessorException { cut = new JPAODataInternalRequestContext(uriInfoResource, serializer, contextAccess, header); @@ -164,10 +177,11 @@ void testCreateFromContextUriAccessWithDebugger() throws ODataJPAIllegalAccessEx assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); assertEquals(queryDirectives, cut.getQueryDirectives()); + assertEquals(etagHelper, cut.getEtagHelper()); } @Test - void testCreateFromContextUriContextAccess() throws ODataJPAIllegalAccessException, ODataJPAProcessorException { + void testCreateFromContextUriContextAccess() throws ODataJPAProcessorException { when(contextAccess.getHeader()).thenReturn(new JPAHttpHeaderHashMap(header)); cut = new JPAODataInternalRequestContext(uriInfoResource, contextAccess); @@ -184,10 +198,11 @@ void testCreateFromContextUriContextAccess() throws ODataJPAIllegalAccessExcepti assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); assertEquals(queryDirectives, cut.getQueryDirectives()); + assertEquals(etagHelper, cut.getEtagHelper()); } @Test - void testCreateFromContextUriContextAccessHeader() throws ODataJPAIllegalAccessException, ODataJPAProcessorException { + void testCreateFromContextUriContextAccessHeader() throws ODataJPAProcessorException { cut = new JPAODataInternalRequestContext(uriInfoResource, contextAccess, header); @@ -203,12 +218,13 @@ void testCreateFromContextUriContextAccessHeader() throws ODataJPAIllegalAccessE assertEquals(locales, cut.getProvidedLocale()); assertNotNull(cut.getCUDRequestHandler()); assertEquals(queryDirectives, cut.getQueryDirectives()); + assertEquals(etagHelper, cut.getEtagHelper()); } @Test - void testCreateFromRequestContextWithDebugSupport() throws ODataJPAIllegalAccessException { + void testCreateFromRequestContextWithDebugSupport() { // contextAccess.get - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); assertEquals(em, cut.getEntityManager()); assertEquals(claims, cut.getClaimsProvider()); @@ -223,7 +239,7 @@ void testCreateFromRequestContextWithDebugSupport() throws ODataJPAIllegalAccess @Test void testCreateFromContextAccessWithDebugSupport() throws ODataJPAIllegalAccessException, ODataJPAProcessorException { - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); cut = new JPAODataInternalRequestContext(page, serializer, cut, header); assertTrue(cut.getDebugSupport().isUserAuthorized()); } @@ -309,8 +325,7 @@ void testSetUriInfoThrowsExceptionPageExists() throws ODataJPAIllegalAccessExcep } @Test - void testSetJPAODataPageThrowsExceptionUriInfoExists() throws ODataJPAIllegalAccessException, - ODataJPAProcessorException { + void testSetJPAODataPageThrowsExceptionUriInfoExists() throws ODataJPAProcessorException { cut = new JPAODataInternalRequestContext(uriInfoResource, serializer, contextAccess, header); assertThrows(ODataJPAIllegalAccessException.class, () -> cut.setJPAODataPage(page)); @@ -336,19 +351,25 @@ void testSetSerializer() throws ODataJPAIllegalAccessException, ODataJPAProcesso @Test void testGetDatabaseProcessor() { - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); assertEquals(dbProcessor, cut.getDatabaseProcessor()); } @Test void testGetEdmProvider() throws ODataException { - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); assertEquals(edmProvider, cut.getEdmProvider()); } @Test void testGetOperationConverter() { - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); assertEquals(operationConverter, cut.getOperationConverter()); } + + @Test + void testGetEtagHelper() { + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); + assertNotNull(cut.getEtagHelper()); + } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPACreateProcessor.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPACreateProcessor.java index 841ea901c..2d6b9f7a5 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPACreateProcessor.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPACreateProcessor.java @@ -49,7 +49,6 @@ import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; -import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.UriResourceNavigation; @@ -59,7 +58,6 @@ 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.JPAStructuredType; -import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAAbstractCUDRequestHandler; import com.sap.olingo.jpa.processor.core.api.JPACUDRequestHandler; import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider; @@ -82,7 +80,7 @@ class TestJPACreateProcessor extends TestJPAModifyProcessor { @Test - void testHookIsCalled() throws ODataJPAModelException, ODataException { + void testHookIsCalled() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -95,7 +93,7 @@ void testHookIsCalled() throws ODataJPAModelException, ODataException { } @Test - void testEntityTypeProvided() throws ODataJPAProcessorException, SerializerException, ODataException { + void testEntityTypeProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -109,7 +107,7 @@ void testEntityTypeProvided() throws ODataJPAProcessorException, SerializerExcep @SuppressWarnings("unchecked") @Test - void testAttributesProvided() throws ODataJPAProcessorException, SerializerException, ODataException { + void testAttributesProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); final Map attributes = new HashMap<>(1); @@ -131,7 +129,7 @@ void testAttributesProvided() throws ODataJPAProcessorException, SerializerExcep } @Test - void testHeadersProvided() throws ODataJPAProcessorException, SerializerException, ODataException { + void testHeadersProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); final Map> headers = new HashMap<>(); @@ -151,8 +149,7 @@ void testHeadersProvided() throws ODataJPAProcessorException, SerializerExceptio } @Test - void testClaimsProvided() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testClaimsProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -170,15 +167,13 @@ void testClaimsProvided() throws ODataJPAProcessorException, SerializerException } @Test - void testGroupsProvided() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testGroupsProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); final RequestHandleSpy spy = new RequestHandleSpy(); final JPAODataGroupsProvider provider = new JPAODataGroupsProvider(); provider.addGroup("Person"); - // final List groups = new ArrayList<>(Arrays.asList("Person")); final Optional groups = Optional.of(provider); when(requestContext.getCUDRequestHandler()).thenReturn(spy); when(requestContext.getGroupsProvider()).thenReturn(groups); @@ -231,8 +226,7 @@ void testThrowUnexpectedExceptionInCaseOfError() throws ODataException { } @Test - void testMinimalResponseLocationHeader() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseLocationHeader() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -245,8 +239,7 @@ void testMinimalResponseLocationHeader() throws ODataJPAProcessorException, Seri } @Test - void testMinimalResponseODataEntityIdHeader() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseODataEntityIdHeader() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -259,7 +252,7 @@ void testMinimalResponseODataEntityIdHeader() throws ODataJPAProcessorException, } @Test - void testMinimalResponseStatusCode() throws ODataJPAProcessorException, SerializerException, ODataException { + void testMinimalResponseStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -272,8 +265,7 @@ void testMinimalResponseStatusCode() throws ODataJPAProcessorException, Serializ } @Test - void testMinimalResponsePreferApplied() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponsePreferApplied() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -286,8 +278,7 @@ void testMinimalResponsePreferApplied() throws ODataJPAProcessorException, Seria } @Test - void testRepresentationResponseStatusCode() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationResponseStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy()); @@ -298,8 +289,7 @@ void testRepresentationResponseStatusCode() throws ODataJPAProcessorException, S } @Test - void testRepresentationResponseStatusCodeMapResult() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationResponseStatusCodeMapResult() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleMapResultSpy()); @@ -310,8 +300,7 @@ void testRepresentationResponseStatusCodeMapResult() throws ODataJPAProcessorExc } @Test - void testRepresentationResponseContent() throws ODataJPAProcessorException, SerializerException, - ODataException, IOException { + void testRepresentationResponseContent() throws ODataException, IOException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy()); @@ -324,8 +313,7 @@ void testRepresentationResponseContent() throws ODataJPAProcessorException, Seri } @Test - void testPersonResponseContent() throws ODataJPAProcessorException, SerializerException, - ODataException, IOException { + void testPersonResponseContent() throws ODataException, IOException { final JPAODataSessionContextAccess context = mock(JPAODataSessionContextAccess.class); final EdmType propertyType = mock(EdmPrimitiveType.class); final EdmProperty propertyFN = createProperty("FullName", EdmString.getInstance()); @@ -361,8 +349,7 @@ void testPersonResponseContent() throws ODataJPAProcessorException, SerializerEx } @Test - void testRepresentationResponseContentMapResult() throws ODataJPAProcessorException, SerializerException, - ODataException, IOException { + void testRepresentationResponseContentMapResult() throws ODataException, IOException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleMapResultSpy()); @@ -375,8 +362,7 @@ void testRepresentationResponseContentMapResult() throws ODataJPAProcessorExcept } @Test - void testRepresentationLocationHeader() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationLocationHeader() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy()); @@ -387,8 +373,7 @@ void testRepresentationLocationHeader() throws ODataJPAProcessorException, Seria } @Test - void testRepresentationLocationHeaderMapResult() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationLocationHeaderMapResult() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleMapResultSpy()); @@ -399,7 +384,7 @@ void testRepresentationLocationHeaderMapResult() throws ODataJPAProcessorExcepti } @Test - void testCallsValidateChangesOnSuccessfullProcessing() throws ODataException { + void testCallsValidateChangesOnSuccessfulProcessing() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -411,7 +396,7 @@ void testCallsValidateChangesOnSuccessfullProcessing() throws ODataException { } @Test - void testDoesNotCallsValidateChangesOnForginTransaction() throws ODataException { + void testDoesNotCallsValidateChangesOnForeignTransaction() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -488,7 +473,7 @@ void testDoesRollbackOnWrongResponse() throws ODataException { } @Test - void testOwnTransactionCommitted() throws ODataJPAModelException, ODataException { + void testOwnTransactionCommitted() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -500,8 +485,7 @@ void testOwnTransactionCommitted() throws ODataJPAModelException, ODataException } @Test - void testResponseCreateChildSameTypeContent() throws ODataJPAProcessorException, SerializerException, - ODataException, IOException { + void testResponseCreateChildSameTypeContent() throws ODataException { when(ets.getName()).thenReturn("AdministrativeDivisions"); final AdministrativeDivision div = new AdministrativeDivision(new AdministrativeDivisionKey("Eurostat", "NUTS1", @@ -540,8 +524,7 @@ void testResponseCreateChildSameTypeContent() throws ODataJPAProcessorException, } @Test - void testResponseCreateChildDifferentTypeContent() throws ODataJPAProcessorException, SerializerException, - ODataException, IOException { + void testResponseCreateChildDifferentTypeContent() throws ODataException { final Organization org = new Organization("Test"); final BusinessPartnerRole role = new BusinessPartnerRole(); @@ -586,11 +569,8 @@ void testResponseCreateChildDifferentTypeContent() throws ODataJPAProcessorExcep when(edmET.getKeyPredicateNames()).thenReturn(keyNames); createKeyProperty(fqn, edmET, "BusinessPartnerID", "Test"); createKeyProperty(fqn, edmET, "RoleCategory", "A"); - // edmType.getFullQualifiedName().getFullQualifiedNameAsString() pathParts.add(uriChild); - // return serviceMetadata.getEdm().getEntityType(es.getODataEntityType().getExternalFQN()); - // com.sap.olingo.jpa.BusinessPartnerRole processor = new JPACUDRequestProcessor(odata, serviceMetadata, requestContext, convHelper); processor.createEntity(request, response, ContentType.JSON, ContentType.JSON); @@ -602,7 +582,7 @@ void testResponseCreateChildDifferentTypeContent() throws ODataJPAProcessorExcep } protected ODataRequest prepareRequestToCreateChild(final JPAAbstractCUDRequestHandler spy) - throws ODataJPAProcessorException, SerializerException, ODataException { + throws ODataException { // .../AdministrativeDivisions(DivisionCode='DE6',CodeID='NUTS1',CodePublisher='Eurostat')/Children final ODataRequest request = prepareSimpleRequest("return=representation"); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAModifyProcessor.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAModifyProcessor.java index c9e495fb3..569498729 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAModifyProcessor.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAModifyProcessor.java @@ -72,7 +72,7 @@ abstract class TestJPAModifyProcessor { protected static JPAEdmProvider jpaEdm; protected static DataSource ds; protected static List annotationProvider; - protected static JPAReferences refrences; + protected static JPAReferences references; @BeforeAll public static void setupClass() throws ODataException { @@ -129,6 +129,7 @@ public void setup() throws Exception { when(requestContext.getUriInfo()).thenReturn(uriInfo); when(requestContext.getSerializer()).thenReturn(serializer); when(requestContext.getTransactionFactory()).thenReturn(factory); + when(requestContext.getEtagHelper()).thenReturn(new JPAODataEtagHelperImpl(odata)); when(uriInfo.getUriResourceParts()).thenReturn(pathParts); when(uriEts.getKeyPredicates()).thenReturn(keyPredicates); when(uriEts.getEntitySet()).thenReturn(ets); @@ -141,7 +142,7 @@ public void setup() throws Exception { } protected ODataRequest prepareRepresentationRequest(final JPAAbstractCUDRequestHandler spy) - throws ODataJPAProcessorException, SerializerException, ODataException { + throws ODataException { final ODataRequest request = prepareSimpleRequest("return=representation"); @@ -175,7 +176,7 @@ protected ODataRequest prepareRepresentationRequest(final JPAAbstractCUDRequestH } protected ODataRequest prepareLinkRequest(final JPAAbstractCUDRequestHandler spy) - throws ODataJPAProcessorException, SerializerException, ODataException { + throws ODataException { // .../AdministrativeDivisions(DivisionCode='DE60',CodeID='NUTS2',CodePublisher='Eurostat') final ODataRequest request = prepareSimpleRequest("return=representation"); @@ -239,8 +240,7 @@ protected ODataRequest prepareSimpleRequest() throws ODataException, ODataJPAPro } @SuppressWarnings("unchecked") - protected ODataRequest prepareSimpleRequest(final String content) throws ODataException, ODataJPAProcessorException, - SerializerException { + protected ODataRequest prepareSimpleRequest(final String content) throws ODataException { final EntityTransaction transaction = mock(EntityTransaction.class); when(em.getTransaction()).thenReturn(transaction); @@ -260,7 +260,7 @@ protected ODataRequest prepareSimpleRequest(final String content) throws ODataEx } protected ODataRequest preparePersonRequest(final JPAAbstractCUDRequestHandler spy) - throws ODataJPAProcessorException, SerializerException, ODataException { + throws ODataException { final ODataRequest request = prepareSimpleRequest("return=representation"); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAODataRequestContextImpl.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAODataRequestContextImpl.java index cfd7180a1..d3661c223 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAODataRequestContextImpl.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAODataRequestContextImpl.java @@ -19,6 +19,7 @@ import jakarta.persistence.EntityManager; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.junit.jupiter.api.BeforeEach; @@ -49,14 +50,16 @@ class TestJPAODataRequestContextImpl { private JPAODataSessionContextAccess sessionContext; private JPAODataRequestContext requestContext; private JPAEdmProvider edmProvider; + private OData odata; @BeforeEach void setup() throws ODataException { edmProvider = mock(JPAEdmProvider.class); sessionContext = mock(JPAODataSessionContextAccess.class); requestContext = mock(JPAODataRequestContext.class); + odata = mock(OData.class); when(sessionContext.getEdmProvider()).thenReturn(edmProvider); - cut = new JPAODataInternalRequestContext(requestContext, sessionContext); + cut = new JPAODataInternalRequestContext(requestContext, sessionContext, odata); } @Test @@ -86,7 +89,7 @@ void testReturnsSetUriInfo() throws ODataJPAIllegalAccessException { } @Test - void testReturnsSetJPASerializer() throws ODataJPAIllegalAccessException { + void testReturnsSetJPASerializer() { final JPASerializer exp = mock(JPASerializer.class); cut.setJPASerializer(exp); assertEquals(exp, cut.getSerializer()); @@ -101,7 +104,7 @@ void testThrowsExceptionOnSetPageIfUriInfoExists() throws ODataJPAIllegalAccessE } @Test - void testThrowsExceptionOnPageIsNull() throws ODataJPAIllegalAccessException { + void testThrowsExceptionOnPageIsNull() { assertThrows(NullPointerException.class, () -> cut.setJPAODataPage(null)); } @@ -114,12 +117,12 @@ void testThrowsExceptionOnSetUriInfoIfUriInfoExists() throws ODataJPAIllegalAcce } @Test - void testThrowsExceptionOnUriInfoIsNull() throws ODataJPAIllegalAccessException { + void testThrowsExceptionOnUriInfoIsNull() { assertThrows(NullPointerException.class, () -> cut.setUriInfo(null)); } @Test - void testThrowsExceptionOnSerializerIsNull() throws ODataJPAIllegalAccessException { + void testThrowsExceptionOnSerializerIsNull() { assertThrows(NullPointerException.class, () -> cut.setJPASerializer(null)); } @@ -176,8 +179,7 @@ void testCopyConstructorCopysExternalAndAddsUriInfoSerializerNull() throws OData } @Test - void testGetCalculatorReturnsEmptyOptionalIfNotTransient() throws ODataJPAModelException, - ODataJPAProcessorException { + void testGetCalculatorReturnsEmptyOptionalIfNotTransient() throws ODataJPAProcessorException { final JPAAttribute attribute = mock(JPAAttribute.class); when(attribute.isTransient()).thenReturn(false); assertFalse(cut.getCalculator(attribute).isPresent()); @@ -218,7 +220,7 @@ void testGetCalculatorReturnsInstanceEntityManager() throws ODataJPAModelExcepti final Constructor c = DummyPropertyCalculator.class.getConstructor(EntityManager.class); cut = new JPAODataInternalRequestContext(JPAODataRequestContext - .with().setEntityManager(mock(EntityManager.class)).build(), sessionContext); + .with().setEntityManager(mock(EntityManager.class)).build(), sessionContext, odata); when(attribute.isTransient()).thenReturn(true); when(attribute.getCalculatorConstructor()).thenReturn((Constructor>) c); @@ -250,7 +252,7 @@ void testGetCalculatorReturnsInstanceTwoParameter() throws ODataJPAModelExceptio final Constructor c = TwoParameterTransientPropertyConverter.class.getConstructor(EntityManager.class, JPAHttpHeaderMap.class); cut = new JPAODataInternalRequestContext(JPAODataRequestContext - .with().setEntityManager(mock(EntityManager.class)).build(), sessionContext); + .with().setEntityManager(mock(EntityManager.class)).build(), sessionContext, odata); when(attribute.isTransient()).thenReturn(true); when(attribute.getCalculatorConstructor()).thenReturn((Constructor>) c); @@ -266,7 +268,7 @@ void testGetLocaleReturnsValueFromExternalContext() { .with() .setEntityManager(mock(EntityManager.class)) .setLocales(Arrays.asList(Locale.UK, Locale.ENGLISH)) - .build(), sessionContext); + .build(), sessionContext, odata); assertEquals(Locale.UK, cut.getLocale()); } @@ -277,7 +279,7 @@ void testGetLocaleReturnsValueFromExternalContextAfterCopy() throws ODataJPAProc .with() .setEntityManager(mock(EntityManager.class)) .setLocales(Arrays.asList(Locale.UK, Locale.ENGLISH)) - .build(), sessionContext); + .build(), sessionContext, odata); cut = new JPAODataInternalRequestContext(mock(UriInfoResource.class), cut); assertEquals(Locale.UK, cut.getLocale()); @@ -299,6 +301,6 @@ private void fillContextForCopyConstructor() { .setClaimsProvider(expCp) .setGroupsProvider(expGp) .build(); - cut = new JPAODataInternalRequestContext(context, sessionContext); + cut = new JPAODataInternalRequestContext(context, sessionContext, odata); } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAUpdateProcessor.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAUpdateProcessor.java index 9d233a120..8af580157 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAUpdateProcessor.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/processor/TestJPAUpdateProcessor.java @@ -36,14 +36,12 @@ import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; -import org.apache.olingo.server.api.serializer.SerializerException; import org.apache.olingo.server.api.uri.UriResourceProperty; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; 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.processor.core.api.JPAAbstractCUDRequestHandler; import com.sap.olingo.jpa.processor.core.api.JPACUDRequestHandler; import com.sap.olingo.jpa.processor.core.api.JPAODataClaimProvider; @@ -61,7 +59,7 @@ class TestJPAUpdateProcessor extends TestJPAModifyProcessor { @Test - void testHookIsCalled() throws ODataJPAModelException, ODataException { + void testHookIsCalled() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -74,7 +72,7 @@ void testHookIsCalled() throws ODataJPAModelException, ODataException { } @Test - void testHttpMethodProvided() throws ODataJPAModelException, ODataException { + void testHttpMethodProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -89,7 +87,7 @@ void testHttpMethodProvided() throws ODataJPAModelException, ODataException { } @Test - void testEntityTypeProvided() throws ODataJPAModelException, ODataException { + void testEntityTypeProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -105,7 +103,7 @@ void testEntityTypeProvided() throws ODataJPAModelException, ODataException { @SuppressWarnings("unchecked") @Test - void testJPAAttributes() throws ODataJPAModelException, ODataException, UnsupportedEncodingException { + void testJPAAttributes() throws ODataException, UnsupportedEncodingException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -129,8 +127,7 @@ void testJPAAttributes() throws ODataJPAModelException, ODataException, Unsuppor @SuppressWarnings("unchecked") @Test - void testProvideSimplePrimitivePutAsPatch() throws ODataJPAProcessorException, SerializerException, - ODataException, UnsupportedEncodingException { + void testProvideSimplePrimitivePutAsPatch() throws ODataException, UnsupportedEncodingException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -166,8 +163,7 @@ void testProvideSimpleComplexPutAsPatch() { @SuppressWarnings("unchecked") @Test - void testProvidePrimitiveCollectionPutAsPatch() throws ODataJPAProcessorException, SerializerException, - ODataException, UnsupportedEncodingException { + void testProvidePrimitiveCollectionPutAsPatch() throws ODataException, UnsupportedEncodingException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -202,8 +198,7 @@ void testProvidePrimitiveCollectionPutAsPatch() throws ODataJPAProcessorExceptio @SuppressWarnings("unchecked") @Test - void testProvideComplexCollectionPutAsPatch() throws ODataJPAProcessorException, SerializerException, - ODataException, UnsupportedEncodingException { + void testProvideComplexCollectionPutAsPatch() throws ODataException, UnsupportedEncodingException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -237,8 +232,7 @@ void testProvideComplexCollectionPutAsPatch() throws ODataJPAProcessorException, } @Test - void testHeadersProvided() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testHeadersProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); final Map> headers = new HashMap<>(); @@ -258,8 +252,7 @@ void testHeadersProvided() throws ODataJPAProcessorException, SerializerExceptio } @Test - void testClaimsProvided() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testClaimsProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -277,15 +270,13 @@ void testClaimsProvided() throws ODataJPAProcessorException, SerializerException } @Test - void testGroupsProvided() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testGroupsProvided() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); final RequestHandleSpy spy = new RequestHandleSpy(); final JPAODataGroupsProvider provider = new JPAODataGroupsProvider(); provider.addGroup("Person"); - // final List groups = new ArrayList<>(Arrays.asList("Person")); final Optional groups = Optional.of(provider); when(requestContext.getCUDRequestHandler()).thenReturn(spy); when(requestContext.getGroupsProvider()).thenReturn(groups); @@ -298,8 +289,7 @@ void testGroupsProvided() throws ODataJPAProcessorException, SerializerException } @Test - void testMinimalResponseUpdateStatusCode() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseUpdateStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -313,8 +303,7 @@ void testMinimalResponseUpdateStatusCode() throws ODataJPAProcessorException, Se } @Test - void testMinimalResponseCreatedStatusCode() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseCreatedStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -328,8 +317,7 @@ void testMinimalResponseCreatedStatusCode() throws ODataJPAProcessorException, S } @Test - void testMinimalResponseUpdatePreferHeader() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseUpdatePreferHeader() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -343,8 +331,7 @@ void testMinimalResponseUpdatePreferHeader() throws ODataJPAProcessorException, } @Test - void testMinimalResponseCreatedPreferHeader() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testMinimalResponseCreatedPreferHeader() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -358,8 +345,7 @@ void testMinimalResponseCreatedPreferHeader() throws ODataJPAProcessorException, } @Test - void testRepresentationResponseUpdatedStatusCode() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationResponseUpdatedStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(new JPAUpdateResult(false, @@ -371,8 +357,7 @@ void testRepresentationResponseUpdatedStatusCode() throws ODataJPAProcessorExcep } @Test - void testRepresentationResponseCreatedStatusCode() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationResponseCreatedStatusCode() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(new JPAUpdateResult(true, @@ -384,7 +369,7 @@ void testRepresentationResponseCreatedStatusCode() throws ODataJPAProcessorExcep } @Test - void testRepresentationResponseUpdatedErrorMissingEntity() throws ODataJPAProcessorException, ODataException { + void testRepresentationResponseUpdatedErrorMissingEntity() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(new JPAUpdateResult(false, null))); @@ -399,7 +384,7 @@ void testRepresentationResponseUpdatedErrorMissingEntity() throws ODataJPAProces } @Test - void testRepresentationResponseCreatedErrorMissingEntity() throws ODataJPAProcessorException, ODataException { + void testRepresentationResponseCreatedErrorMissingEntity() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(new JPAUpdateResult(true, null))); @@ -414,8 +399,7 @@ void testRepresentationResponseCreatedErrorMissingEntity() throws ODataJPAProces } @Test - void testRepresentationResponseUpdatedWithKey() throws ODataJPAProcessorException, SerializerException, - ODataException { + void testRepresentationResponseUpdatedWithKey() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(new JPAUpdateResult(false, @@ -556,7 +540,7 @@ void testDoesRollbackOnWrongResponse() throws ODataException { } @Test - void testResponseErrorIfNull() throws ODataJPAProcessorException, ODataException { + void testResponseErrorIfNull() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareRepresentationRequest(new RequestHandleSpy(null)); @@ -571,7 +555,7 @@ void testResponseErrorIfNull() throws ODataJPAProcessorException, ODataException } @Test - void testResponseUpdateLink() throws ODataJPAProcessorException, ODataException { + void testResponseUpdateLink() throws ODataException { final AdministrativeDivisionKey key = new AdministrativeDivisionKey("Eurostat", "NUTS2", "DE60"); final AdministrativeDivision resultEntity = new AdministrativeDivision(key); @@ -591,7 +575,7 @@ void testResponseUpdateLink() throws ODataJPAProcessorException, ODataException } @Test - void testOwnTransactionCommitted() throws ODataJPAModelException, ODataException { + void testOwnTransactionCommitted() throws ODataException { final ODataResponse response = new ODataResponse(); final ODataRequest request = prepareSimpleRequest(); @@ -627,7 +611,6 @@ public JPAUpdateResult updateEntity(final JPARequestEntity requestEntity, final final HttpMethod verb) throws ODataJPAProcessException { this.et = requestEntity.getEntityType(); this.jpaAttributes = requestEntity.getData(); - // this.keys = requestEntity.getKeys(); this.em = em; this.called = true; this.method = verb; diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapperTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapperTest.java index 46344c534..1ba1ddc7f 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapperTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPACollectionExpandWrapperTest.java @@ -41,12 +41,12 @@ void setup() { filterOptions = mock(FilterOption.class); countOptions = mock(CountOption.class); selectOptions = mock(SelectOption.class); - cut = new JPACollectionExpandWrapper(jpaEntityType, uriInfo); when(uriInfo.getFilterOption()).thenReturn(filterOptions); when(uriInfo.getCountOption()).thenReturn(countOptions); when(uriInfo.getSelectOption()).thenReturn(selectOptions); when(uriInfo.getUriResourceParts()).thenReturn(parts); + cut = new JPACollectionExpandWrapper(jpaEntityType, uriInfo); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQueryTest.java index 8a28d08e0..a5adf69a3 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQueryTest.java @@ -75,6 +75,16 @@ void setup() throws ODataException { when(requestContext.getOperationConverter()).thenReturn(new JPADefaultDatabaseProcessor()); } + @Test + void testSelectAllWithAllExpand() throws ODataException { + // .../Organizations?$expand=Roles&$format=json + final JPAInlineItemInfo item = createOrganizationExpandRoles(null, null); + cut = new JPAExpandJoinQuery(OData.newInstance(), item, requestContext, Optional.empty()); + final JPAExpandQueryResult act = cut.execute(); + assertEquals(4, act.getNoResults()); + assertEquals(7, act.getNoResultsDeep()); + } + @Test void testSelectDistinctExpand() throws ODataException { // .../BusinessPartnerRoles?$expand=BusinessPartner($select=ID;$expand=Roles) @@ -87,16 +97,6 @@ void testSelectDistinctExpand() throws ODataException { @Test void testSelectOrganizationByIdWithAllExpand() throws ODataException { - // .../Organizations?$expand=Roles&$format=json - final JPAInlineItemInfo item = createOrganizationExpandRoles(null, null); - cut = new JPAExpandJoinQuery(OData.newInstance(), item, requestContext, Optional.empty()); - final JPAExpandQueryResult act = cut.execute(); - assertEquals(4, act.getNoResults()); - assertEquals(7, act.getNoResultsDeep()); - } - - @Test - void testSelectOrgByIdWithAllExpand() throws ODataException { // .../Organizations('2')?$expand=Roles&$format=json final UriParameter key = mock(UriParameter.class); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionDB.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAFunctionRequestProcessorDbTest.java similarity index 82% rename from jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionDB.java rename to jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAFunctionRequestProcessorDbTest.java index fdf24f272..912025b7a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionDB.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAFunctionRequestProcessorDbTest.java @@ -34,6 +34,8 @@ import org.mockito.stubbing.Answer; 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.annotation.EdmFunctionType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOperationResultParameter; @@ -44,7 +46,7 @@ import com.sap.olingo.jpa.processor.core.processor.JPAFunctionRequestProcessor; import com.sap.olingo.jpa.processor.core.serializer.JPAOperationSerializer; -class TestJPAFunctionDB { +class JPAFunctionRequestProcessorDbTest { protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; private JPAODataDatabaseProcessor dbProcessor; @@ -63,6 +65,8 @@ class TestJPAFunctionDB { private JPAOperationSerializer serializer; private SerializerResult serializerResult; private EntityManager em; + private JPAHttpHeaderMap headers; + private JPARequestParameterMap parameters; @BeforeEach void setup() throws ODataException { @@ -82,12 +86,16 @@ void setup() throws ODataException { function = mock(JPADataBaseFunction.class); uriResources = new ArrayList<>(); edmFunction = mock(EdmFunction.class); + headers = mock(JPAHttpHeaderMap.class); + parameters = mock(JPARequestParameterMap.class); when(requestContext.getSerializer()).thenReturn(serializer); when(serializer.serialize(any(Annotatable.class), any(EdmType.class), any(ODataRequest.class))) .thenReturn(serializerResult); when(requestContext.getUriInfo()).thenReturn(uriInfo); when(requestContext.getEntityManager()).thenReturn(em); + when(requestContext.getHeader()).thenReturn(headers); + when(requestContext.getRequestParameter()).thenReturn(parameters); when(uriInfo.getUriResourceParts()).thenReturn(uriResources); when(requestContext.getDatabaseProcessor()).thenReturn(dbProcessor); when(requestContext.getEdmProvider()).thenReturn(provider); @@ -118,28 +126,6 @@ public Class answer(final InvocationOnMock invocation) throws Throwable { when(edmReturnType.getType()).thenReturn(new EdmBoolean()); cut.retrieveData(request, response, ContentType.JSON); - verify(dbProcessor, times(1)).executeFunctionQuery(uriResources, function, em); - } - - @Test - void testCallsFunctionCount() throws ODataApplicationException, ODataLibraryException, - ODataJPAModelException { - - final EdmReturnType edmReturnType = mock(EdmReturnType.class); - final JPAOperationResultParameter resultParam = mock(JPAOperationResultParameter.class); - when(function.getResultParameter()).thenReturn(resultParam); - when(resultParam.getTypeFQN()).thenReturn(new FullQualifiedName(PUNIT_NAME, "CheckRights")); - when(resultParam.getType()).thenAnswer(new Answer>() { - @Override - public Class answer(final InvocationOnMock invocation) throws Throwable { - return Boolean.class; - } - }); - - when(edmFunction.getReturnType()).thenReturn(edmReturnType); - when(edmReturnType.getType()).thenReturn(new EdmBoolean()); - - cut.retrieveData(request, response, ContentType.JSON); - verify(dbProcessor, times(1)).executeFunctionQuery(uriResources, function, em); + verify(dbProcessor, times(1)).executeFunctionQuery(uriResources, function, em, headers, parameters); } } 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 612a2f1af..53b0eca96 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,6 +1,7 @@ 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.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -8,16 +9,18 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; 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.api.JPAEntityType; 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.impl.JPADefaultEdmNameBuilder; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; @@ -30,8 +33,7 @@ class JPAJoinQueryTest extends TestQueryBase { private CriteriaBuilder cb; - @SuppressWarnings("rawtypes") - private CriteriaQuery cq; + private EntityManager em; private JPAODataRequestContextAccess localContext; private JPAHttpHeaderMap headerMap; @@ -42,7 +44,7 @@ class JPAJoinQueryTest extends TestQueryBase { 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); @@ -111,4 +113,11 @@ void testDerivedTypeRequestedFalseOtherBaseType() { assertFalse(cut.derivedTypeRequested(baseType, potentialSubType)); } + + @Test + void testBuildEntityPathListRethrowsException() throws ODataJPAModelException { + final var jpaEntityType = mock(JPAEntityType.class); + when(jpaEntityType.getPathList()).thenThrow(ODataJPAModelException.class); + assertThrows(ODataApplicationException.class, () -> cut.buildEntityPathList(jpaEntityType)); + } } 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 ddf18baef..202b2256b 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 @@ -101,7 +101,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { when(em.getCriteriaBuilder()).thenReturn(cb); when(uriResourceItem.getKeyPredicates()).thenReturn(Collections.singletonList(key)); when(parent.getQuery()).thenReturn(cq); - when(cq.> subquery(any())).thenReturn(subQuery); + when(cq.> subquery(any(Class.class))).thenReturn(subQuery); when(claimsProvider.get("RoleCategory")).thenReturn(Collections.singletonList(new JPAClaimsPair<>("A"))); doReturn(BusinessPartnerProtected.class).when(from).getJavaType(); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryTest.java index 30411acc4..c5c213a97 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationFilterQueryTest.java @@ -79,7 +79,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { when(em.getCriteriaBuilder()).thenReturn(cb); when(uriResourceItem.getKeyPredicates()).thenReturn(Collections.singletonList(key)); when(parent.getQuery()).thenReturn(cq); - when(cq.subquery(any())).thenReturn(subQuery); + when(cq.subquery(any(Class.class))).thenReturn(subQuery); when(subQuery.from(JoinPartnerRoleRelation.class)).thenReturn(queryJoinTable); when(subQuery.from(BusinessPartnerRoleProtected.class)).thenReturn(queryRoot); when(claimsProvider.get("RoleCategory")).thenReturn(Collections.singletonList(new JPAClaimsPair<>("A"))); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQueryTest.java index ae612b236..4492f173f 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPANavigationNullQueryTest.java @@ -91,7 +91,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { // when(uriResourceItem.getType()).thenReturn(jpaEntityType); when(uriResourceItem.getKeyPredicates()).thenReturn(Collections.singletonList(key)); when(parent.getQuery()).thenReturn(cq); - when(cq.subquery(any())).thenReturn(subQuery); + when(cq.subquery(any(Class.class))).thenReturn(subQuery); when(subQuery.from(BusinessPartnerProtected.class)).thenReturn(queryRoot); when(claimsProvider.get("UserId")).thenReturn(Collections.singletonList(new JPAClaimsPair<>("Willi"))); doReturn(BusinessPartnerProtected.class).when(from).getJavaType(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAOrderByBuilder.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java similarity index 75% rename from jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAOrderByBuilder.java rename to jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java index f71f6e578..b3c253a4a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAOrderByBuilder.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java @@ -2,22 +2,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +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.mock; import static org.mockito.Mockito.when; -import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.From; -import jakarta.persistence.criteria.Order; +import java.util.Set; import org.apache.olingo.commons.api.edm.EdmNavigationProperty; import org.apache.olingo.commons.api.edm.EdmProperty; @@ -43,13 +41,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable; 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.processor.core.api.JPAODataPage; import com.sap.olingo.jpa.processor.core.exception.ODataJPANotImplementedException; 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.testmodel.AdministrativeDivisionDescription; +import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneSource; +import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneTarget; import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerRole; import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerWithGroups; import com.sap.olingo.jpa.processor.core.testmodel.CollectionDeep; @@ -57,7 +57,12 @@ import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.util.TestBase; -class TestJPAOrderByBuilder extends TestBase { +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Path; + +class JPAOrderByBuilderTest extends TestBase { private JPAOrderByBuilder cut; private Map> joinTables; private UriInfoResource uriResource; @@ -66,19 +71,24 @@ class TestJPAOrderByBuilder extends TestBase { private OrderByOption orderBy; private From adminTarget; private From orgTarget; + private From toOneSourceTarget; private CriteriaBuilder cb; private JPAEntityType jpaAdminEntity; private JPAEntityType jpaOrgEntity; + private JPAEntityType toOneSourceEntity; private List groups; private JPAODataPage page; + private Set> orderByPaths; @BeforeEach - void setup() throws ODataJPAModelException, ODataException { + void setup() throws ODataException { cb = emf.getCriteriaBuilder(); jpaAdminEntity = getHelper().getJPAEntityType(AdministrativeDivisionDescription.class); adminTarget = cb.createQuery().from(getHelper().getEntityType(AdministrativeDivisionDescription.class)); jpaOrgEntity = getHelper().getJPAEntityType(Organization.class); + toOneSourceEntity = getHelper().getJPAEntityType(AssociationOneToOneSource.class); orgTarget = cb.createQuery().from(getHelper().getEntityType(Organization.class)); + toOneSourceTarget = cb.createQuery().from(getHelper().getEntityType(AssociationOneToOneSource.class)); groups = new ArrayList<>(); cut = new JPAOrderByBuilder(jpaAdminEntity, adminTarget, cb, groups); @@ -88,55 +98,56 @@ void setup() throws ODataJPAModelException, ODataException { uriResource = mock(UriInfoResource.class); page = null; joinTables = new HashMap<>(); + orderByPaths = new HashSet<>(); } @Test - void testNoTopSkipOrderByReturnsEmptyList() throws IOException, ODataException { - final List act = cut.createOrderByList(joinTables, uriResource, page); + void testNoTopSkipOrderByReturnsEmptyList() throws ODataException { + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(0, act.size()); } @Test - void testTopReturnsByPrimaryKey() throws IOException, ODataException { + void testTopReturnsByPrimaryKey() throws ODataException { when(uriResource.getTopOption()).thenReturn(top); when(top.getValue()).thenReturn(5); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testSkipReturnsByPrimaryKey() throws IOException, ODataException { + void testSkipReturnsByPrimaryKey() throws ODataException { when(uriResource.getSkipOption()).thenReturn(skip); when(skip.getValue()).thenReturn(5); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testTopSkipReturnsByPrimaryKey() throws IOException, ODataException { + void testTopSkipReturnsByPrimaryKey() throws ODataException { when(uriResource.getTopOption()).thenReturn(top); when(top.getValue()).thenReturn(5); when(uriResource.getSkipOption()).thenReturn(skip); when(skip.getValue()).thenReturn(5); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @@ -144,25 +155,25 @@ void testTopSkipReturnsByPrimaryKey() throws IOException, ODataException { void testOrderByEmptyReturnsEmptyList() throws ODataApplicationException { when(uriResource.getOrderByOption()).thenReturn(orderBy); when(orderBy.getOrders()).thenReturn(Collections.emptyList()); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(0, act.size()); } @Test - void testOrderByOneProperty() throws ODataApplicationException, ODataJPAModelException { + void testOrderByOneProperty() throws ODataApplicationException { createOrderByItem("Name"); when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); } @Test - void testOrderByOneComplexProperty() throws ODataApplicationException, ODataJPAModelException { + void testOrderByOneComplexProperty() throws ODataApplicationException { cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); createComplexOrderByItem(); when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); } @@ -175,7 +186,7 @@ void testThrowsNotImplementedOnOrderByFunction() { when(part.getKind()).thenReturn(UriResourceKind.function); assertThrows(ODataJPANotImplementedException.class, - () -> cut.createOrderByList(joinTables, uriResource, page)); + () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); } @Test @@ -189,10 +200,11 @@ void testOrderByNavigationCountDefault() throws ODataException { pathParts.add(countPart); when(navigationPart.getProperty()).thenReturn(navigationProperty); + when(navigationPart.isCollection()).thenReturn(true); when(navigationProperty.getName()).thenReturn("Roles"); joinTables.put("Roles", cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertTrue(act.get(0).isAscending()); @@ -210,10 +222,11 @@ void testOrderByNavigationCountDescending() throws ODataException { pathParts.add(countPart); when(navigationPart.getProperty()).thenReturn(navigationProperty); + when(navigationPart.isCollection()).thenReturn(true); when(navigationProperty.getName()).thenReturn("Roles"); joinTables.put("Roles", cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); @@ -221,7 +234,7 @@ void testOrderByNavigationCountDescending() throws ODataException { } @Test - void testOrderByCollectionOrderByCountAsc() throws IOException, ODataException { + void testOrderByCollectionOrderByCountAsc() throws ODataException { final JPAEntityType jpaEntity = getHelper().getJPAEntityType(CollectionDeep.class); final From target = cb.createQuery().from(getHelper().getEntityType(CollectionDeep.class)); cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); @@ -243,7 +256,7 @@ void testOrderByCollectionOrderByCountAsc() throws IOException, ODataException { joinTables.put("FirstLevel/SecondLevel/Comment", cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertTrue(act.get(0).isAscending()); @@ -251,25 +264,25 @@ void testOrderByCollectionOrderByCountAsc() throws IOException, ODataException { } @Test - void testThrowsBadRequestExceptionOnUnknownProperty() throws ODataApplicationException, ODataJPAModelException { + void testThrowsBadRequestExceptionOnUnknownProperty() { createOrderByItem("Name"); when(uriResource.getOrderByOption()).thenReturn(orderBy); cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); assertThrows(ODataJPAProcessorException.class, - () -> cut.createOrderByList(joinTables, uriResource, page)); + () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); } @Test - void testThrowsBadRequestExceptionOnUnknownComplex() throws ODataApplicationException, ODataJPAModelException { + void testThrowsBadRequestExceptionOnUnknownComplex() { createComplexOrderByItem(); when(uriResource.getOrderByOption()).thenReturn(orderBy); cut = new JPAOrderByBuilder(jpaAdminEntity, adminTarget, cb, groups); assertThrows(ODataJPAProcessorException.class, - () -> cut.createOrderByList(joinTables, uriResource, page)); + () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); } @Test - void testThrowExceptionOrderByGroupedPropertyWithoutGroup() throws IOException, ODataException { + void testThrowExceptionOrderByGroupedPropertyWithoutGroup() throws ODataException { final JPAEntityType jpaEntity = getHelper().getJPAEntityType(BusinessPartnerWithGroups.class); final From target = cb.createQuery().from(getHelper().getEntityType(BusinessPartnerWithGroups.class)); cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); @@ -277,32 +290,32 @@ void testThrowExceptionOrderByGroupedPropertyWithoutGroup() throws IOException, cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); final ODataJPAQueryException act = assertThrows(ODataJPAQueryException.class, - () -> cut.createOrderByList(joinTables, uriResource, page)); + () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); assertEquals(HttpStatusCode.FORBIDDEN.getStatusCode(), act.getStatusCode()); } @Test - void testOrderByPropertyWithGroupsOneGroup() throws IOException, ODataException { + void testOrderByPropertyWithGroupsOneGroup() throws ODataException { final JPAEntityType jpaEntity = getHelper().getJPAEntityType(BusinessPartnerWithGroups.class); final From target = cb.createQuery().from(getHelper().getEntityType(BusinessPartnerWithGroups.class)); groups.add("Person"); cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); createOrderByItem("Country"); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); } @Test - void testOrderByPropertyAndTop() throws IOException, ODataException { + void testOrderByPropertyAndTop() throws ODataException { createOrderByItem("DivisionCode"); when(top.getValue()).thenReturn(5); when(uriResource.getOrderByOption()).thenReturn(orderBy); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertFalse(act.isEmpty()); assertEquals(String.class, act.get(0).getExpression().getJavaType()); @@ -310,89 +323,89 @@ void testOrderByPropertyAndTop() throws IOException, ODataException { } @Test - void testThrowExceptionOrderByTransientPrimitiveSimpleProperty() throws IOException, ODataException { + void testThrowExceptionOrderByTransientPrimitiveSimpleProperty() throws ODataException { final JPAEntityType jpaEntity = getHelper().getJPAEntityType(Person.class); final From target = cb.createQuery().from(getHelper().getEntityType(Person.class)); cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); createOrderByItem("FullName"); final ODataJPAQueryException act = assertThrows(ODataJPAQueryException.class, - () -> cut.createOrderByList(joinTables, uriResource, page)); + () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); assertEquals(HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), act.getStatusCode()); } @Test - void testPagePresentOnlyTopValue() throws IOException, ODataException { + void testPagePresentOnlyTopValue() throws ODataException { page = new JPAODataPage(null, 0, 10, skip); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testPagePresentMaxTopValueNoOrdering() throws IOException, ODataException { + void testPagePresentMaxTopValueNoOrdering() throws ODataException { page = new JPAODataPage(null, 0, Integer.MAX_VALUE, skip); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(0, act.size()); } @Test - void testPagePresentOnlySkipValue() throws IOException, ODataException { + void testPagePresentOnlySkipValue() throws ODataException { page = new JPAODataPage(null, 10, 0, null); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testPageAndTopPresent() throws IOException, ODataException { + void testPageAndTopPresent() throws ODataException { page = new JPAODataPage(null, 10, 0, null); when(top.getValue()).thenReturn(5); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testPageMaxTopValueAndTopPresent() throws IOException, ODataException { + void testPageMaxTopValueAndTopPresent() throws ODataException { page = new JPAODataPage(null, Integer.MAX_VALUE, 0, null); when(top.getValue()).thenReturn(5); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertEquals(4, act.size()); assertEquals(4, act.stream() .filter(Order::isAscending) - .collect(Collectors.toList()).size()); + .toList().size()); assertOrder(act); } @Test - void testOrderByPropertyAndPage() throws IOException, ODataException { + void testOrderByPropertyAndPage() throws ODataException { createOrderByItem("DivisionCode"); page = new JPAODataPage(null, 10, 0, null); when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page); + final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); assertFalse(act.isEmpty()); assertEquals(String.class, act.get(0).getExpression().getJavaType()); @@ -400,14 +413,56 @@ void testOrderByPropertyAndPage() throws IOException, ODataException { assertEquals(5, act.size()); } + @Test + void testOrderByNavigationToOne() throws ODataException { + final JPAAnnotatable annotatable = getHelper().sd.getEntitySet(toOneSourceEntity); + final OrderByItem order = mock(OrderByItem.class); + final Member member = mock(Member.class); + final UriInfoResource orderResourceInfo = mock(UriInfoResource.class); + final UriResourceNavigation navigationProperty = mock(UriResourceNavigation.class); + final EdmNavigationProperty edmNavigationProperty = mock(EdmNavigationProperty.class); + final UriResourcePrimitiveProperty property = mock(UriResourcePrimitiveProperty.class); + final EdmProperty edmProperty = mock(EdmProperty.class); + final From orderByFrom = cb.createQuery().from(getHelper().getEntityType(AssociationOneToOneTarget.class)); + + when(uriResource.getOrderByOption()).thenReturn(orderBy); + + when(orderBy.getText()).thenReturn("ColumnTarget/Source"); + when(orderBy.getOrders()).thenReturn(Collections.singletonList(order)); + when(order.getExpression()).thenReturn(member); + when(member.getResourcePath()).thenReturn(orderResourceInfo); + when(orderResourceInfo.getUriResourceParts()).thenReturn(Arrays.asList(navigationProperty, property)); + + when(navigationProperty.isCollection()).thenReturn(false); + when(navigationProperty.getProperty()).thenReturn(edmNavigationProperty); + when(edmNavigationProperty.getName()).thenReturn("ColumnTarget"); + + when(property.getProperty()).thenReturn(edmProperty); + when(edmProperty.getName()).thenReturn("Source"); + + joinTables.put("AssociationOneToOneSource", toOneSourceTarget); + joinTables.put("ColumnTarget", orderByFrom); + + cut = new JPAOrderByBuilder(annotatable, toOneSourceEntity, toOneSourceTarget, cb, Collections.emptyList()); + final var act = cut.createOrderByList(joinTables, uriResource, null, orderByPaths); + assertNotNull(act); + assertEquals(1, act.size()); + assertEquals(1, orderByPaths.size()); + assertTrue(orderByPaths.stream() + .filter(p -> p.getAlias().equals("Source")) + .findFirst() + .isPresent()); + } + private List createOrderByClause(final Boolean isDescending) { final OrderByItem item = mock(OrderByItem.class); final Member expression = mock(Member.class); final UriInfo uriInfo = mock(UriInfo.class); final List pathParts = new ArrayList<>(); when(item.getExpression()).thenReturn(expression); - if (isDescending != null) + if (isDescending != null) { when(item.isDescending()).thenReturn(isDescending); + } when(expression.getResourcePath()).thenReturn(uriInfo); when(uriInfo.getUriResourceParts()).thenReturn(pathParts); when(uriResource.getOrderByOption()).thenReturn(orderBy); @@ -415,7 +470,7 @@ private List createOrderByClause(final Boolean isDescending) { return pathParts; } - private void createOrderByItem(final String externalName) throws ODataJPAModelException { + private void createOrderByItem(final String externalName) { final List pathParts = createOrderByClause(Boolean.TRUE); final UriResourceProperty part = mock(UriResourcePrimitiveProperty.class); @@ -425,7 +480,7 @@ private void createOrderByItem(final String externalName) throws ODataJPAModelEx createPrimitiveEdmProperty(part, externalName); } - private void createComplexOrderByItem() throws ODataJPAModelException { + private void createComplexOrderByItem() { final List pathParts = createOrderByClause(Boolean.TRUE); final UriResourceProperty complexPart = mock(UriResourceComplexProperty.class); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java index 6e4d2bed3..7d46f33fe 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java @@ -14,6 +14,7 @@ import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,6 +38,7 @@ class TestJPAExpandQueryCreateResult extends TestBase { private JPAExpandJoinQuery cut; private JPAODataSessionContextAccess sessionContext; private JPAODataInternalRequestContext requestContext; + private OData odata; @BeforeEach void setup() throws ODataException, ODataJPAIllegalAccessException { @@ -48,7 +50,8 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); - requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext); + odata = OData.newInstance(); + requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext, odata); requestContext.setUriInfo(new UriInfoDouble(new ExpandItemDouble(targetEntity).getResourcePath())); cut = new JPAExpandJoinQuery(null, helper.getJPAAssociationPath("Organizations", "Roles"), diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunction.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunction.java index 9631cdb6e..03f85458b 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunction.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunction.java @@ -1,18 +1,14 @@ package com.sap.olingo.jpa.processor.core.query; import java.io.IOException; -import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; -import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.EntityTransaction; import jakarta.persistence.Persistence; -import jakarta.persistence.Query; import org.apache.olingo.commons.api.ex.ODataException; import org.junit.jupiter.api.BeforeEach; @@ -21,7 +17,6 @@ import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; -import com.sap.olingo.jpa.processor.core.util.TestHelper; class TestJPAFunction { protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; @@ -29,7 +24,6 @@ class TestJPAFunction { protected static DataSource ds; protected static boolean functionCreated; - protected TestHelper helper; protected Map> headers; @BeforeEach @@ -39,7 +33,6 @@ void setup() { properties.put("jakarta.persistence.nonJtaDataSource", ds); emf = Persistence.createEntityManagerFactory(PUNIT_NAME, properties); emf.getProperties(); - createFunction(); } @Disabled("The segment of an action or of a non-composable function must be the last resource-path segment.") @@ -51,41 +44,9 @@ void testNavigationAfterFunctionNotAllowed() throws IOException, ODataException } @Test - void testFunctionGenerateQueryString() throws IOException, ODataException, SQLException { + void testFunctionGenerateQueryString() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "Siblings(DivisionCode='BE25',CodeID='NUTS2',CodePublisher='Eurostat')"); helper.assertStatus(200); } - - private void createFunction() { - if (!functionCreated) { - final EntityManager em = emf.createEntityManager(); - final EntityTransaction t = em.getTransaction(); - final StringBuilder createSiblingsString = new StringBuilder(); - createSiblingsString.append( - "CREATE FUNCTION \"OLINGO\".\"Siblings\" (\"Publisher\" VARCHAR(10), \"ID\" VARCHAR(10), \"Division\" VARCHAR(10)) "); - createSiblingsString.append( - "RETURNS TABLE(\"CodePublisher\" VARCHAR(10),\"CodeID\" VARCHAR(10),\"DivisionCode\" VARCHAR(10),"); - createSiblingsString.append( - "\"CountryISOCode\" VARCHAR(4), \"ParentCodeID\" VARCHAR(10),\"ParentDivisionCode\" VARCHAR(10),"); - createSiblingsString.append("\"AlternativeCode\" VARCHAR(10),\"Area\" int, \"Population\" BIGINT) "); - createSiblingsString.append("READS SQL DATA "); - createSiblingsString.append("RETURN TABLE( SELECT * FROM \"AdministrativeDivision\" as a WHERE "); - createSiblingsString.append("EXISTS (SELECT \"CodePublisher\" "); - createSiblingsString.append("FROM \"OLINGO\".\"AdministrativeDivision\" as b "); - createSiblingsString.append("WHERE b.\"CodeID\" = \"ID\" "); - createSiblingsString.append("AND b.\"DivisionCode\" = \"Division\" "); - createSiblingsString.append("AND b.\"CodePublisher\" = a.\"CodePublisher\" "); - createSiblingsString.append("AND b.\"ParentCodeID\" = a.\"ParentCodeID\" "); - createSiblingsString.append("AND b.\"ParentDivisionCode\" = a.\"ParentDivisionCode\") "); - createSiblingsString.append("AND NOT( a.\"CodePublisher\" = \"Publisher\" "); - createSiblingsString.append("AND a.\"CodeID\" = \"ID\" "); - createSiblingsString.append("AND a.\"DivisionCode\" = \"Division\" )); "); - t.begin(); - final Query qP = em.createNativeQuery(createSiblingsString.toString()); - qP.executeUpdate(); - t.commit(); - functionCreated = true; - } - } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionSerializer.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionSerializer.java index a7d343f16..88523a699 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionSerializer.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAFunctionSerializer.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; -import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,14 +21,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; -import com.sap.olingo.jpa.processor.core.util.TestHelper; class TestJPAFunctionSerializer { protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; protected static EntityManagerFactory emf; protected static DataSource ds; - protected TestHelper helper; protected Map> headers; @BeforeEach @@ -42,7 +39,7 @@ void setup() { } @Test - void testFunctionReturnsEntityType() throws IOException, ODataException, SQLException { + void testFunctionReturnsEntityType() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "EntityType(A=1250)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -53,7 +50,7 @@ void testFunctionReturnsEntityType() throws IOException, ODataException, SQLExce } @Test - void testFunctionReturnsEntityTypeNull() throws IOException, ODataException, SQLException { + void testFunctionReturnsEntityTypeNull() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "EntityType(A=0)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -61,7 +58,7 @@ void testFunctionReturnsEntityTypeNull() throws IOException, ODataException, SQL } @Test - void testFunctionReturnsEntityTypeCollection() throws IOException, ODataException, SQLException { + void testFunctionReturnsEntityTypeCollection() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "ListOfEntityType(A=1250)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -77,7 +74,7 @@ void testFunctionReturnsEntityTypeCollection() throws IOException, ODataExceptio } @Test - void testFunctionReturnsPrimitiveType() throws IOException, ODataException, SQLException { + void testFunctionReturnsPrimitiveType() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "PrimitiveValue(A=124)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -89,7 +86,7 @@ void testFunctionReturnsPrimitiveType() throws IOException, ODataException, SQLE } @Test - void testFunctionReturnsPrimitiveTypeNull() throws IOException, ODataException, SQLException { + void testFunctionReturnsPrimitiveTypeNull() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "PrimitiveValue(A=0)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -97,7 +94,7 @@ void testFunctionReturnsPrimitiveTypeNull() throws IOException, ODataException, } @Test - void testFunctionReturnsPrimitiveTypeCollection() throws IOException, ODataException, SQLException { + void testFunctionReturnsPrimitiveTypeCollection() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "ListOfPrimitiveValues(A=124)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -111,7 +108,7 @@ void testFunctionReturnsPrimitiveTypeCollection() throws IOException, ODataExcep } @Test - void testFunctionReturnsComplexType() throws IOException, ODataException, SQLException { + void testFunctionReturnsComplexType() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "ComplexType(A=124)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -123,7 +120,7 @@ void testFunctionReturnsComplexType() throws IOException, ODataException, SQLExc } @Test - void testFunctionReturnsComplexTypeNull() throws IOException, ODataException, SQLException { + void testFunctionReturnsComplexTypeNull() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "ComplexType(A=0)", "com.sap.olingo.jpa.processor.core.testobjects"); @@ -131,7 +128,7 @@ void testFunctionReturnsComplexTypeNull() throws IOException, ODataException, SQ } @Test - void testFunctionReturnsComplexTypeCollection() throws IOException, ODataException, SQLException { + void testFunctionReturnsComplexTypeCollection() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, ds, "ListOfComplexType(A='Willi')", "com.sap.olingo.jpa.processor.core.testobjects"); 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 857a8295d..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 @@ -1087,41 +1087,4 @@ void testExpandWithNavigationCountFilter() throws IOException, ODataException { final ObjectNode parent = (ObjectNode) divisions.get(0).get("Parent"); assertNotNull(parent); } - - @Test - void testExpandWithFilterOnCollectionField() throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$expand=SupportEngineers($filter=InhouseAddress/any(p:p/Building eq '2'))"); - helper.assertStatus(200); - final ArrayNode organizations = helper.getValues(); - for(JsonNode organization : organizations) { - if (organization.get("ID").asText().equals("1") || organization.get("ID").asText().equals("2")) { - final ArrayNode supportEngineers = (ArrayNode) organization.get("SupportEngineers"); - assertEquals(1, supportEngineers.size()); - ArrayNode address = (ArrayNode)supportEngineers.get(0).get("InhouseAddress"); - assertEquals(1, address.size()); - assertEquals("2", address.get(0).get("Building").asText()); - } else { - final ArrayNode supportEngineers = (ArrayNode) organization.get("SupportEngineers"); - assertEquals(0, supportEngineers.size()); - } - } - } - - @Test - void testAnyFilterOnExpandWithMultipleHops() throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$expand=AdministrativeInformation/Created/User($filter=Jobs/any(p:p/Id eq '98'))"); - helper.assertStatus(200); - final ArrayNode organizations = helper.getValues(); - for(JsonNode organization : organizations) { - JsonNode user = organization.get("AdministrativeInformation").get("Created").get("User"); - if(organization.get("ID").asText().equals("4")) { - assertNotNull(user); - assertEquals("98", user.get("ID").asText()); - } else { - assert (user instanceof NullNode); - } - } - } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java index 421951d62..3b263b031 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java @@ -13,6 +13,7 @@ import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmProperty; 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.UriInfo; import org.apache.olingo.server.api.uri.UriResource; @@ -27,7 +28,6 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; 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.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; import com.sap.olingo.jpa.processor.core.api.JPAODataContextAccessDouble; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContext; @@ -45,6 +45,7 @@ class TestJPAQueryBuildSelectionPathList extends TestBase { private JPAODataSessionContextAccess sessionContext; private UriInfo uriInfo; private JPAODataInternalRequestContext requestContext; + private OData odata; @BeforeEach void setup() throws ODataException, ODataJPAIllegalAccessException { @@ -55,9 +56,10 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { createHeaders(); sessionContext = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), dataSource, null, null); + odata = mock(OData.class); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); - requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext); + requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext, odata); requestContext.setUriInfo(uriInfo); cut = new JPAJoinQuery(null, requestContext); @@ -238,7 +240,7 @@ void checkSelectNavigationComplexWithSelectPrimitive() throws ODataException { } @Test - void checkSelectContainsVersionEvenSoIgnored() throws ODataApplicationException, ODataJPAModelException { + void checkSelectContainsVersionEvenSoIgnored() throws ODataApplicationException { final List resourcePath = buildUriInfo("BusinessPartnerProtecteds", "BusinessPartnerProtected"); final UriResourcePrimitiveProperty byResource = mock(UriResourcePrimitiveProperty.class); final EdmProperty byProperty = mock(EdmProperty.class); 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 5a651e53d..b67426984 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 @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; @@ -22,6 +23,30 @@ class TestJPAQueryCollection extends TestBase { + @Test + void testSelectOneSimpleCollectionProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations('1')?$select=Comment"); + helper.assertStatus(200); + + final ObjectNode organization = helper.getValue(); + final ArrayNode comment = (ArrayNode) organization.get("Comment"); + assertEquals(2, comment.size()); + } + + @Test + void testSelectOneComplexCollectionProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Persons('99')?$select=InhouseAddress"); + helper.assertStatus(200); + + final ObjectNode organization = helper.getValue(); + final ArrayNode address = (ArrayNode) organization.get("InhouseAddress"); + assertEquals(2, address.size()); + } + @Test void testSelectPropertyAndCollection() throws IOException, ODataException { @@ -184,22 +209,92 @@ void testSelectOnlyOneCollectionDeepComplex() throws IOException, ODataException } @Test - void testSelectOnlyNoCollectionDeepComplex() throws IOException, ODataException { + void testSelectTwoCollectionDeepComplex() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "CollectionDeeps('501')?$select=FirstLevel/SecondLevel/Number"); + "CollectionDeeps('502')?$select=FirstLevel/SecondLevel/Comment,FirstLevel/SecondLevel/Address"); helper.assertStatus(200); final ObjectNode collection = helper.getValue(); final TextNode actId = (TextNode) collection.get("ID"); - assertEquals("501", actId.asText()); + assertEquals("502", actId.asText()); final ObjectNode complex = (ObjectNode) collection.get("FirstLevel"); assertFalse(complex.get("SecondLevel") instanceof NullNode); final ObjectNode second = (ObjectNode) complex.get("SecondLevel"); + final ArrayNode comment = (ArrayNode) second.get("Comment"); + assertEquals(2, comment.size()); + final ArrayNode address = (ArrayNode) second.get("Address"); + assertNotNull(address); + } + + @Test + void testSelectOnlyNoCollectionDeepComplex() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionDeeps('501')?$select=FirstLevel/SecondLevel/Number"); + helper.assertStatus(200); + + final ObjectNode collection = helper.getValue(); + final TextNode actId = (TextNode) collection.get("ID"); + assertEquals("501", actId.asText()); + final ObjectNode second = assertDeepCollectionSecondLevel(collection); final IntNode number = (IntNode) second.get("Number"); assertEquals(-1, number.asInt()); } + @Test + void testSelectOneCollectionPropertyViaDeepPath() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionDeeps('501')/FirstLevel/SecondLevel?$select=Comment"); + helper.assertStatus(200); + + final ObjectNode second = helper.getValue(); + final ArrayNode comment = (ArrayNode) second.get("Comment"); + assertTrue(comment.size() > 0); + } + + @Test + void testSelectTwoCollectionPropertyViaDeepPath() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionDeeps('501')/FirstLevel/SecondLevel?$select=Address,Comment"); + helper.assertStatus(200); + + final ObjectNode second = helper.getValue(); + final ArrayNode comment = (ArrayNode) second.get("Comment"); + assertTrue(comment.size() > 0); + final ArrayNode addresses = (ArrayNode) second.get("Address"); + assertTrue(addresses.size() > 0); + } + + @Test + void testSelectAllCollectionPropertyViaDeepPath() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionDeeps('501')/FirstLevel/SecondLevel"); + helper.assertStatus(200); + + final ObjectNode second = helper.getValue(); + final ArrayNode comment = (ArrayNode) second.get("Comment"); + assertTrue(comment.size() > 0); + final ArrayNode addresses = (ArrayNode) second.get("Address"); + assertTrue(addresses.size() > 0); + } + + @Test + void testSelectOneCollectionPropertyViaPath() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "CollectionDeeps('501')/FirstLevel?$select=SecondLevel/Comment"); + helper.assertStatus(200); + + final ObjectNode first = helper.getValue(); + final ObjectNode secondLevel = (ObjectNode) first.get("SecondLevel"); + final ArrayNode comment = (ArrayNode) secondLevel.get("Comment"); + assertTrue(comment.size() > 0); + } + @Test void testSelectCollectionWithoutRequiredGroup() throws IOException, ODataException { @@ -274,7 +369,7 @@ void testPathWithTransientCollection() throws IOException, ODataException { final ObjectNode complex = helper.getValue(); assertEquals(1, complex.get("LevelID").asInt()); assertFalse(complex.get("SecondLevel") instanceof NullNode); - assertEquals(2, ((ArrayNode) complex.get("TransientCollection")).size()); + assertEquals(2, complex.get("TransientCollection").size()); } @Test @@ -384,4 +479,11 @@ void testSelectCollectionCountComplexPropertySingleton() throws IOException, ODa final ValueNode count = helper.getSingleValue(); assertEquals(1, count.asInt()); } + + private ObjectNode assertDeepCollectionSecondLevel(final ObjectNode collection) { + + final ObjectNode complex = (ObjectNode) collection.get("FirstLevel"); + assertFalse(complex.get("SecondLevel") instanceof NullNode); + return (ObjectNode) complex.get("SecondLevel"); + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java index f04e12b31..727d75de0 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java @@ -23,6 +23,7 @@ import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmProperty; 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.UriInfo; import org.apache.olingo.server.api.uri.UriResource; @@ -53,9 +54,11 @@ class TestJPAQueryFromClause extends TestBase { private JPAAbstractJoinQuery cut; private JPAEntityType jpaEntityType; private JPAODataSessionContextAccess sessionContext; + private OData odata; @BeforeEach void setup() throws ODataException, ODataJPAIllegalAccessException { + odata = mock(OData.class); final UriInfo uriInfo = mock(UriInfo.class); final EdmEntitySet odataEs = mock(EdmEntitySet.class); final EdmEntityType odataType = mock(EdmEntityType.class); @@ -81,7 +84,7 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); when(externalContext.getRequestParameter()).thenReturn(mock(JPARequestParameterMap.class)); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, - sessionContext); + sessionContext, odata); requestContext.setUriInfo(uriInfo); cut = new JPAJoinQuery(null, requestContext); } @@ -159,7 +162,7 @@ void checkFromListDescriptionAssociationAllFields() throws ODataApplicationExcep } @Test - void checkFromListDescriptionAssozationAllFields2() throws ODataApplicationException, ODataJPAModelException, + void checkFromListDescriptionAssociationAllFields2() throws ODataApplicationException, ODataJPAModelException, JPANoSelectionException { final List orderBy = new ArrayList<>(); final List descriptionPathList = new ArrayList<>(); @@ -175,8 +178,7 @@ void checkFromListDescriptionAssozationAllFields2() throws ODataApplicationExcep } @Test - void checkThrowsIfEliminatedByGroups() throws ODataJPAIllegalAccessException, ODataException, - JPANoSelectionException { + void checkThrowsIfEliminatedByGroups() throws ODataJPAIllegalAccessException, ODataException { final JPAODataInternalRequestContext requestContext = buildRequestContextToTestGroups(null); @@ -240,7 +242,7 @@ private JPAODataInternalRequestContext buildRequestContextToTestGroups(final JPA when(externalContext.getGroupsProvider()).thenReturn(Optional.ofNullable(groups)); when(externalContext.getRequestParameter()).thenReturn(mock(JPARequestParameterMap.class)); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, - sessionContext); + sessionContext, odata); requestContext.setUriInfo(uriInfo); return requestContext; } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryHandlesETag.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryHandlesETag.java new file mode 100644 index 000000000..952311afd --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryHandlesETag.java @@ -0,0 +1,78 @@ +package com.sap.olingo.jpa.processor.core.query; + +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.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.commons.api.http.HttpHeader; +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.processor.core.util.IntegrationTestHelper; +import com.sap.olingo.jpa.processor.core.util.TestBase; + +class TestJPAQueryHandlesETag extends TestBase { + + static Stream containsETag() { + return Stream.of( + arguments("Single entity", "Organizations('3')"), + arguments("Single entity eTag not selected", "Organizations('3')?$select=ID"), + arguments("Collection one result", "Organizations?$filter=ID eq '3'")); + + } + + @ParameterizedTest + @MethodSource("containsETag") + void testResultContainsETagHeader(final String test, final String url) throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, url); + helper.assertStatus(200); + assertNotNull(helper.getHeader(HttpHeader.ETAG), test); + assertTrue(helper.getHeader(HttpHeader.ETAG).startsWith("\"")); + assertTrue(helper.getHeader(HttpHeader.ETAG).endsWith("\"")); + } + + @Test + void testResultNotContainsETagHeader() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations"); + helper.assertStatus(200); + assertNull(helper.getHeader(HttpHeader.ETAG)); + } + + @Test + void testIfNoneMatchHeaderNotModified() throws IOException, ODataException { + final Map> headers = new HashMap<>(); + headers.put(HttpHeader.IF_NONE_MATCH, Arrays.asList("\"0\"")); + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations('1')", headers); + helper.assertStatus(304); + assertNotNull(helper.getHeader(HttpHeader.ETAG), "\"0\""); + assertTrue(helper.getRawResult().isBlank()); + } + + @Test + void testIfMatchHeaderPreconditionFailed() throws IOException, ODataException { + final Map> headers = new HashMap<>(); + headers.put(HttpHeader.IF_MATCH, Arrays.asList("\"2\"")); + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations('1')", headers); + helper.assertStatus(412); + assertTrue(helper.getRawResult().isBlank()); + } + + @Test + void testIfMatchHeaderUnknownNotFound() throws IOException, ODataException { + final Map> headers = new HashMap<>(); + headers.put(HttpHeader.IF_MATCH, Arrays.asList("\"2\"")); + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations('1000')", headers); + helper.assertStatus(404); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java index c81c99ded..be91fd1c5 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java @@ -3,16 +3,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import jakarta.persistence.EntityManagerFactory; import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; +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.JPAODataGroupsProvider; +import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; -import com.sap.olingo.jpa.processor.core.util.TestBase; -class TestJPAQueryOrderByClause extends TestBase { +class TestJPAQueryOrderByClause { + protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; + public static final String[] enumPackages = { "com.sap.olingo.jpa.processor.core.testmodel" }; + protected static EntityManagerFactory emf; + protected Map> headers; + protected static JPAEdmNameBuilder nameBuilder; + protected static DataSource dataSource; + + @BeforeAll + public static void setupClass() { + dataSource = DataSourceHelper.createDataSource(DataSourceHelper.DB_HSQLDB); + emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, dataSource); + nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); + } @Test void testOrderByOneProperty() throws IOException, ODataException { @@ -37,16 +60,11 @@ void testOrderByOneComplexPropertyAsc() throws IOException, ODataException { } @Test - void testOrderByOneComplexPropertyDesc() throws IOException, ODataException { + void testOrderByOneComplexPropertyDeepAsc() throws IOException, ODataException { - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$orderby=Address/Region desc"); - if (helper.getStatus() != 200) - System.out.println(helper.getRawResult()); + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$orderby=AdministrativeInformation/Created/By"); helper.assertStatus(200); - - final ArrayNode orgs = helper.getValues(); - assertEquals("US-UT", orgs.get(0).get("Address").get("Region").asText()); - assertEquals("US-CA", orgs.get(9).get("Address").get("Region").asText()); } @Test @@ -248,4 +266,20 @@ void testOrderByOnTransientCollectionProperty() throws IOException, ODataExcepti "CollectionDeeps?$orderby=FirstLevel/TransientCollection/$count asc"); helper.assertStatus(501); } + + @Test + void testOrderByToOneProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AssociationOneToOneSources?$orderby=ColumnTarget/Source asc"); + helper.assertStatus(200); + } + + @Test + void testOrderByToOnePropertyWithCollectionProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "BusinessPartnerRoles?$expand=BusinessPartner($expand=Roles;$select=ID)&$orderby=BusinessPartner/Country asc"); + helper.assertStatus(200); + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java index 23ccec1e0..15469761e 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java @@ -168,7 +168,8 @@ void checkSelectSupertypePropertyTypeName2() throws ODataException, ODataJPAIlle assertContains(selectClause, "Name2"); assertContains(selectClause, "Type"); assertContains(selectClause, "ID"); - assertEquals(3, selectClause.size()); + assertContains(selectClause, "ETag"); + assertEquals(4, selectClause.size()); } @Test @@ -252,8 +253,9 @@ void checkSelectCollectionProperty() throws ODataException, ODataJPAIllegalAcces final List> selectClause = cut.createSelectClause(joinTables, cut.buildSelectionPathList( new UriInfoDouble(new SelectOptionDouble("Comment"))).joinedPersistent(), root, Collections.emptyList()); - assertEquals(1, selectClause.size()); - assertEquals("ID", selectClause.get(0).getAlias()); + assertEquals(2, selectClause.size()); + assertContains(selectClause, "ID"); + assertContains(selectClause, "ETag"); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectWithGroupClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectWithGroupClause.java index 9261ba69e..ed0819cb6 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectWithGroupClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectWithGroupClause.java @@ -12,9 +12,7 @@ import org.apache.olingo.server.api.ODataApplicationException; import org.junit.jupiter.api.Test; -import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataGroupsProvider; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerWithGroups; import com.sap.olingo.jpa.processor.core.util.SelectOptionDouble; import com.sap.olingo.jpa.processor.core.util.TestGroupBase; @@ -23,7 +21,7 @@ class TestJPAQuerySelectWithGroupClause extends TestGroupBase { @Test - void checkSelectAllWithoutGroupReturnsNotAssigned() throws ODataApplicationException, ODataJPAModelException { + void checkSelectAllWithoutGroupReturnsNotAssigned() throws ODataApplicationException { fillJoinTable(root); final List> selectClause = cut.createSelectClause(joinTables, cut.buildSelectionPathList( @@ -36,7 +34,7 @@ void checkSelectAllWithoutGroupReturnsNotAssigned() throws ODataApplicationExcep } @Test - void checkSelectAllWithOneGroupReturnsAlsoThose() throws ODataException, ODataJPAIllegalAccessException { + void checkSelectAllWithOneGroupReturnsAlsoThose() throws ODataException { root = emf.getCriteriaBuilder().createTupleQuery().from(BusinessPartnerWithGroups.class); fillJoinTable(root); final List groups = new ArrayList<>(); @@ -51,7 +49,7 @@ void checkSelectAllWithOneGroupReturnsAlsoThose() throws ODataException, ODataJP } @Test - void checkSelectAllWithTwoGroupReturnsAlsoThose() throws ODataException, ODataJPAIllegalAccessException { + void checkSelectAllWithTwoGroupReturnsAlsoThose() throws ODataException { root = emf.getCriteriaBuilder().createTupleQuery().from(BusinessPartnerWithGroups.class); fillJoinTable(root); final List groups = new ArrayList<>(); @@ -67,7 +65,7 @@ void checkSelectAllWithTwoGroupReturnsAlsoThose() throws ODataException, ODataJP } @Test - void checkSelectTwoWithOneGroupReturnsAll() throws ODataApplicationException, ODataJPAModelException { + void checkSelectTwoWithOneGroupReturnsAll() throws ODataApplicationException { root = emf.getCriteriaBuilder().createTupleQuery().from(BusinessPartnerWithGroups.class); fillJoinTable(root); final List groups = new ArrayList<>(); @@ -84,7 +82,7 @@ void checkSelectTwoWithOneGroupReturnsAll() throws ODataApplicationException, OD } @Test - void checkSelectTwoWithOneGroupReturnsOnlyID() throws ODataApplicationException, ODataJPAModelException { + void checkSelectTwoWithOneGroupReturnsOnlyID() throws ODataApplicationException { root = emf.getCriteriaBuilder().createTupleQuery().from(BusinessPartnerWithGroups.class); fillJoinTable(root); final List groups = new ArrayList<>(); @@ -100,7 +98,7 @@ void checkSelectTwoWithOneGroupReturnsOnlyID() throws ODataApplicationException, } @Test - void checkSelectTwoWithoutGroupReturnsOnlyID() throws ODataApplicationException, ODataJPAModelException { + void checkSelectTwoWithoutGroupReturnsOnlyID() throws ODataApplicationException { root = emf.getCriteriaBuilder().createTupleQuery().from(BusinessPartnerWithGroups.class); fillJoinTable(root); final JPAODataGroupsProvider groups = new JPAODataGroupsProvider(); 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 a037ec8cc..32e587996 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 @@ -17,6 +17,7 @@ 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.Tag; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,6 +28,7 @@ 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.Assertions; import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; import com.sap.olingo.jpa.processor.core.util.TestBase; @@ -91,7 +93,7 @@ void testTopReturnsAllIfToLarge() throws IOException, ODataException { 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 requestContext = new JPAODataInternalRequestContext(externalContext, sessionContext, odata); final var handler = odata.createHandler(odata.createServiceMetadata(sessionContext.getEdmProvider(), new ArrayList<>())); @@ -106,6 +108,16 @@ void testTopReturnsAllIfToLarge() throws IOException, ODataException { assertNull(value.get("@odata.nextLink")); } + @Tag(Assertions.CB_ONLY_TEST) + @Test + void testExpandTopSkipWithoutError() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$skip=5&$top=5&$expand=Children"); + helper.assertStatus(200); + final ObjectNode collection = helper.getValue(); + final ArrayNode act = ((ArrayNode) collection.get("value")); + assertEquals(5, act.size()); + } public String getRawResult(final HttpServletResponse response) throws IOException { final InputStream in = asInputStream(response); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryWithProtection.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryWithProtection.java index bfd6f3fc3..74476f251 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryWithProtection.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryWithProtection.java @@ -42,7 +42,6 @@ import com.fasterxml.jackson.databind.node.ValueNode; import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; @@ -78,6 +77,7 @@ class TestJPAQueryWithProtection extends TestQueryBase { @BeforeEach public void setup() throws ODataException, ODataJPAIllegalAccessException { super.setup(); + contextSpy = spy(context); final JPAEdmProvider providerSpy = spy(context.getEdmProvider()); sdSpy = spy(context.getEdmProvider().getServiceDocument()); @@ -512,7 +512,7 @@ void testAllowAllOnNonStringPropertiesAlsoDouble() throws ODataException, JPANoS } @Test - void testAllowAllOnMultipleClaims() throws ODataException, JPANoSelectionException, ODataJPAQueryException { + void testAllowAllOnMultipleClaims() throws ODataException, JPANoSelectionException { prepareTestDeepProtected(); when(etSpy.getProtections()).thenCallRealMethod(); final JPAODataClaimsProvider claims = new JPAODataClaimsProvider(); @@ -624,14 +624,14 @@ private void prepareTest() throws ODataException, JPANoSelectionException { doReturn(etSpy).when(sdSpy).getEntity("BusinessPartnerProtecteds"); doReturn(etSpy).when(sdSpy).getEntity(odataType); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, - contextSpy); + contextSpy, odata); try { requestContext.setUriInfo(uriInfo); } catch (final ODataJPAIllegalAccessException e) { fail(); } cut = new JPAJoinQuery(null, requestContext); - cut.createFromClause(new ArrayList(1), new ArrayList(), cut.cq, null); + cut.createFromClause(new ArrayList<>(1), new ArrayList<>(), cut.cq, null); } private void prepareTestDeepProtected() throws ODataException, JPANoSelectionException { @@ -649,13 +649,13 @@ private void prepareTestDeepProtected() throws ODataException, JPANoSelectionExc doReturn(etSpy).when(sdSpy).getEntity(odataType); doReturn(null).when(etSpy).getAssociation(""); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, - contextSpy); + contextSpy, odata); try { requestContext.setUriInfo(uriInfo); } catch (final ODataJPAIllegalAccessException e) { fail(); } cut = new JPAJoinQuery(null, requestContext); - cut.createFromClause(new ArrayList(1), new ArrayList(), cut.cq, null); + cut.createFromClause(new ArrayList<>(1), new ArrayList<>(), cut.cq, null); } } 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 2cf30707c..d2cfb0d6c 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 @@ -52,10 +52,10 @@ class TestJPAServerDrivenPaging extends TestBase { @Test - void testReturnsNotImplementedIfPagingProviderNotAvailable() throws IOException, ODataException { + void testReturnsGoneIfPagingProviderNotAvailable() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Organizations?$skiptoken=xyz"); - helper.assertStatus(501); + helper.assertStatus(410); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverter.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverter.java index 43cce74fe..94b2e5c8f 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverter.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverter.java @@ -19,6 +19,7 @@ import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.ValueType; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,6 +49,7 @@ class TestJPATupleChildConverter extends TestBase { private JPAODataRequestContextAccess requestContext; private JPAODataRequestContext context; private JPAODataSessionContextAccess sessionContext; + private OData odata; @BeforeEach void setup() throws ODataException { @@ -60,7 +62,8 @@ void setup() throws ODataException { uriHelper.setKeyPredicates(keyPredicates, "ID"); context = mock(JPAODataRequestContext.class); sessionContext = mock(JPAODataSessionContextAccess.class); - requestContext = new JPAODataInternalRequestContext(context, sessionContext); + odata = mock(OData.class); + requestContext = new JPAODataInternalRequestContext(context, sessionContext, odata); cut = new JPATupleChildConverter(helper.sd, uriHelper, new ServiceMetadataDouble(nameBuilder, "Organization"), requestContext); } @@ -159,7 +162,7 @@ void checkConvertsOneResultsTwoElementsSelectionWithEtag() throws ODataApplicati assertEquals(1, act.getEntities().get(0).getProperties().size()); assertEquals("1", act.getEntities().get(0).getProperties().get(0).getValue()); assertEquals("ID", act.getEntities().get(0).getProperties().get(0).getName()); - assertEquals("2", act.getEntities().get(0).getETag()); + assertEquals("\"2\"", act.getEntities().get(0).getETag()); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverterCompoundKey.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverterCompoundKey.java index 3b45edf1c..b3cc0100a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverterCompoundKey.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPATupleChildConverterCompoundKey.java @@ -14,6 +14,7 @@ import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,6 +42,7 @@ class TestJPATupleChildConverterCompoundKey extends TestBase { private JPAODataRequestContextAccess requestContext; private JPAODataRequestContext context; private JPAODataSessionContextAccess sessionContext; + private OData odata; @BeforeEach void setup() throws ODataException { @@ -50,7 +52,8 @@ void setup() throws ODataException { keyPredicates = new HashMap<>(); context = mock(JPAODataRequestContext.class); sessionContext = mock(JPAODataSessionContextAccess.class); - requestContext = new JPAODataInternalRequestContext(context, sessionContext); + odata = mock(OData.class); + requestContext = new JPAODataInternalRequestContext(context, sessionContext, odata); } @Test diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/UtilityTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/UtilityTest.java index 0a1e1d795..cc0f10900 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/UtilityTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/UtilityTest.java @@ -10,8 +10,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.olingo.commons.api.edm.EdmComplexType; import org.apache.olingo.commons.api.edm.EdmEntitySet; @@ -36,6 +38,9 @@ import org.junit.jupiter.api.Test; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; +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.exception.ODataJPAUtilException; @@ -259,6 +264,63 @@ void testDetermineAssociationsNavigationPathAndStar() throws ODataException { assertEquals(2, act.size()); } + @Test + void testDetermineAssociationsSelectOneProperty() throws ODataException { + final TestHelper helper = getHelper(); + final JPAPath commentPath = mock(JPAPath.class); + final Set selectOptions = new HashSet<>(); + final UriResourceEntitySet es = createEntitySetResource("Organizations"); + final JPACollectionAttribute pathLeaf = mock(JPACollectionAttribute.class); + final EdmEntityType edmType = mock(EdmEntityType.class); + + when(commentPath.getLeaf()).thenReturn(pathLeaf); + when(commentPath.getPath()).thenReturn(Collections.singletonList(pathLeaf)); + when(commentPath.getAlias()).thenReturn("Comment"); + when(es.getType()).thenReturn(edmType); + when(edmType.getNamespace()).thenReturn("com.sap.olingo.jpa"); + when(edmType.getName()).thenReturn("Organization"); + + resourceParts.add(es); + selectOptions.add(commentPath); + + final Map act = Utility.determineAssociations(helper.sd, resourceParts, + selectOptions); + + assertEquals(1, act.size()); + assertNotNull(act.get(commentPath)); + } + + @Test + void testDetermineAssociationsUriPathSelectPath() throws ODataException { + final TestHelper helper = getHelper(); + final Set selectOptions = new HashSet<>(); + final UriResourceEntitySet es = createEntitySetResource("CollectionDeeps"); + final UriResourceComplexProperty cp = createComplexPropertyResource("FirstLevel"); + final EdmEntityType edmType = mock(EdmEntityType.class); + + final JPAAttribute pathParent = mock(JPAAttribute.class); + final JPAPath commentPath = mock(JPAPath.class); + final JPACollectionAttribute pathLeaf = mock(JPACollectionAttribute.class); + + when(commentPath.getLeaf()).thenReturn(pathLeaf); + when(commentPath.getPath()).thenReturn(Arrays.asList(pathParent, pathLeaf)); + when(commentPath.getAlias()).thenReturn("SecondLevel/Comment"); + when(es.getType()).thenReturn(edmType); + when(edmType.getNamespace()).thenReturn("com.sap.olingo.jpa"); + when(edmType.getName()).thenReturn("CollectionDeep"); + + resourceParts.add(es); + resourceParts.add(cp); + selectOptions.add(commentPath); + + final Map act = Utility.determineAssociations(helper.sd, resourceParts, + selectOptions); + + assertEquals(1, act.size()); + assertNotNull(act.get(commentPath)); + assertEquals("FirstLevel/SecondLevel/Comment", act.get(commentPath).getAlias()); + } + private UriResourceNavigation createNavigationResource() { final EdmNavigationProperty property = mock(EdmNavigationProperty.class); final UriResourceNavigation navigation = mock(UriResourceNavigation.class); @@ -269,9 +331,9 @@ private UriResourceNavigation createNavigationResource() { return navigation; } - private UriResourceEntitySet createEntitySetResource(final String string) { + private UriResourceEntitySet createEntitySetResource(final String name) { final EdmEntitySet es = mock(EdmEntitySet.class); - when(es.getName()).thenReturn("Persons"); + when(es.getName()).thenReturn(name); return createEntitySetResource(es); } @@ -283,12 +345,6 @@ private UriResourceEntitySet createEntitySetResource(final EdmEntitySet es) { return resourceItem; } - private UriResourceSingleton createSingletonResource(final String string) { - final EdmSingleton es = mock(EdmSingleton.class); - when(es.getName()).thenReturn("Singleton"); - return createSingletonResource(es); - } - private UriResourceSingleton createSingletonResource(final EdmSingleton es) { final UriResourceSingleton resourceItem = mock(UriResourceSingleton.class); when(resourceItem.getKind()).thenReturn(UriResourceKind.singleton); @@ -296,12 +352,12 @@ private UriResourceSingleton createSingletonResource(final EdmSingleton es) { return resourceItem; } - private UriResourceComplexProperty createComplexPropertyResource(final String string) { + private UriResourceComplexProperty createComplexPropertyResource(final String name) { final EdmProperty property = mock(EdmProperty.class); final EdmComplexType complexType = mock(EdmComplexType.class); - when(property.getName()).thenReturn("AdministrativeInformation"); + when(property.getName()).thenReturn(name); when(complexType.getNamespace()).thenReturn(PUNIT_NAME); - when(complexType.getName()).thenReturn("AdministrativeInformation"); + when(complexType.getName()).thenReturn(name); return createComplexPropertyResource(complexType, property); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java index f61e7e58a..b4d3dffdf 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java @@ -30,16 +30,17 @@ public TestFunctionActionConstructor(final EntityManager em, final JPAHttpHeader @EdmFunction(returnType = @ReturnType(type = Boolean.class), hasFunctionImport = true, isBound = false) public Boolean func(@EdmParameter(name = "date") final LocalDate date) { - return Boolean.TRUE; + return em != null && header != null && parameter != null; } @EdmAction(returnType = @ReturnType) - public void action(@EdmParameter(name = "date") final LocalDate date) { - + public void action(@EdmParameter(name = "date") final LocalDate date) throws Exception { + if (em == null || header == null || parameter == null) + throw new Exception("Missing parameter"); } @EdmFunction(returnType = @ReturnType) public Boolean funcEnum(@EdmParameter(name = "access") final FileAccess value) { - return Boolean.TRUE; + return em != null && header != null && parameter != null; } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionReturnType.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionReturnType.java index 4f599be1b..f8de2ffa3 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionReturnType.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionReturnType.java @@ -84,6 +84,7 @@ public List listOfEntityType(@EdmParameter(name = "A") f public Person convertBirthday() { final Person p = new Person(); p.setID("1"); + p.setETag(3L); p.setBirthDay(LocalDate.now()); p.setInhouseAddress(new ArrayList<>()); return p; diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestJavaActions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestJavaActions.java index 2016f3b0d..4cd73bf55 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestJavaActions.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestJavaActions.java @@ -61,6 +61,14 @@ public BusinessPartnerRole boundWithEntitySetPath( return null; } + @EdmAction(returnType = @ReturnType(type = Person.class), isBound = false) + public Person returnsEntityWithETag() { + final var person = new Person(); + person.setETag(7); + person.setID("Test"); + return person; + } + @EdmAction() public ChangeInformation returnEmbeddable() { return new ChangeInformation(); 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 d0a2e29e1..9a34444a2 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 @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,7 +39,6 @@ import org.mockito.Answers; import org.mockito.ArgumentCaptor; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -62,6 +62,8 @@ public class IntegrationTestHelper { public final HttpServletRequest request; public final HttpServletResponse response; private final ArgumentCaptor captorStatus; + private final ArgumentCaptor captorHeader; + private final ArgumentCaptor captorHeaderValue; public static final String uriPrefix = "http://localhost:8080/Test/Olingo.svc/"; public static final String PUNIT_NAME = "com.sap.olingo.jpa"; @@ -146,6 +148,8 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour final OData odata = OData.newInstance(); String[] packages = TestBase.enumPackages; captorStatus = ArgumentCaptor.forClass(Integer.class); + captorHeader = ArgumentCaptor.forClass(String.class); + captorHeaderValue = ArgumentCaptor.forClass(String.class); this.request = getRequestMock(uriPrefix + urlPath, requestBody == null ? null : new StringBuilder(requestBody.toString()), headers); this.response = getResponseMock(); @@ -160,7 +164,7 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour new ArrayList<>())); final JPAODataInternalRequestContext requestContext = buildRequestContext(localEmf, claims, groups, edmProvider, - sessionContext); + sessionContext, odata); handler.register(new JPAODataRequestProcessor(sessionContext, requestContext)); handler.register(new JPAODataBatchProcessor(sessionContext, requestContext)); @@ -170,16 +174,17 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour public JPAODataInternalRequestContext buildRequestContext(final EntityManagerFactory localEmf, final JPAODataClaimsProvider claims, final JPAODataGroupProvider groups, final JPAEdmProvider edmProvider, - final JPAODataSessionContextAccess sessionContext) throws ODataException { + final JPAODataSessionContextAccess sessionContext, final OData odata) 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)); when(externalContext.getGroupsProvider()).thenReturn(Optional.ofNullable(groups)); when(externalContext.getDebuggerSupport()).thenReturn(new DefaultDebugSupport()); when(externalContext.getRequestParameter()).thenReturn(mock(JPARequestParameterMap.class)); final JPAODataInternalRequestContext requestContext = new JPAODataInternalRequestContext(externalContext, - sessionContext); + sessionContext, odata); return requestContext; } @@ -199,6 +204,14 @@ public int getStatus() { return captorStatus.getValue(); } + public String getHeader(final String name) { + verify(response, atLeastOnce()).addHeader(captorHeader.capture(), captorHeaderValue.capture()); + final var index = captorHeader.getAllValues().indexOf(name); + if (index >= 0) + return captorHeaderValue.getAllValues().get(index); + return null; + } + public String getRawResult() throws IOException { final InputStream in = asInputStream(); final StringBuilder builder = new StringBuilder(); @@ -230,7 +243,7 @@ public InputStream asInputStream() throws IOException { return new ResultStream((OutPutStream) response.getOutputStream()); } - public ArrayNode getValues() throws JsonProcessingException, IOException { + public ArrayNode getValues() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final JsonNode node = mapper.readTree(getRawResult()); if (!(node.get("value") instanceof ArrayNode)) @@ -239,7 +252,7 @@ public ArrayNode getValues() throws JsonProcessingException, IOException { return values; } - public ObjectNode getValue() throws JsonProcessingException, IOException { + public ObjectNode getValue() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final JsonNode value = mapper.readTree(getRawResult()); if (!(value instanceof ObjectNode)) @@ -247,7 +260,7 @@ public ObjectNode getValue() throws JsonProcessingException, IOException { return (ObjectNode) value; } - public ValueNode getSingleValue() throws JsonProcessingException, IOException { + public ValueNode getSingleValue() throws IOException { final ObjectMapper mapper = new ObjectMapper(); final JsonNode value = mapper.readTree(getRawResult()); if (!(value instanceof ValueNode)) @@ -255,7 +268,7 @@ public ValueNode getSingleValue() throws JsonProcessingException, IOException { return (ValueNode) value; } - public ValueNode getSingleValue(final String nodeName) throws JsonProcessingException, IOException { + public ValueNode getSingleValue(final String nodeName) throws IOException { final ObjectMapper mapper = new ObjectMapper(); final JsonNode node = mapper.readTree(getRawResult()); if (!(node.get(nodeName) instanceof ValueNode)) 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 7409c7225..5c11934dc 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 @@ -11,6 +11,7 @@ import org.apache.olingo.server.api.uri.UriResourceProperty; import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmQueryExtensionProvider; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEtagValidator; 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.JPAAttribute; @@ -240,4 +241,10 @@ public Object getAnnotationValue(final String alias, final String term, final St throws ODataJPAModelException { return this.base.getAnnotationValue(alias, term, property); } + + @Override + public JPAEtagValidator getEtagValidator() { + fail(); + return JPAEtagValidator.WEAK; + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java index 30da97fa5..bed52127a 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java @@ -1,5 +1,6 @@ package com.sap.olingo.jpa.processor.core.util; +import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -10,11 +11,11 @@ import jakarta.persistence.EntityManagerFactory; import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; 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.impl.JPADefaultEdmNameBuilder; import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; @@ -29,11 +30,17 @@ public class TestBase { protected static DataSource dataSource; @BeforeAll - public static void setupClass() throws ODataJPAModelException { + public static void setupClass() { dataSource = DataSourceHelper.createDataSource(DataSourceHelper.DB_H2); emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, dataSource); nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); } + + @AfterAll + public static void teardownClass() throws SQLException { + emf.close(); + dataSource.getConnection().close(); + } protected void createHeaders() { headers = new HashMap<>(); 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 5e6ecadc2..658f58cfe 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 @@ -15,6 +15,7 @@ import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceEntitySet; @@ -41,6 +42,7 @@ public class TestGroupBase extends TestBase { protected JPAODataSessionContextAccess context; protected UriInfo uriInfo; protected JPAODataInternalRequestContext requestContext; + protected OData odata; public TestGroupBase() { super(); @@ -59,7 +61,8 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { null, null); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); - requestContext = new JPAODataInternalRequestContext(externalContext, context); + odata = OData.newInstance(); + requestContext = new JPAODataInternalRequestContext(externalContext, context, odata); requestContext.setUriInfo(uriInfo); cut = new JPAJoinQuery(null, requestContext); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java index 26658fbcc..ce3e09962 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java @@ -16,6 +16,7 @@ import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceEntitySet; @@ -43,6 +44,7 @@ public class TestQueryBase extends TestBase { protected UriInfo uriInfo; protected JPAODataInternalRequestContext requestContext; protected JPAODataRequestContext externalContext; + protected OData odata; public TestQueryBase() { super(); @@ -51,6 +53,7 @@ public TestQueryBase() { @BeforeEach public void setup() throws ODataException, ODataJPAIllegalAccessException { buildUriInfo("BusinessPartners", "BusinessPartner"); + odata = mock(OData.class); helper = new TestHelper(emf, PUNIT_NAME); nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); jpaEntityType = helper.getJPAEntityType("BusinessPartners"); @@ -60,7 +63,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { null, null); externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); - requestContext = new JPAODataInternalRequestContext(externalContext, context); + requestContext = new JPAODataInternalRequestContext(externalContext, context, odata); requestContext.setUriInfo(uriInfo); cut = new JPAJoinQuery(null, requestContext); @@ -93,7 +96,7 @@ protected EdmType buildRequestContext(final String esName, final String etName) final EdmType odataType = buildUriInfo(esName, etName); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); - requestContext = new JPAODataInternalRequestContext(externalContext, context); + requestContext = new JPAODataInternalRequestContext(externalContext, context, odata); requestContext.setUriInfo(uriInfo); return odataType; } diff --git a/jpa/odata-jpa-spring-support/pom.xml b/jpa/odata-jpa-spring-support/pom.xml index 8211fd767..378cec316 100644 --- a/jpa/odata-jpa-spring-support/pom.xml +++ b/jpa/odata-jpa-spring-support/pom.xml @@ -4,8 +4,8 @@ com.sap.olingo - odata-jpa - 2.1.3-SNAPSHOT + odata-jpa + 2.1.3 odata-jpa-spring-support @@ -15,4 +15,4 @@ cf-java-logging-support-log4j2 - + diff --git a/jpa/odata-jpa-test/pom.xml b/jpa/odata-jpa-test/pom.xml index 72e2c63f5..4764fb09f 100644 --- a/jpa/odata-jpa-test/pom.xml +++ b/jpa/odata-jpa-test/pom.xml @@ -4,7 +4,7 @@ com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-test odata-jpa-test diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneSource.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneSource.java index db5ab4083..e8cce50cb 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneSource.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneSource.java @@ -27,6 +27,6 @@ public class AssociationOneToOneSource { * Association with a given name. So it is expected that table AssociationOneToOneSource contains a column TARGET. */ @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "target") + @JoinColumn(name = "\"TARGET\"") private AssociationOneToOneTarget columnTarget; } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneTarget.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneTarget.java index e0eb96985..903e00ae7 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneTarget.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/AssociationOneToOneTarget.java @@ -15,6 +15,9 @@ public class AssociationOneToOneTarget { @Column(name = "\"ID\"") protected String iD; + @Column(name = "\"SOURCE\"") + protected String source; + @OneToOne(mappedBy = "defaultTarget", fetch = FetchType.LAZY) private AssociationOneToOneSource defaultSource; diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java index 412f2735d..24627c9b9 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java @@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import db.migration.V1_1__SchemaMigration; + public class DataSourceHelper { private static final String DB_SCHEMA = "OLINGO"; @@ -64,6 +66,9 @@ public static DataSource createDataSource(final int database) { config.schemas(DB_SCHEMA); config.createSchemas(true); + // Prefer additional migration over callback to create UDFs + config.javaMigrations(new V1_1__SchemaMigration()); + config.loggers("auto"); new Flyway(config).migrate(); return config.getDataSource(); } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DeepProtectedExample.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DeepProtectedExample.java index a47f82c38..8a4c25816 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DeepProtectedExample.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DeepProtectedExample.java @@ -1,6 +1,9 @@ package com.sap.olingo.jpa.processor.core.testmodel; +import java.sql.Timestamp; + import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -20,7 +23,8 @@ public class DeepProtectedExample { @Version @Column(name = "\"ETag\"", nullable = false) - private long eTag; + @Convert(converter = TimestampLongConverter.class) + private Timestamp etag; @Column(name = "\"Type\"", length = 1, insertable = false, updatable = false, nullable = false) private String type; @@ -52,8 +56,12 @@ public void setPostalAddress(final AddressDeepThreeProtections postalAddress) { this.postalAddress = postalAddress; } - public long getETag() { - return eTag; + public Timestamp getEtag() { + return etag; + } + + public void setEtag(final Timestamp timestamp) { + etag = timestamp; } @Override @@ -65,15 +73,15 @@ public int hashCode() { } @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final DeepProtectedExample other = (DeepProtectedExample) obj; - if (iD == null) { - if (other.iD != null) return false; - } else if (!iD.equals(other.iD)) return false; - return true; + public boolean equals(final Object object) { + if (object instanceof final DeepProtectedExample other) { + if (iD == null) { + if (other.iD == null) + return true; + } else { + return iD.equals(other.iD); + } + } + return false; } - } 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 94be3ac56..b224eae1d 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 @@ -43,6 +43,7 @@ public class PostalAddressDataWithGroup { @EdmDescriptionAssociation(languageAttribute = "key/language", descriptionAttribute = "name") @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "\"CodePublisher\"", referencedColumnName = "\"Address.RegionCodePublisher\"", insertable = false, updatable = false) @JoinColumn(name = "\"CodeID\"", referencedColumnName = "\"Address.RegionCodeID\"", insertable = false, diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java new file mode 100644 index 000000000..861eca00a --- /dev/null +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java @@ -0,0 +1,27 @@ +package com.sap.olingo.jpa.processor.core.testmodel; + +import java.sql.Timestamp; +import java.time.Instant; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +/** + * Default converter to convert from {@link Long} to {@link java.sql.Timestamp}. + * + * @author Oliver Grande + * @version 1.0.0-RC + */ +@Converter(autoApply = false) +public class TimestampLongConverter implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(final Timestamp instant) { + return instant == null ? null : instant.toInstant().toEpochMilli(); + } + + @Override + public Timestamp convertToEntityAttribute(final Long epochMilliseconds) { + return epochMilliseconds == null ? null : Timestamp.from(Instant.ofEpochMilli(epochMilliseconds)); + } +} \ No newline at end of file diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/User.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/User.java index 10008883e..23151b0af 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/User.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/User.java @@ -1,7 +1,12 @@ package com.sap.olingo.jpa.processor.core.testmodel; +import static jakarta.persistence.EnumType.STRING; + +import java.util.Objects; + import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -19,6 +24,10 @@ public class User { @Column(name = "\"Enabled\"", length = 60) private Boolean enabled; + @Column(name = "\"UserType\"") + @Enumerated(STRING) + private UserType userType; + public String getUsername() { return username; } @@ -50,4 +59,25 @@ public void setEnabled(final Boolean enabled) { public void setUsername(final String username) { setId(username); } + + public UserType getUserType() { + return userType; + } + + public void setUserType(final UserType userType) { + this.userType = userType; + } + + @Override + public int hashCode() { + return Objects.hash(username); + } + + @Override + public boolean equals(final Object object) { + + if (object instanceof final User other) + return Objects.equals(username, other.username); + return false; + } } diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/UserType.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/UserType.java new file mode 100644 index 000000000..3073316cf --- /dev/null +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/UserType.java @@ -0,0 +1,8 @@ +package com.sap.olingo.jpa.processor.core.testmodel; + +import com.sap.olingo.jpa.metadata.core.edm.annotation.EdmEnumeration; + +@EdmEnumeration +public enum UserType { + BATCH, INTERACTIVE; +} 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 0e1a15b81..18e602fdb 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(35), - NO_ENTITY_SETS(33), + NO_ENTITY_TYPES(36), + NO_ENTITY_SETS(34), NO_SINGLETONS(3), NO_COMPLEXT_TYPES(25); diff --git a/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java b/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java new file mode 100644 index 000000000..a6f687e00 --- /dev/null +++ b/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java @@ -0,0 +1,162 @@ +package db.migration; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.flywaydb.core.internal.database.DatabaseType; + +/** + * + * + * + * Created 2024-06-09 + * @author Oliver Grande + * @since 2.1.2 + * + */ +public class V1_1__SchemaMigration extends BaseJavaMigration { // NOSONAR + + @Override + public void migrate(final Context context) throws Exception { + final var configuration = context.getConfiguration(); + final var connection = context.getConnection(); + final DatabaseType dbType = configuration.getDatabaseType(); + final List> preparedStatements = switch (dbType.getName()) { + case "H2" -> createFunctionH2(); + case "SAP HANA" -> createFunctionHANA(connection); + case "HSQLDB" -> createFunctionHSQLDB(connection); + case "PostgreSQL" -> createFunctionPostgres(connection); + default -> raiseUnsupportedDbException(dbType); + + }; + + for (final var preparedStatement : preparedStatements) { + if (preparedStatement.isPresent()) { + preparedStatement.get().execute(); + preparedStatement.get().close(); + } + } + } + + private List> raiseUnsupportedDbException(final DatabaseType dbType) { + throw new IllegalArgumentException("No migration for database of type: " + dbType.getName()); + } + + private List> createFunctionPostgres(final Connection connection) throws SQLException { + final String sql = + """ + CREATE OR REPLACE FUNCTION "OLINGO"."Siblings"("CodePublisher" character varying, "CodeID" character varying, "DivisionCode" character varying) + RETURNS SETOF "OLINGO"."AdministrativeDivision" + LANGUAGE sql + AS $function$ + SELECT "CodePublisher", "CodeID", "DivisionCode", "CountryISOCode", "ParentCodeID", "ParentDivisionCode", "AlternativeCode", "Area", "Population" + FROM "OLINGO"."AdministrativeDivision" as kids + WHERE ("CodePublisher", "ParentCodeID", "ParentDivisionCode") IN + (SELECT "CodePublisher", "ParentCodeID", "ParentDivisionCode" + FROM "OLINGO"."AdministrativeDivision" + WHERE "CodePublisher" = $1 + AND "CodeID" = $2 + AND "DivisionCode" = $3 ) + AND kids."DivisionCode" <> $3; + $function$ + ; + """; + return Collections.singletonList(Optional.of(connection.prepareStatement(sql))); + } + + private List> createFunctionHSQLDB(final Connection connection) throws SQLException { + + final String sqlSiblings = + """ + CREATE FUNCTION "OLINGO"."Siblings" ("Publisher" VARCHAR(10), "ID" VARCHAR(10), "Division" VARCHAR(10)) + RETURNS TABLE( + "CodePublisher" VARCHAR(10), + "CodeID" VARCHAR(10), + "DivisionCode" VARCHAR(10), + "CountryISOCode" VARCHAR(4), + "ParentCodeID" VARCHAR(10), + "ParentDivisionCode" VARCHAR(10), + "AlternativeCode" VARCHAR(10), + "Area" int, + "Population" BIGINT) + READS SQL DATA + RETURN TABLE( SELECT "CodePublisher", "CodeID", "DivisionCode", "CountryISOCode", "ParentCodeID", "ParentDivisionCode", "AlternativeCode", "Area", "Population" + FROM "AdministrativeDivision" as a + WHERE + EXISTS (SELECT "CodePublisher" + FROM "AdministrativeDivision" as b + WHERE b."CodeID" = "ID" + AND b."DivisionCode" = "Division" + AND b."CodePublisher" = a."CodePublisher" + AND b."ParentCodeID" = a."ParentCodeID" + AND b."ParentDivisionCode" = a."ParentDivisionCode") + AND NOT( a."CodePublisher" = "Publisher" + AND a."CodeID" = "ID" + AND a."DivisionCode" = "Division" ) + ); + """; + final String sqlPopulationDensity = """ + CREATE FUNCTION "OLINGO"."PopulationDensity" (UnitArea BIGINT, Population BIGINT ) + RETURNS DOUBLE + BEGIN ATOMIC + DECLARE areaDouble DOUBLE; + DECLARE populationDouble DOUBLE; + SET areaDouble = UnitArea; + SET populationDouble = Population; + IF UnitArea <= 0 THEN + RETURN 0; + ELSE + RETURN populationDouble / areaDouble; + END IF; + END + """; + final String sqlConvertToQkm = """ + CREATE FUNCTION "OLINGO"."ConvertToQkm" (UnitArea BIGINT ) + RETURNS INT + IF UnitArea <= 0 THEN RETURN 0; + ELSE RETURN UnitArea / 1000 / 1000; + END IF + """; + + return Arrays.asList( + Optional.of(connection.prepareStatement(sqlSiblings)), + Optional.of(connection.prepareStatement(sqlConvertToQkm)), + Optional.of(connection.prepareStatement(sqlPopulationDensity))); + } + + private List> createFunctionHANA(final Connection connection) throws SQLException { + final String sql = + """ + CREATE FUNCTION "OLINGO"."Siblings" ("CodePublisher" VARCHAR(10), "CodeID" VARCHAR(10), "DivisionCode" VARCHAR(10)) + RETURNS TABLE ( + "CodePublisher" VARCHAR(10), "CodeID" VARCHAR(10), "DivisionCode" VARCHAR(10), + "CountryISOCode" VARCHAR(4), "ParentCodeID" VARCHAR(10), "ParentDivisionCode" VARCHAR(10), + "AlternativeCode" VARCHAR(10), "Area" BIGINT, "Population" BIGINT) + LANGUAGE SQLSCRIPT AS + BEGIN + RETURN SELECT "CodePublisher", "CodeID", "DivisionCode", "CountryISOCode", "ParentCodeID", "ParentDivisionCode", "AlternativeCode", "Area", "Population" + FROM "OLINGO"."AdministrativeDivision" as kids + WHERE ("CodePublisher", "ParentCodeID", "ParentDivisionCode") IN + (SELECT "CodePublisher", "ParentCodeID", "ParentDivisionCode" + FROM "OLINGO"."AdministrativeDivision" + WHERE "CodePublisher" = :"CodePublisher" + AND "CodeID" = :"CodeID" + AND "DivisionCode" = :"DivisionCode" ) + AND kids."DivisionCode" <> :"DivisionCode"; + END; + """; + return Arrays.asList(Optional.of(connection.prepareStatement(sql))); + } + + private List> createFunctionH2() { + return Collections.emptyList(); + } + +} 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 cfd4cefd7..958e82f02 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 @@ -79,6 +79,7 @@ com.sap.olingo.jpa.metadata.converter.TimeInstantLongConverter com.sap.olingo.jpa.metadata.converter.OffsetDateTimeConverter com.sap.olingo.jpa.processor.core.testmodel.LocalDateTimeConverter + com.sap.olingo.jpa.processor.core.testmodel.TimestampLongConverter com.sap.olingo.jpa.processor.core.testmodel.TransientRefComplex com.sap.olingo.jpa.processor.core.testmodel.TransientRefIgnore com.sap.olingo.jpa.processor.core.testmodel.TemporalWithValidityPeriodKey @@ -92,17 +93,18 @@ com.sap.olingo.jpa.processor.core.testmodel.AnnotationsParent com.sap.olingo.jpa.processor.core.testmodel.AnnotationsSingleton com.sap.olingo.jpa.processor.core.testmodel.DetailSettings + com.sap.olingo.jpa.processor.core.testmodel.User true - + + - @@ -146,9 +148,9 @@ - - - + + + 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 9208dc332..745159a35 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 @@ -850,9 +850,10 @@ CREATE TABLE "User" ( "UserName" VARCHAR(60) NOT NULL , "Password" VARCHAR(60), "Enabled" BOOLEAN, + "UserType" VARCHAR(20), PRIMARY KEY ("UserName")); -insert into "User" values ('Willi', '$2a$10$ekL4q.jeDmuc2AhZF/ARUe2KTMczEBHZlML.bN985noWuJcdilbg6', true); -insert into "User" values ('Marvin', '$2a$10$dPD0o8lEbOy0vYtpWkE78.vVBKWElJjiezkFo1nr6hG3EBRx4Gpl.', true); +insert into "User" values ('Willi', '$2a$10$ekL4q.jeDmuc2AhZF/ARUe2KTMczEBHZlML.bN985noWuJcdilbg6', true, 'INTERACTIVE'); +insert into "User" values ('Marvin', '$2a$10$dPD0o8lEbOy0vYtpWkE78.vVBKWElJjiezkFo1nr6hG3EBRx4Gpl.', true, 'INTERACTIVE'); CREATE TABLE "CountryRestriction" ( "UserName" VARCHAR(60) NOT NULL , @@ -1016,4 +1017,3 @@ CREATE TABLE "DummyToBeIgnored" ( -- AND a."CodeID" = "ID" -- AND a."DivisionCode" = "Division" ) -- ); - diff --git a/jpa/odata-jpa-vocabularies/pom.xml b/jpa/odata-jpa-vocabularies/pom.xml index f9881fecc..98e3922c6 100644 --- a/jpa/odata-jpa-vocabularies/pom.xml +++ b/jpa/odata-jpa-vocabularies/pom.xml @@ -3,7 +3,7 @@ com.sap.olingo odata-jpa - 2.1.3-SNAPSHOT + 2.1.3 odata-jpa-vocabularies odata-jpa-vocabularies diff --git a/jpa/pom.xml b/jpa/pom.xml index cb786d800..e93c2d3a8 100644 --- a/jpa/pom.xml +++ b/jpa/pom.xml @@ -1,376 +1,377 @@ - - 4.0.0 - com.sap.olingo - odata-jpa - 2.1.3-SNAPSHOT - pom - odata-jpa - https://github.com/SAP/olingo-jpa-processor-v4 - - UTF-8 - 17 - 17 - 3.13.0 - 5.0.0 - 2.17.1 - 1.7.1 - 4.3.0 - 3.8.3 - 10.15.0 - 6.1.0 - 4.0.1 - 3.1.0 - ${project.version} - 6.1.10 - 4.0.3 - 6.4.0.Final - 3.2.2 - 5.10.2 - 1.10.2 - 5.12.0 - 0.8.12 - 4.1.111.Final - - ${project.basedir}/odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml, - ${project.basedir}/../odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml - - - - - build.fast - - true - true - true - .*(proxy|client).* - - - - build.quality - - false - - - - - - - odata-jpa-metadata - odata-jpa-test - odata-jpa-annotation - odata-jpa-processor - odata-jpa-processor-cb - odata-jpa-processor-parallel - odata-jpa-coverage - odata-jpa-spring-support - odata-jpa-processor-ext - odata-jpa-vocabularies - odata-jpa-odata-vocabularies - - - - - ${project.groupId} - odata-jpa-metadata - ${project.version} - - - ${project.groupId} - odata-jpa-test - ${project.version} - test - - - ${project.groupId} - odata-jpa-annotation - ${project.version} - - - ${project.groupId} - odata-jpa-processor - ${project.version} - - - ${project.groupId} - odata-jpa-processor-cb - ${project.version} - - - ${project.groupId} - odata-jpa-processor-parallel - ${project.version} - - - ${project.groupId} - odata-jpa-coverage - ${project.version} - - - ${project.groupId} - odata-jpa-spring-support - ${project.version} - - - ${project.groupId} - odata-jpa-processor-ext - ${project.version} - - - ${project.groupId} - odata-jpa-vocabularies - ${project.version} - - - ${project.groupId} - odata-jpa-odata-vocabularies - ${project.version} - - - - org.apache.olingo - odata-server-api - ${odata.version} - - - org.apache.olingo - odata-server-core - ${odata.version} - - - org.apache.olingo - odata-commons-api - ${odata.version} - - - - org.hsqldb - hsqldb - 2.7.3 - - - com.h2database - h2 - 2.2.224 - - - org.flywaydb - flyway-core - ${flyway.version} - - - org.flywaydb - flyway-database-hsqldb - ${flyway.version} - - - org.apache.openjpa - openjpa-all - ${openjpa} - - - org.eclipse.persistence - org.eclipse.persistence.jpa - ${eclipseLink.version} - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - - - jakarta.persistence - jakarta.persistence-api - ${jpa.version} - - - jakarta.servlet - jakarta.servlet-api - ${jakarta.version} - - - jakarta.transaction - jakarta.transaction-api - 2.0.1 - - - org.springframework - spring-jcl - ${spring-jcl.version} - - - org.apache.commons - commons-lang3 - 3.14.0 - - - org.slf4j - slf4j-api - 2.0.13 - - - org.slf4j - slf4j-simple - 2.0.13 - - - com.sap.hcp.cf.logging - cf-java-logging-support-log4j2 - ${cf-logging.version} - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - com.fasterxml.jackson - jackson-bom - ${jackson.version} - pom - import - - - io.netty - netty-codec-http - ${netty.version} - - - net.oneandone.reflections8 - reflections8 - 0.11.7 - - - org.junit.jupiter - junit-jupiter - ${junit.version} - - - org.junit.platform - junit-platform-launcher - ${junit-platform.version} - - - org.mockito - mockito-core - ${mockito.version} - - - com.tngtech.archunit - archunit-junit5 - 1.3.0 - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.3.0 - - - - junit.platform.output.capture.stdout = false - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven.compiler.version} - - ${project.build.source} - ${project.build.target} - true - true - -Xlint:unchecked - ${project.build.sourceEncoding} - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - 4.0.0.4121 - - - org.basepom.maven - duplicate-finder-maven-plugin - 2.0.1 - - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - org.eluder.coveralls - coveralls-maven-plugin - ${coveralls.version} - - - org.pitest - pitest-maven - 1.16.1 - - - org.pitest - pitest-junit5-plugin - 1.2.1 - - - - - org.codehaus.mojo - versions-maven-plugin - 2.16.2 - - - org.apache.commons:commons-collections4 - - - - - - - - org.jacoco - jacoco-maven-plugin - - - prepare-agent - - prepare-agent - - - - - - - SOURCEFILE - - *src/test/* - - - - - - - org.basepom.maven - duplicate-finder-maven-plugin - - - default - verify - - check - - - - - - + + 4.0.0 + com.sap.olingo + odata-jpa + 2.1.3 + pom + odata-jpa + https://github.com/SAP/olingo-jpa-processor-v4 + + + UTF-8 + 17 + 17 + 3.13.0 + 5.0.0 + 2.16.0 + 1.7.1 + 4.3.0 + 3.8.3 + 10.15.0 + 6.1.0 + 4.0.1 + 3.1.0 + ${project.version} + 6.1.10 + 4.0.3 + 6.4.0.Final + 3.2.2 + 5.10.2 + 1.10.2 + 5.12.0 + 0.8.11 + 4.1.111.Final + + ${project.basedir}/odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml, + ${project.basedir}/../odata-jpa-coverage/target/site/jacoco-aggregate/jacoco.xml + + + + + build.fast + + true + true + true + .*(proxy|client).* + + + + build.quality + + false + + + + + + + odata-jpa-metadata + odata-jpa-test + odata-jpa-annotation + odata-jpa-processor + odata-jpa-processor-cb + odata-jpa-processor-parallel + odata-jpa-coverage + odata-jpa-spring-support + odata-jpa-processor-ext + odata-jpa-vocabularies + odata-jpa-odata-vocabularies + + + + + ${project.groupId} + odata-jpa-metadata + ${project.version} + + + ${project.groupId} + odata-jpa-test + ${project.version} + test + + + ${project.groupId} + odata-jpa-annotation + ${project.version} + + + ${project.groupId} + odata-jpa-processor + ${project.version} + + + ${project.groupId} + odata-jpa-processor-cb + ${project.version} + + + ${project.groupId} + odata-jpa-processor-parallel + ${project.version} + + + ${project.groupId} + odata-jpa-coverage + ${project.version} + + + ${project.groupId} + odata-jpa-spring-support + ${project.version} + + + ${project.groupId} + odata-jpa-processor-ext + ${project.version} + + + ${project.groupId} + odata-jpa-vocabularies + ${project.version} + + + ${project.groupId} + odata-jpa-odata-vocabularies + ${project.version} + + + + org.apache.olingo + odata-server-api + ${odata.version} + + + org.apache.olingo + odata-server-core + ${odata.version} + + + org.apache.olingo + odata-commons-api + ${odata.version} + + + + org.hsqldb + hsqldb + 2.7.3 + + + com.h2database + h2 + 2.2.224 + + + org.flywaydb + flyway-core + ${flyway.version} + + + org.flywaydb + flyway-database-hsqldb + ${flyway.version} + + + org.apache.openjpa + openjpa-all + ${openjpa} + + + org.eclipse.persistence + org.eclipse.persistence.jpa + ${eclipseLink.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.version} + + + jakarta.persistence + jakarta.persistence-api + ${jpa.version} + + + jakarta.servlet + jakarta.servlet-api + ${jakarta.version} + + + jakarta.transaction + jakarta.transaction-api + 2.0.1 + + + org.springframework + spring-jcl + ${spring-jcl.version} + + + org.apache.commons + commons-lang3 + 3.14.0 + + + org.slf4j + slf4j-api + 2.0.9 + + + org.slf4j + slf4j-simple + 2.0.13 + + + com.sap.hcp.cf.logging + cf-java-logging-support-log4j2 + ${cf-logging.version} + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + + + io.netty + netty-codec-http + ${netty.version} + + + net.oneandone.reflections8 + reflections8 + 0.11.7 + + + org.junit.jupiter + junit-jupiter + ${junit.version} + + + org.junit.platform + junit-platform-launcher + ${junit-platform.version} + + + org.mockito + mockito-core + ${mockito.version} + + + com.tngtech.archunit + archunit-junit5 + 1.3.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.3.0 + + + + junit.platform.output.capture.stdout = false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${project.build.source} + ${project.build.target} + true + true + -Xlint:unchecked + ${project.build.sourceEncoding} + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 4.0.0.4121 + + + org.basepom.maven + duplicate-finder-maven-plugin + 2.0.1 + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + org.eluder.coveralls + coveralls-maven-plugin + ${coveralls.version} + + + org.pitest + pitest-maven + 1.16.1 + + + org.pitest + pitest-junit5-plugin + 1.2.1 + + + + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + + + org.apache.commons:commons-collections4 + + + + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + + + + SOURCEFILE + + *src/test/* + + + + + + + org.basepom.maven + duplicate-finder-maven-plugin + + + default + verify + + check + + + + + +