[ 
https://issues.apache.org/jira/browse/CAMEL-25575?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-25575.
---------------------------------
    Resolution: Fixed

Fixed by https://github.com/apache/camel/pull/27759 (merged to main as 
75ddf4853b21, 4.24.0).

_Claude Code on behalf of Claus Ibsen_

> camel-dropbox - search returns only the first page of the matches (100 by 
> default) although its javadoc promises all files found; the rest is dropped 
> silently (get of a folder: first page of the listing)
> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25575
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25575
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-dropbox
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>             Fix For: 4.24.0
>
>
> *{{search}}* is the main case. {{DropboxAPIFacade.search}} (lines 245-259 at 
> main c3829069cb72), used by the {{dropbox:search}} producer and consumer:
> {code:java}
> SearchV2Result listing = client.files().searchV2Builder(query)
>         .withOptions(SearchOptions.newBuilder().withPath(remotePath).build())
>         .start();
> return new DropboxSearchResult(listing.getMatches());
> {code}
> The request does not set {{max_results}}, and {{SearchOptions}} of 
> dropbox-core-sdk 7.1.1 uses 100 then (the constant in its no-argument 
> constructor, read in the jar). {{SearchV2Result}} has {{getHasMore()}} and 
> {{getCursor()}} for the next page ({{searchContinueV2(cursor)}}); the facade 
> reads neither. So with the default options a search with more matches than 
> one page returns the first page only: the body, and the {{FOUND_FILES}} 
> header that is built from it, lack the rest. Nothing is logged and the 
> exchange succeeds. The javadoc of the method says "@return a result object 
> containing all the files found." (line 242), and the component has no option 
> or header to ask for the next page, so a route cannot get the rest.
> *{{get}} of a folder* is the lesser case ({{downloadFilesInFolder}}, lines 
> 307-318), used by the {{dropbox:get}} producer and consumer:
> {code:java}
> ListFolderResult folderResult = client.files().listFolder(path.equals("/") ? 
> "" : path);
> for (Metadata entry : folderResult.getEntries()) { ... download ... }
> {code}
> {{ListFolderResult}} has {{getHasMore()}} and {{getCursor()}} as well 
> ({{listFolderContinue(cursor)}}). The facade downloads the files of the first 
> response only, although the javadoc says "Get the content of every file 
> inside the remote path" (line 296). It needs a folder larger than one page of 
> the listing; that size is decided by the service (the request sets no limit) 
> and I did not measure it. {{get}} keeps the content of every file in memory, 
> so it is not the operation for a very large folder anyway.
> Not verified (nothing was run against Dropbox): the page size the service 
> really uses for a search (100 is what the SDK asks for), whether the service 
> limits the number of matches a search can return in total, and the page size 
> of a folder listing.
> h3. Reproduction
> New {{DropboxAPIFacadePagingTest}} (3 tests): the Dropbox client is a Mockito 
> mock that answers a search and a folder listing in three pages (page, 
> {{has_more}}, cursor; the fake fails the test after 10 continue requests). On 
> main 2 tests fail:
> * {{searchReturnsTheMatchesOfEveryPage}}: {{the matches of the three pages, 
> each once and in order ==> expected: <[/docs/report-1.txt, 
> /docs/report-2.txt, /docs/report-3.txt, /docs/report-4.txt, 
> /docs/report-5.txt]> but was: <[/docs/report-1.txt, /docs/report-2.txt]>}}.
> * {{getDownloadsTheFilesOfEveryPageOfTheFolder}}: {{the files of the three 
> pages, each once and in order ==> expected: <[/docs/a.txt, /docs/b.txt, 
> /docs/c.txt, /docs/d.txt]> but was: <[/docs/a.txt, /docs/b.txt]>}}.
> The third, {{resultThatFitsInOnePageNeedsNoFurtherRequest}}, passes on main 
> as well; it is a guard that a result of one page sends no continue request (a 
> folder listing returns a cursor with its last page too).
> h3. Proposed fix
> In {{DropboxAPIFacade}}: {{search}} calls {{searchContinueV2}} with the 
> cursor of the response while {{getHasMore()}} is true (and a cursor is 
> present) and returns the matches of all pages; {{downloadFilesInFolder}} 
> calls {{listFolderContinue}} the same way and downloads the files of each 
> page. The order is the order of the service. {{mockito-core}} is added to the 
> module as a test dependency.
> Behaviour change, with a 4.24 upgrade-guide entry: a search returns the 
> matches of every page, and {{get}} of a large folder downloads all its files. 
> Consequences that a user can notice:
> * a search with many matches sends one request per page instead of one 
> request; the search consumer does so at every poll;
> * when a request for a later page fails, the operation fails and the matches 
> that were already read are not returned. That is as before for a failed 
> search, but there are now more requests that can fail;
> * the number of pages is decided by the service ({{has_more}}); the facade 
> has no limit of its own;
> * {{get}} keeps the content of all files in the message body, as before, so a 
> large folder needs more memory than when only the first page was read.
> No new option. camel-dropbox unit tests with the fix: 4, 0 failures (the 
> integration tests need a Dropbox account and were not run).
> Found with a Lean 4 model of a token-paged service: the collection is a list, 
> a response is a {{take}} / {{drop}} slice of it plus a token when items 
> remain. Property: the result is the collection, every item once and in order. 
> Counterexample for "first page only": with a page of two, {{[1, 2, 3]}} gives 
> {{[1, 2]}} ({{cex_third_match_dropped}}); with a page of 100, 101 matches 
> give 100 ({{cex_101_matches}}); {{main_complete_iff}}: the first page is the 
> whole result exactly when the result fits in one page. In the model, 
> following the token returns the whole list ({{fix_meets_spec}}) and equals 
> main's result when it fits in one page ({{fix_eq_main_one_page}}). The model 
> says nothing about the real service. Then confirmed with the real class as 
> described.
> Affected: main (4.23.0-SNAPSHOT at c3829069cb72), and the release branches 
> camel-4.18.x (c3fd5317a5a0), camel-4.22.x (2821f284056c) and camel-4.23.x 
> (197e70656a41): {{DropboxAPIFacade}} has the same lines on all three (javadoc 
> at 242 and 296, {{searchV2Builder}} at 252, {{listFolder}} at 309, no 
> {{getHasMore}} / {{getCursor}} / continue call in the file). Read, not run.
> Duplicate check (2026-10-12): JIRA text "dropbox search has_more" (2, 
> unrelated: CAMEL-13025, CAMEL-12955), "dropbox search results truncated" (1, 
> unrelated: CAMEL-12955), "dropbox listFolder" (none); GitHub pull requests 
> "dropbox search": none about the result of a search; no open pull request 
> touches camel-dropbox.
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to