Skip to content

Commit 52e60ee

Browse files
committed
Merge pull request #243 from mziccard/sign-url-time-unit
Make signUrl take long duration and TimeUnit parameter
2 parents cedc9a2 + 6bbfb05 commit 52e60ee

File tree

9 files changed

+132
-44
lines changed

9 files changed

+132
-44
lines changed

gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.io.IOException;
3535
import java.io.InputStream;
3636
import java.io.InputStreamReader;
37+
import java.io.ObjectStreamException;
3738
import java.io.Serializable;
3839
import java.lang.reflect.Method;
3940
import java.net.HttpURLConnection;
@@ -62,6 +63,7 @@ public abstract class ServiceOptions<
6263
private final ServiceRpcFactory<ServiceRpcT, OptionsT> serviceRpcFactory;
6364
private final int connectTimeout;
6465
private final int readTimeout;
66+
private final Clock clock;
6567

6668
public interface HttpTransportFactory extends Serializable {
6769
HttpTransport create();
@@ -91,7 +93,44 @@ public HttpTransport create() {
9193
}
9294
}
9395

96+
/**
97+
* A class providing access to the current time in milliseconds. This class is mainly used for
98+
* testing and will be replaced by Java8's {@code java.time.Clock}.
99+
*
100+
* Implementations should implement {@code Serializable} wherever possible and must document
101+
* whether or not they do support serialization.
102+
*/
103+
public static abstract class Clock {
94104

105+
private static ServiceOptions.Clock DEFAULT_TIME_SOURCE = new DefaultClock();
106+
107+
/**
108+
* Returns current time in milliseconds according to this clock.
109+
*/
110+
public abstract long millis();
111+
112+
/**
113+
* Returns the default clock. Default clock uses {@link System#currentTimeMillis()} to get time
114+
* in milliseconds.
115+
*/
116+
public static ServiceOptions.Clock defaultClock() {
117+
return DEFAULT_TIME_SOURCE;
118+
}
119+
120+
private static class DefaultClock extends ServiceOptions.Clock implements Serializable {
121+
122+
private static final long serialVersionUID = -5077300394286703864L;
123+
124+
@Override
125+
public long millis() {
126+
return System.currentTimeMillis();
127+
}
128+
129+
private Object readResolve() throws ObjectStreamException {
130+
return DEFAULT_TIME_SOURCE;
131+
}
132+
}
133+
}
95134

96135
protected abstract static class Builder<
97136
ServiceRpcT,
@@ -106,6 +145,7 @@ protected abstract static class Builder<
106145
private ServiceRpcFactory<ServiceRpcT, OptionsT> serviceRpcFactory;
107146
private int connectTimeout = -1;
108147
private int readTimeout = -1;
148+
private Clock clock;
109149

110150
protected Builder() {}
111151

@@ -125,6 +165,18 @@ protected B self() {
125165
return (B) this;
126166
}
127167

168+
/**
169+
* Sets the service's clock. The clock is mainly used for testing purpose. {@link Clock} will be
170+
* replaced by Java8's {@code java.time.Clock}.
171+
*
172+
* @param clock the clock to set
173+
* @return the builder.
174+
*/
175+
public B clock(Clock clock) {
176+
this.clock = clock;
177+
return self();
178+
}
179+
128180
/**
129181
* Sets project id.
130182
*
@@ -221,6 +273,7 @@ protected ServiceOptions(Builder<ServiceRpcT, OptionsT, ?> builder) {
221273
serviceRpcFactory = builder.serviceRpcFactory;
222274
connectTimeout = builder.connectTimeout;
223275
readTimeout = builder.readTimeout;
276+
clock = firstNonNull(builder.clock, Clock.defaultClock());
224277
}
225278

226279
private static AuthCredentials defaultAuthCredentials() {
@@ -419,9 +472,17 @@ public int readTimeout() {
419472
return readTimeout;
420473
}
421474

475+
/**
476+
* Returns the service's clock. Default time source uses {@link System#currentTimeMillis()} to
477+
* get current time.
478+
*/
479+
public Clock clock() {
480+
return clock;
481+
}
482+
422483
protected int baseHashCode() {
423484
return Objects.hash(projectId, host, httpTransportFactory, authCredentials, retryParams,
424-
serviceRpcFactory);
485+
serviceRpcFactory, connectTimeout, readTimeout, clock);
425486
}
426487

427488
protected boolean baseEquals(ServiceOptions<?, ?> other) {
@@ -430,7 +491,10 @@ protected boolean baseEquals(ServiceOptions<?, ?> other) {
430491
&& Objects.equals(httpTransportFactory, other.httpTransportFactory)
431492
&& Objects.equals(authCredentials, other.authCredentials)
432493
&& Objects.equals(retryParams, other.retryParams)
433-
&& Objects.equals(serviceRpcFactory, other.serviceRpcFactory);
494+
&& Objects.equals(serviceRpcFactory, other.serviceRpcFactory)
495+
&& Objects.equals(connectTimeout, other.connectTimeout)
496+
&& Objects.equals(readTimeout, other.readTimeout)
497+
&& Objects.equals(clock, clock);
434498
}
435499

436500
public abstract Builder<ServiceRpcT, OptionsT, ?> toBuilder();

gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.util.Calendar;
5656
import java.util.HashMap;
5757
import java.util.Map;
58+
import java.util.concurrent.TimeUnit;
5859

5960
/**
6061
* An example of using the Google Cloud Storage.
@@ -499,11 +500,8 @@ public void run(Storage storage, Tuple<ServiceAccountAuthCredentials, Blob> tupl
499500

500501
private void run(Storage storage, ServiceAccountAuthCredentials cred, Blob blob)
501502
throws IOException {
502-
Calendar cal = Calendar.getInstance();
503-
cal.add(Calendar.DATE, 1);
504-
long expiration = cal.getTimeInMillis() / 1000;
505503
System.out.println("Signed URL: " +
506-
blob.signUrl(expiration, SignUrlOption.serviceAccount(cred)));
504+
blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.serviceAccount(cred)));
507505
}
508506

509507
@Override

gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Collections;
3333
import java.util.List;
3434
import java.util.Objects;
35+
import java.util.concurrent.TimeUnit;
3536

3637
/**
3738
* A Google cloud storage object.
@@ -246,13 +247,15 @@ public BlobWriteChannel writer(BlobTargetOption... options) {
246247
* time period. This is particularly useful if you don't want publicly accessible blobs, but don't
247248
* want to require users to explicitly log in.
248249
*
249-
* @param expirationTimeInSeconds the signed URL expiration (using epoch time)
250-
* @param options signed url options
250+
* @param duration time until the signed URL expires, expressed in {@code unit}. The finer
251+
* granularity supported is 1 second, finer granularities will be truncated
252+
* @param unit time unit of the {@code duration} parameter
253+
* @param options optional URL signing options
251254
* @return a signed URL for this bucket and the specified options
252255
* @see <a href="https://cloud.google.com/storage/docs/access-control#Signed-URLs">Signed-URLs</a>
253256
*/
254-
public URL signUrl(long expirationTimeInSeconds, SignUrlOption... options) {
255-
return storage.signUrl(info, expirationTimeInSeconds, options);
257+
public URL signUrl(long duration, TimeUnit unit, SignUrlOption... options) {
258+
return storage.signUrl(info, duration, unit, options);
256259
}
257260

258261
/**
@@ -269,7 +272,7 @@ public Storage storage() {
269272
* @param storage the storage service used to issue the request
270273
* @param infos the blobs to get
271274
* @return an immutable list of {@code Blob} objects. If a blob does not exist or access to it has
272-
* been denied the corresponding item in the list is {@code null}.
275+
* been denied the corresponding item in the list is {@code null}.
273276
* @throws StorageException upon failure
274277
*/
275278
public static List<Blob> get(final Storage storage, BlobInfo... infos) {
@@ -294,7 +297,7 @@ public Blob apply(BlobInfo f) {
294297
* @param storage the storage service used to issue the request
295298
* @param infos the blobs to update
296299
* @return an immutable list of {@code Blob} objects. If a blob does not exist or access to it has
297-
* been denied the corresponding item in the list is {@code null}.
300+
* been denied the corresponding item in the list is {@code null}.
298301
* @throws StorageException upon failure
299302
*/
300303
public static List<Blob> update(final Storage storage, BlobInfo... infos) {
@@ -319,8 +322,8 @@ public Blob apply(BlobInfo f) {
319322
* @param storage the storage service used to issue the request
320323
* @param infos the blobs to delete
321324
* @return an immutable list of booleans. If a blob has been deleted the corresponding item in the
322-
* list is {@code true}. If deletion failed or access to the resource was denied the item is
323-
* {@code false}.
325+
* list is {@code true}. If deletion failed or access to the resource was denied the item is
326+
* {@code false}.
324327
* @throws StorageException upon failure
325328
*/
326329
public static List<Boolean> delete(Storage storage, BlobInfo... infos) {

gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.LinkedList;
3535
import java.util.List;
3636
import java.util.Set;
37+
import java.util.concurrent.TimeUnit;
3738

3839
/**
3940
* An interface for Google Cloud Storage.
@@ -643,22 +644,24 @@ public static Builder builder() {
643644
* <p>
644645
* Example usage of creating a signed URL that is valid for 2 weeks:
645646
* <pre> {@code
646-
* service.signUrl(BlobInfo.of("bucket", "name"), TimeUnit.DAYS.toSeconds(14));
647+
* service.signUrl(BlobInfo.of("bucket", "name"), 14, TimeUnit.DAYS);
647648
* }</pre>
648649
*
649-
* @param blobInfo the blob associated with the signed url
650-
* @param expirationTimeInSeconds the signed URL expiration (using epoch time)
650+
* @param blobInfo the blob associated with the signed URL
651+
* @param duration time until the signed URL expires, expressed in {@code unit}. The finer
652+
* granularity supported is 1 second, finer granularities will be truncated
653+
* @param unit time unit of the {@code duration} parameter
651654
* @param options optional URL signing options
652655
* @see <a href="https://cloud.google.com/storage/docs/access-control#Signed-URLs">Signed-URLs</a>
653656
*/
654-
URL signUrl(BlobInfo blobInfo, long expirationTimeInSeconds, SignUrlOption... options);
657+
URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options);
655658

656659
/**
657660
* Gets the requested blobs. A batch request is used to perform this call.
658661
*
659662
* @param blobInfos blobs to get
660663
* @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it
661-
* has been denied the corresponding item in the list is {@code null}.
664+
* has been denied the corresponding item in the list is {@code null}.
662665
* @throws StorageException upon failure
663666
*/
664667
List<BlobInfo> get(BlobInfo... blobInfos);
@@ -668,7 +671,7 @@ public static Builder builder() {
668671
*
669672
* @param blobInfos blobs to update
670673
* @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it
671-
* has been denied the corresponding item in the list is {@code null}.
674+
* has been denied the corresponding item in the list is {@code null}.
672675
* @throws StorageException upon failure
673676
*/
674677
List<BlobInfo> update(BlobInfo... blobInfos);
@@ -678,8 +681,8 @@ public static Builder builder() {
678681
*
679682
* @param blobInfos blobs to delete
680683
* @return an immutable list of booleans. If a blob has been deleted the corresponding item in the
681-
* list is {@code true}. If deletion failed or access to the resource was denied the item is
682-
* {@code false}.
684+
* list is {@code true}. If deletion failed or access to the resource was denied the item is
685+
* {@code false}.
683686
* @throws StorageException upon failure
684687
*/
685688
List<Boolean> delete(BlobInfo... blobInfos);

gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import java.util.Map;
6969
import java.util.Set;
7070
import java.util.concurrent.Callable;
71+
import java.util.concurrent.TimeUnit;
7172

7273
final class StorageImpl extends BaseService<StorageOptions> implements Storage {
7374

@@ -521,7 +522,9 @@ public BlobWriteChannel writer(BlobInfo blobInfo, BlobTargetOption... options) {
521522
}
522523

523524
@Override
524-
public URL signUrl(BlobInfo blobInfo, long expiration, SignUrlOption... options) {
525+
public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) {
526+
long expiration = TimeUnit.SECONDS.convert(
527+
options().clock().millis() + unit.toMillis(duration), TimeUnit.MILLISECONDS);
525528
EnumMap<SignUrlOption.Option, Object> optionMap = Maps.newEnumMap(SignUrlOption.Option.class);
526529
for (SignUrlOption option : options) {
527530
optionMap.put(option.option(), option.value());

gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageOptions.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ private Builder(StorageOptions options) {
4747
super(options);
4848
}
4949

50+
/**
51+
* Sets the path delimiter for the storage service.
52+
*
53+
* @param pathDelimiter the path delimiter to set
54+
* @return the builder.
55+
*/
5056
public Builder pathDelimiter(String pathDelimiter) {
5157
this.pathDelimiter = pathDelimiter;
5258
return this;
@@ -61,7 +67,6 @@ public StorageOptions build() {
6167
private StorageOptions(Builder builder) {
6268
super(builder);
6369
pathDelimiter = MoreObjects.firstNonNull(builder.pathDelimiter, DEFAULT_PATH_DELIMITER);
64-
// todo: consider providing read-timeout
6570
}
6671

6772
@Override
@@ -84,6 +89,9 @@ StorageRpc storageRpc() {
8489
return storageRpc;
8590
}
8691

92+
/**
93+
* Returns the storage service's path delimiter.
94+
*/
8795
public String pathDelimiter() {
8896
return pathDelimiter;
8997
}

gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.net.URL;
3939
import java.util.Arrays;
4040
import java.util.List;
41+
import java.util.concurrent.TimeUnit;
4142

4243
public class BlobTest {
4344

@@ -161,9 +162,9 @@ public void testWriter() throws Exception {
161162
@Test
162163
public void testSignUrl() throws Exception {
163164
URL url = new URL("http://localhost:123/bla");
164-
expect(storage.signUrl(BLOB_INFO, 100)).andReturn(url);
165+
expect(storage.signUrl(BLOB_INFO, 100, TimeUnit.SECONDS)).andReturn(url);
165166
replay(storage);
166-
assertEquals(url, blob.signUrl(100));
167+
assertEquals(url, blob.signUrl(100, TimeUnit.SECONDS));
167168
}
168169

169170
@Test

gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -435,10 +435,7 @@ public void testGetSignedUrl() throws IOException {
435435
String blobName = "test-get-signed-url-blob";
436436
BlobInfo blob = BlobInfo.of(bucket, blobName);
437437
assertNotNull(storage.create(BlobInfo.of(bucket, blobName), BLOB_BYTE_CONTENT));
438-
Calendar calendar = Calendar.getInstance();
439-
calendar.add(Calendar.HOUR, 1);
440-
long expiration = calendar.getTimeInMillis() / 1000;
441-
URL url = storage.signUrl(blob, expiration);
438+
URL url = storage.signUrl(blob, 1, TimeUnit.HOURS);
442439
URLConnection connection = url.openConnection();
443440
byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
444441
try (InputStream responseStream = connection.getInputStream()) {
@@ -453,10 +450,8 @@ public void testPostSignedUrl() throws IOException {
453450
String blobName = "test-post-signed-url-blob";
454451
BlobInfo blob = BlobInfo.of(bucket, blobName);
455452
assertNotNull(storage.create(BlobInfo.of(bucket, blobName)));
456-
Calendar calendar = Calendar.getInstance();
457-
calendar.add(Calendar.HOUR, 1);
458-
long expiration = calendar.getTimeInMillis() / 1000;
459-
URL url = storage.signUrl(blob, expiration, Storage.SignUrlOption.httpMethod(HttpMethod.POST));
453+
URL url =
454+
storage.signUrl(blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST));
460455
URLConnection connection = url.openConnection();
461456
connection.setDoOutput(true);
462457
connection.connect();

0 commit comments

Comments
 (0)