Skip to content

Commit b6e5d96

Browse files
authored
Add `toUrl() to Search API
1 parent a5e275e commit b6e5d96

File tree

3 files changed

+98
-28
lines changed

3 files changed

+98
-28
lines changed

cloudinary-core/src/main/java/com/cloudinary/Search.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.cloudinary;
22

33
import com.cloudinary.api.ApiResponse;
4+
import com.cloudinary.utils.Base64Coder;
45
import com.cloudinary.utils.ObjectUtils;
6+
import com.cloudinary.utils.StringUtils;
7+
import org.cloudinary.json.JSONObject;
58

9+
import java.nio.charset.StandardCharsets;
610
import java.util.ArrayList;
711
import java.util.Arrays;
812
import java.util.HashMap;
@@ -16,6 +20,8 @@ public class Search {
1620
private ArrayList<String> withFieldParam;
1721
private HashMap<String, Object> params;
1822

23+
private int ttl = 300;
24+
1925
Search(Cloudinary cloudinary) {
2026
this.api = cloudinary.api();
2127
this.params = new HashMap<String, Object>();
@@ -24,6 +30,10 @@ public class Search {
2430
this.withFieldParam = new ArrayList<String>();
2531
}
2632

33+
public Search ttl(int ttl) {
34+
this.ttl = ttl;
35+
return this;
36+
}
2737
public Search expression(String value) {
2838
this.params.put("expression", value);
2939
return this;
@@ -68,14 +78,51 @@ public Search sortBy(String field, String dir) {
6878

6979
public HashMap<String, Object> toQuery() {
7080
HashMap<String, Object> queryParams = new HashMap<String, Object>(this.params);
71-
queryParams.put("with_field", withFieldParam);
72-
queryParams.put("sort_by", sortByParam);
73-
queryParams.put("aggregate", aggregateParam);
81+
if (withFieldParam.size() > 0) {
82+
queryParams.put("with_field", withFieldParam);
83+
}
84+
if(sortByParam.size() > 0) {
85+
queryParams.put("sort_by", sortByParam);
86+
}
87+
if(aggregateParam.size() > 0) {
88+
queryParams.put("aggregate", aggregateParam);
89+
}
7490
return queryParams;
7591
}
7692

7793
public ApiResponse execute() throws Exception {
7894
Map<String, String> options = ObjectUtils.asMap("content_type", "json");
7995
return this.api.callApi(Api.HttpMethod.POST, Arrays.asList("resources", "search"), this.toQuery(), options);
8096
}
97+
98+
99+
public String toUrl() throws Exception {
100+
return toUrl(null, null);
101+
}
102+
103+
public String toUrl(String nextCursor) throws Exception {
104+
return toUrl(null, nextCursor);
105+
}
106+
/***
107+
Creates a signed Search URL that can be used on the client side.
108+
***/
109+
public String toUrl(Integer ttl, String nextCursor) throws Exception {
110+
String nextCursorParam = nextCursor;
111+
String apiSecret = api.cloudinary.config.apiSecret;
112+
if (apiSecret == null) throw new IllegalArgumentException("Must supply api_secret");
113+
if(ttl == null) {
114+
ttl = this.ttl;
115+
}
116+
HashMap queryParams = toQuery();
117+
if(nextCursorParam == null) {
118+
nextCursorParam = (String) queryParams.get("next_cursor");
119+
}
120+
queryParams.remove("next_cursor");
121+
JSONObject json = ObjectUtils.toJSON(queryParams);
122+
String base64Query = Base64Coder.encodeURLSafeString(json.toString());
123+
String signature = StringUtils.encodeHexString(Util.hash(String.format("%d%s%s", ttl, base64Query, apiSecret), SignatureAlgorithm.SHA256));
124+
String prefix = Url.unsignedDownloadUrlPrefix(null,api.cloudinary.config);
125+
126+
return String.format("%s/search/%s/%d/%s%s", prefix, signature, ttl, base64Query,nextCursorParam != null && !nextCursorParam.isEmpty() ? "/"+nextCursorParam : "");
127+
}
81128
}

cloudinary-core/src/main/java/com/cloudinary/Url.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,6 @@ public String generate() {
339339
}
340340

341341
public String generate(String source) {
342-
343342
boolean useRootPath = this.config.useRootPath;
344343
if (this.useRootPath != null) {
345344
useRootPath = this.useRootPath;
@@ -405,8 +404,7 @@ public String generate(String source) {
405404
String resourceType = this.resourceType;
406405
if (resourceType == null) resourceType = "image";
407406
String finalResourceType = finalizeResourceType(resourceType, type, urlSuffix, useRootPath, config.shorten);
408-
String prefix = unsignedDownloadUrlPrefix(source, config.cloudName, config.privateCdn, config.cdnSubdomain, config.secureCdnSubdomain, config.cname, config.secure, config.secureDistribution);
409-
407+
String prefix = unsignedDownloadUrlPrefix(source, config);
410408
String join = StringUtils.join(new String[]{prefix, finalResourceType, signature, transformationStr, version, source}, "/");
411409
String url = StringUtils.mergeSlashesInUrl(join);
412410

@@ -507,49 +505,52 @@ public String finalizeResourceType(String resourceType, String type, String urlS
507505
return result;
508506
}
509507

510-
public String unsignedDownloadUrlPrefix(String source, String cloudName, boolean privateCdn, boolean cdnSubdomain, Boolean secureCdnSubdomain, String cname, boolean secure, String secureDistribution) {
511-
if (this.config.cloudName.startsWith("/")) {
512-
return "/res" + this.config.cloudName;
508+
public static String unsignedDownloadUrlPrefix(String source, Configuration config) {
509+
if (config.cloudName.startsWith("/")) {
510+
return "/res" + config.cloudName;
513511
}
514-
boolean sharedDomain = !this.config.privateCdn;
512+
boolean sharedDomain = !config.privateCdn;
515513

516514
String prefix;
515+
String cloudName;
516+
String secureDistribution = config.secureDistribution;
517+
Boolean secureCdnSubdomain = null;
517518

518-
if (this.config.secure) {
519-
if (StringUtils.isEmpty(this.config.secureDistribution) || this.config.secureDistribution.equals(Cloudinary.OLD_AKAMAI_SHARED_CDN)) {
520-
secureDistribution = this.config.privateCdn ? this.config.cloudName + "-res.cloudinary.com" : Cloudinary.SHARED_CDN;
519+
if (config.secure) {
520+
if (StringUtils.isEmpty(config.secureDistribution) || config.secureDistribution.equals(Cloudinary.OLD_AKAMAI_SHARED_CDN)) {
521+
secureDistribution = config.privateCdn ? config.cloudName + "-res.cloudinary.com" : Cloudinary.SHARED_CDN;
521522
}
522523
if (!sharedDomain) {
523524
sharedDomain = secureDistribution.equals(Cloudinary.SHARED_CDN);
524525
}
525526

526527
if (secureCdnSubdomain == null && sharedDomain) {
527-
secureCdnSubdomain = this.config.cdnSubdomain;
528+
secureCdnSubdomain = config.cdnSubdomain;
528529
}
529530

530531
if (secureCdnSubdomain != null && secureCdnSubdomain == true) {
531-
secureDistribution = this.config.secureDistribution.replace("res.cloudinary.com", "res-" + shard(source) + ".cloudinary.com");
532+
secureDistribution = config.secureDistribution.replace("res.cloudinary.com", "res-" + shard(source) + ".cloudinary.com");
532533
}
533534

534535
prefix = "https://" + secureDistribution;
535-
} else if (StringUtils.isNotBlank(this.config.cname)) {
536-
String subdomain = this.config.cdnSubdomain ? "a" + shard(source) + "." : "";
537-
prefix = "http://" + subdomain + this.config.cname;
536+
} else if (StringUtils.isNotBlank(config.cname)) {
537+
String subdomain = config.cdnSubdomain ? "a" + shard(source) + "." : "";
538+
prefix = "http://" + subdomain + config.cname;
538539
} else {
539540
String protocol = "http://";
540-
cloudName = this.config.privateCdn ? this.config.cloudName + "-" : "";
541+
cloudName = config.privateCdn ? config.cloudName + "-" : "";
541542
String res = "res";
542-
String subdomain = this.config.cdnSubdomain ? "-" + shard(source) : "";
543+
String subdomain = config.cdnSubdomain ? "-" + shard(source) : "";
543544
String domain = ".cloudinary.com";
544545
prefix = StringUtils.join(new String[]{protocol, cloudName, res, subdomain, domain}, "");
545546
}
546547
if (sharedDomain) {
547-
prefix += "/" + this.config.cloudName;
548+
prefix += "/" + config.cloudName;
548549
}
549550
return prefix;
550551
}
551552

552-
private String shard(String input) {
553+
private static String shard(String input) {
553554
CRC32 crc32 = new CRC32();
554555
crc32.update(Util.getUTF8Bytes(input));
555556
return String.valueOf((crc32.getValue() % 5 + 5) % 5 + 1);

cloudinary-test-common/src/main/java/com/cloudinary/test/AbstractSearchTest.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.cloudinary.test;
22

33
import com.cloudinary.Cloudinary;
4+
import com.cloudinary.Configuration;
45
import com.cloudinary.Search;
6+
import com.cloudinary.api.ApiResponse;
57
import com.cloudinary.utils.ObjectUtils;
68
import org.junit.*;
79
import org.junit.rules.TestName;
@@ -71,10 +73,14 @@ public void shouldFindResourcesByTag() throws Exception {
7173

7274
@Test
7375
public void shouldFindFolders() throws Exception {
74-
cloudinary.api().createFolder(SEARCH_FOLDER, null);
75-
Map result = cloudinary.searchFolders().expression(String.format("name:%s", SEARCH_FOLDER)).execute();
76-
final List<Map> folders = (List) result.get("folders");
77-
assertThat(folders, hasItem(hasEntry("name", SEARCH_FOLDER)));
76+
Map createFolderResult = cloudinary.api().createFolder(SEARCH_FOLDER, null);
77+
Thread.sleep(3000);
78+
if ((Boolean) createFolderResult.get("success")) {
79+
Map result = cloudinary.searchFolders().expression(String.format("name:%s", SEARCH_FOLDER)).execute();
80+
System.out.println("SUCCESS!");
81+
final List<Map> folders = (List) result.get("folders");
82+
assertThat(folders, hasItem(hasEntry("name", SEARCH_FOLDER)));
83+
}
7884
}
7985

8086
@Test
@@ -152,7 +158,23 @@ public void shouldPaginateResourcesLimitedByTagAndOrderdByAscendingPublicId() th
152158
assertEquals(3, result.get("total_count"));
153159
assertEquals(SEARCH_TEST_2, resources.get(0).get("public_id"));
154160
assertNull(result.get("next_cursor"));
161+
}
155162

156-
163+
@Test
164+
public void testShouldBuildSearchUrl() throws Exception {
165+
String nextCursor = "db27cfb02b3f69cb39049969c23ca430c6d33d5a3a7c3ad1d870c54e1a54ee0faa5acdd9f6d288666986001711759d10";
166+
Cloudinary cloudinaryToSearch = new Cloudinary("cloudinary://key:secret@test123");
167+
cloudinaryToSearch.config.secure = true;
168+
169+
Search search = cloudinaryToSearch.search().expression("resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m").sortBy("public_id", "desc").maxResults(30);
170+
String base64Query = "eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIHVwbG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjozMCwic29ydF9ieSI6W3sicHVibGljX2lkIjoiZGVzYyJ9XX0=";
171+
String ttl300Signature = "431454b74cefa342e2f03e2d589b2e901babb8db6e6b149abf25bc0dd7ab20b7";
172+
String ttl1000Signature = "25b91426a37d4f633a9b34383c63889ff8952e7ffecef29a17d600eeb3db0db7";
173+
174+
assertEquals(String.format("https://res.cloudinary.com/%s/search/%s/%d/%s", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query), search.toUrl());
175+
assertEquals(String.format("https://res.cloudinary.com/%s/search/%s/%d/%s/%s", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query, nextCursor), search.toUrl(nextCursor));
176+
assertEquals(String.format("https://res.cloudinary.com/%s/search/%s/%d/%s/%s", cloudinaryToSearch.config.cloudName, ttl1000Signature, 1000, base64Query, nextCursor), search.toUrl(1000, nextCursor));
177+
cloudinaryToSearch.config.privateCdn = true;
178+
assertEquals(String.format("https://%s-res.cloudinary.com/search/%s/%d/%s", cloudinaryToSearch.config.cloudName, ttl300Signature, 300, base64Query), search.toUrl(300, ""));
157179
}
158-
}
180+
}

0 commit comments

Comments
 (0)