diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 4ca0036da3..288e394897 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:27b1b1884dce60460d7521b23c2a73376cba90c0ef3d9f0d32e4bdb786959cfd + digest: sha256:9de537d592b60e5eac73b374a28263969bae91ecdb29b445e894576fbf54851c diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in index 924f94ae6f..a5010f77d4 100644 --- a/.kokoro/requirements.in +++ b/.kokoro/requirements.in @@ -17,7 +17,7 @@ pycparser==2.21 pyperclip==1.8.2 python-dateutil==2.8.2 requests==2.27.1 -certifi==2022.9.24 +certifi==2022.12.7 importlib-metadata==4.8.3 zipp==3.6.0 google_api_core==2.8.2 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index 71fcafc703..15c404aa5a 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -16,9 +16,9 @@ cachetools==4.2.4 \ # via # -r requirements.in # google-auth -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 +certifi==2022.12.7 \ + --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ + --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 # via # -r requirements.in # requests diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a85f144d9..72cdd9fa2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## [2.9.1](https://github.com/googleapis/java-core/compare/v2.9.0...v2.9.1) (2023-01-09) + + +### Bug Fixes + +* Make title and description of Condition Nullable ([#1063](https://github.com/googleapis/java-core/issues/1063)) ([1238462](https://github.com/googleapis/java-core/commit/12384628ab07b7edb04181c79ed07e742277e4c4)) + + +### Documentation + +* Update javadocs for ReadChannel to be more clear about the behavior ([#1050](https://github.com/googleapis/java-core/issues/1050)) ([8b574f6](https://github.com/googleapis/java-core/commit/8b574f66c6e77b31ec8d544319a116f5c19804b9)) + + +### Dependencies + +* Update dependency com.google.api-client:google-api-client-bom to v2.1.2 ([#1064](https://github.com/googleapis/java-core/issues/1064)) ([6bf1c24](https://github.com/googleapis/java-core/commit/6bf1c2414978340cb997acb18e98815e20028718)) +* Update dependency com.google.api:api-common to v2.3.1 ([#1053](https://github.com/googleapis/java-core/issues/1053)) ([43ce490](https://github.com/googleapis/java-core/commit/43ce49083ebdfada11732ff82aa407c69311f4c9)) +* Update dependency com.google.api:api-common to v2.4.0 ([#1069](https://github.com/googleapis/java-core/issues/1069)) ([083bc07](https://github.com/googleapis/java-core/commit/083bc071f4529cd2a827cd4586e5c02690f4ffcd)) +* Update dependency com.google.api:gax-bom to v2.21.0 ([#1070](https://github.com/googleapis/java-core/issues/1070)) ([e133832](https://github.com/googleapis/java-core/commit/e133832eb6e81089745e21d9b0188cb91af0a51b)) +* Update dependency com.google.api.grpc:proto-google-common-protos to v2.12.0 ([#1067](https://github.com/googleapis/java-core/issues/1067)) ([bfab3ad](https://github.com/googleapis/java-core/commit/bfab3adbcefea538d50d567297eb491650ad19ba)) +* Update dependency com.google.api.grpc:proto-google-iam-v1 to v1.6.23 ([#1060](https://github.com/googleapis/java-core/issues/1060)) ([a2e5c4e](https://github.com/googleapis/java-core/commit/a2e5c4eb35824ee019b0d0b8f984144f07d76c0a)) +* Update dependency com.google.api.grpc:proto-google-iam-v1 to v1.7.0 ([#1068](https://github.com/googleapis/java-core/issues/1068)) ([404f222](https://github.com/googleapis/java-core/commit/404f2220ea85253945f470a6370870cce248c11e)) +* Update dependency com.google.auth:google-auth-library-bom to v1.14.0 ([#1056](https://github.com/googleapis/java-core/issues/1056)) ([328628d](https://github.com/googleapis/java-core/commit/328628da55cea6d27c4fe4ed2aaa14bf2bc59a58)) +* Update dependency com.google.code.gson:gson to v2.10.1 ([#1066](https://github.com/googleapis/java-core/issues/1066)) ([a4b941e](https://github.com/googleapis/java-core/commit/a4b941e8daed4650bd019a7c03602cd617bdcad2)) +* Update dependency com.google.errorprone:error_prone_annotations to v2.17.0 ([#1061](https://github.com/googleapis/java-core/issues/1061)) ([e7159c2](https://github.com/googleapis/java-core/commit/e7159c28eb4882395cc351d3dbd29db74a75c271)) +* Update dependency com.google.errorprone:error_prone_annotations to v2.18.0 ([#1071](https://github.com/googleapis/java-core/issues/1071)) ([b85690f](https://github.com/googleapis/java-core/commit/b85690fdcf0803502412bf5af418c17b43a718e7)) +* Update dependency com.google.protobuf:protobuf-bom to v3.21.12 ([#1054](https://github.com/googleapis/java-core/issues/1054)) ([3fc149e](https://github.com/googleapis/java-core/commit/3fc149e28a8724b100717c0ddfb138338401fea9)) +* Update dependency io.grpc:grpc-bom to v1.51.1 ([#1058](https://github.com/googleapis/java-core/issues/1058)) ([d8ca14c](https://github.com/googleapis/java-core/commit/d8ca14cdb10d5af41396b40702a4abd7c9b5b15c)) +* Update dependency org.easymock:easymock to v5.1.0 ([#1062](https://github.com/googleapis/java-core/issues/1062)) ([4c0095d](https://github.com/googleapis/java-core/commit/4c0095daa0a17564c972434e4824c03b2204e3e0)) +* Update dependency org.threeten:threetenbp to v1.6.5 ([#1052](https://github.com/googleapis/java-core/issues/1052)) ([7e12b5d](https://github.com/googleapis/java-core/commit/7e12b5d137db97d95320416cd80e467bb300c499)) + ## [2.9.0](https://github.com/googleapis/java-core/compare/v2.8.28...v2.9.0) (2022-12-05) diff --git a/google-cloud-core-bom/pom.xml b/google-cloud-core-bom/pom.xml index 8ebe76e7f9..a3702f906b 100644 --- a/google-cloud-core-bom/pom.xml +++ b/google-cloud-core-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-bom - 2.9.0 + 2.9.1 pom com.google.cloud @@ -63,17 +63,17 @@ com.google.cloud google-cloud-core - 2.9.0 + 2.9.1 com.google.cloud google-cloud-core-grpc - 2.9.0 + 2.9.1 com.google.cloud google-cloud-core-http - 2.9.0 + 2.9.1 diff --git a/google-cloud-core-grpc/pom.xml b/google-cloud-core-grpc/pom.xml index 9ccf063051..561b669fea 100644 --- a/google-cloud-core-grpc/pom.xml +++ b/google-cloud-core-grpc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-grpc - 2.9.0 + 2.9.1 jar Google Cloud Core gRPC https://github.com/googleapis/java-core @@ -13,7 +13,7 @@ com.google.cloud google-cloud-core-parent - 2.9.0 + 2.9.1 google-cloud-core-grpc @@ -47,15 +47,10 @@ io.grpc grpc-api - - io.grpc - grpc-core - com.google.http-client google-http-client - junit junit diff --git a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/GrpcTransportOptions.java b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/GrpcTransportOptions.java index 841cc8218b..4db83253a3 100644 --- a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/GrpcTransportOptions.java +++ b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/GrpcTransportOptions.java @@ -32,8 +32,6 @@ import com.google.cloud.ServiceOptions; import com.google.cloud.TransportOptions; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.grpc.internal.SharedResourceHolder; -import io.grpc.internal.SharedResourceHolder.Resource; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Objects; @@ -51,8 +49,8 @@ public class GrpcTransportOptions implements TransportOptions { private transient ExecutorFactory executorFactory; /** Shared thread pool executor. */ - private static final Resource EXECUTOR = - new Resource() { + private static final SharedResourceHolder.Resource EXECUTOR = + new SharedResourceHolder.Resource() { @Override public ScheduledExecutorService create() { ScheduledThreadPoolExecutor service = diff --git a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java new file mode 100644 index 0000000000..40fd6884c1 --- /dev/null +++ b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.grpc; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Throwables; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject + * to changes + * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java) + * + *

A simple wrapper for a {@link Runnable} that logs any exception thrown by it, before + * re-throwing it. + */ +final class LogExceptionRunnable implements Runnable { + + private static final Logger log = Logger.getLogger(LogExceptionRunnable.class.getName()); + + private final Runnable task; + + public LogExceptionRunnable(Runnable task) { + this.task = checkNotNull(task, "task"); + } + + @Override + public void run() { + try { + task.run(); + } catch (Throwable t) { + log.log(Level.SEVERE, "Exception while executing runnable " + task, t); + Throwables.throwIfUnchecked(t); + throw new AssertionError(t); + } + } + + @Override + public String toString() { + return "LogExceptionRunnable(" + task + ")"; + } +} diff --git a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java new file mode 100644 index 0000000000..c8fc6a886b --- /dev/null +++ b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java @@ -0,0 +1,184 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.grpc; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.IdentityHashMap; +import java.util.concurrent.*; + +/** + * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject + * to changes + * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/SharedResourceHolder.java) + * + *

A holder for shared resource singletons. + * + *

Components like client channels and servers need certain resources, e.g. a thread pool, to + * run. If the user has not provided such resources, these components will use a default one, which + * is shared as a static resource. This class holds these default resources and manages their + * life-cycles. + * + *

A resource is identified by the reference of a {@link Resource} object, which is typically a + * singleton, provided to the get() and release() methods. Each Resource object (not its class) maps + * to an object cached in the holder. + * + *

Resources are ref-counted and shut down after a delay when the ref-count reaches zero. + */ +final class SharedResourceHolder { + static final long DESTROY_DELAY_SECONDS = 1; + + // The sole holder instance. + private static final SharedResourceHolder holder = + new SharedResourceHolder( + new ScheduledExecutorFactory() { + @Override + public ScheduledExecutorService createScheduledExecutor() { + return Executors.newSingleThreadScheduledExecutor( + getThreadFactory("grpc-shared-destroyer-%d", true)); + } + }); + + private final IdentityHashMap, Instance> instances = new IdentityHashMap<>(); + + private final ScheduledExecutorFactory destroyerFactory; + + private ScheduledExecutorService destroyer; + + // Visible to tests that would need to create instances of the holder. + SharedResourceHolder(ScheduledExecutorFactory destroyerFactory) { + this.destroyerFactory = destroyerFactory; + } + + private static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) { + return new ThreadFactoryBuilder().setDaemon(daemon).setNameFormat(nameFormat).build(); + } + + /** + * Try to get an existing instance of the given resource. If an instance does not exist, create a + * new one with the given factory. + * + * @param resource the singleton object that identifies the requested static resource + */ + public static T get(Resource resource) { + return holder.getInternal(resource); + } + + /** + * Releases an instance of the given resource. + * + *

The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw + * IllegalArgumentException. + * + *

Caller must not release a reference more than once. It's advisory that you clear the + * reference to the instance with the null returned by this method. + * + * @param resource the singleton Resource object that identifies the released static resource + * @param instance the released static resource + * @return a null which the caller can use to clear the reference to that instance. + */ + public static T release(final Resource resource, final T instance) { + return holder.releaseInternal(resource, instance); + } + + /** + * Visible to unit tests. + * + * @see #get(Resource) + */ + @SuppressWarnings("unchecked") + synchronized T getInternal(Resource resource) { + Instance instance = instances.get(resource); + if (instance == null) { + instance = new Instance(resource.create()); + instances.put(resource, instance); + } + if (instance.destroyTask != null) { + instance.destroyTask.cancel(false); + instance.destroyTask = null; + } + instance.refcount++; + return (T) instance.payload; + } + + /** Visible to unit tests. */ + synchronized T releaseInternal(final Resource resource, final T instance) { + final Instance cached = instances.get(resource); + if (cached == null) { + throw new IllegalArgumentException("No cached instance found for " + resource); + } + Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance"); + Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero"); + cached.refcount--; + if (cached.refcount == 0) { + Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled"); + // Schedule a delayed task to destroy the resource. + if (destroyer == null) { + destroyer = destroyerFactory.createScheduledExecutor(); + } + cached.destroyTask = + destroyer.schedule( + new LogExceptionRunnable( + new Runnable() { + @Override + public void run() { + synchronized (SharedResourceHolder.this) { + // Refcount may have gone up since the task was scheduled. Re-check it. + if (cached.refcount == 0) { + try { + resource.close(instance); + } finally { + instances.remove(resource); + if (instances.isEmpty()) { + destroyer.shutdown(); + destroyer = null; + } + } + } + } + } + }), + DESTROY_DELAY_SECONDS, + TimeUnit.SECONDS); + } + // Always returning null + return null; + } + + /** Defines a resource, and the way to create and destroy instances of it. */ + public interface Resource { + /** Create a new instance of the resource. */ + T create(); + + /** Destroy the given instance. */ + void close(T instance); + } + + interface ScheduledExecutorFactory { + ScheduledExecutorService createScheduledExecutor(); + } + + private static class Instance { + final Object payload; + int refcount; + ScheduledFuture destroyTask; + + Instance(Object payload) { + this.payload = payload; + } + } +} diff --git a/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java b/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java new file mode 100644 index 0000000000..ba70a0de55 --- /dev/null +++ b/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java @@ -0,0 +1,292 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.grpc; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import java.util.LinkedList; +import java.util.concurrent.Delayed; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject + * to changes + * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/test/java/io/grpc/internal/SharedResourceHolderTest.java) + * + *

Unit tests for {@link SharedResourceHolder}. + */ +@RunWith(JUnit4.class) +public class SharedResourceHolderTest { + + private final LinkedList> scheduledDestroyTasks = new LinkedList<>(); + + private SharedResourceHolder holder; + + private static class ResourceInstance { + volatile boolean closed; + } + + private static class ResourceFactory implements SharedResourceHolder.Resource { + @Override + public ResourceInstance create() { + return new ResourceInstance(); + } + + @Override + public void close(ResourceInstance instance) { + instance.closed = true; + } + } + + // Defines two kinds of resources + private static final SharedResourceHolder.Resource SHARED_FOO = + new ResourceFactory(); + private static final SharedResourceHolder.Resource SHARED_BAR = + new ResourceFactory(); + + @Before + public void setUp() { + holder = new SharedResourceHolder(new MockExecutorFactory()); + } + + @Test + public void destroyResourceWhenRefCountReachesZero() { + ResourceInstance foo1 = holder.getInternal(SHARED_FOO); + ResourceInstance sharedFoo = foo1; + ResourceInstance foo2 = holder.getInternal(SHARED_FOO); + assertSame(sharedFoo, foo2); + + ResourceInstance bar1 = holder.getInternal(SHARED_BAR); + ResourceInstance sharedBar = bar1; + + foo2 = holder.releaseInternal(SHARED_FOO, foo2); + // foo refcount not reached 0, thus shared foo is not closed + assertTrue(scheduledDestroyTasks.isEmpty()); + assertFalse(sharedFoo.closed); + assertNull(foo2); + + foo1 = holder.releaseInternal(SHARED_FOO, foo1); + assertNull(foo1); + + // foo refcount has reached 0, a destroying task is scheduled + assertEquals(1, scheduledDestroyTasks.size()); + MockScheduledFuture scheduledDestroyTask = scheduledDestroyTasks.poll(); + assertEquals( + SharedResourceHolder.DESTROY_DELAY_SECONDS, + scheduledDestroyTask.getDelay(TimeUnit.SECONDS)); + + // Simluate that the destroyer executes the foo destroying task + scheduledDestroyTask.runTask(); + assertTrue(sharedFoo.closed); + + // After the destroying, obtaining a foo will get a different instance + ResourceInstance foo3 = holder.getInternal(SHARED_FOO); + assertNotSame(sharedFoo, foo3); + + holder.releaseInternal(SHARED_BAR, bar1); + + // bar refcount has reached 0, a destroying task is scheduled + assertEquals(1, scheduledDestroyTasks.size()); + scheduledDestroyTask = scheduledDestroyTasks.poll(); + assertEquals( + SharedResourceHolder.DESTROY_DELAY_SECONDS, + scheduledDestroyTask.getDelay(TimeUnit.SECONDS)); + + // Simulate that the destroyer executes the bar destroying task + scheduledDestroyTask.runTask(); + assertTrue(sharedBar.closed); + } + + @Test + public void cancelDestroyTask() { + ResourceInstance foo1 = holder.getInternal(SHARED_FOO); + ResourceInstance sharedFoo = foo1; + holder.releaseInternal(SHARED_FOO, foo1); + // A destroying task for foo is scheduled + MockScheduledFuture scheduledDestroyTask = scheduledDestroyTasks.poll(); + assertFalse(scheduledDestroyTask.cancelled); + + // obtaining a foo before the destroying task is executed will cancel the destroy + ResourceInstance foo2 = holder.getInternal(SHARED_FOO); + assertTrue(scheduledDestroyTask.cancelled); + assertTrue(scheduledDestroyTasks.isEmpty()); + assertFalse(sharedFoo.closed); + + // And it will be the same foo instance + assertSame(sharedFoo, foo2); + + // Release it and the destroying task is scheduled again + holder.releaseInternal(SHARED_FOO, foo2); + scheduledDestroyTask = scheduledDestroyTasks.poll(); + assertNotNull(scheduledDestroyTask); + assertFalse(scheduledDestroyTask.cancelled); + scheduledDestroyTask.runTask(); + assertTrue(sharedFoo.closed); + } + + @Test + public void releaseWrongInstance() { + ResourceInstance uncached = new ResourceInstance(); + try { + holder.releaseInternal(SHARED_FOO, uncached); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + ResourceInstance cached = holder.getInternal(SHARED_FOO); + try { + holder.releaseInternal(SHARED_FOO, uncached); + fail("Should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + holder.releaseInternal(SHARED_FOO, cached); + } + + @Test + public void overreleaseInstance() { + ResourceInstance foo1 = holder.getInternal(SHARED_FOO); + holder.releaseInternal(SHARED_FOO, foo1); + try { + holder.releaseInternal(SHARED_FOO, foo1); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + } + + @Test + public void handleInstanceCloseError() { + class ExceptionOnCloseResource implements SharedResourceHolder.Resource { + @Override + public ResourceInstance create() { + return new ResourceInstance(); + } + + @Override + public void close(ResourceInstance instance) { + throw new RuntimeException(); + } + } + + SharedResourceHolder.Resource resource = new ExceptionOnCloseResource(); + ResourceInstance instance = holder.getInternal(resource); + holder.releaseInternal(resource, instance); + MockScheduledFuture scheduledDestroyTask = scheduledDestroyTasks.poll(); + try { + scheduledDestroyTask.runTask(); + fail("Should throw RuntimeException"); + } catch (RuntimeException e) { + // expected + } + + // Future resource fetches should not get the partially-closed one. + assertNotSame(instance, holder.getInternal(resource)); + } + + private class MockExecutorFactory implements SharedResourceHolder.ScheduledExecutorFactory { + @Override + public ScheduledExecutorService createScheduledExecutor() { + ScheduledExecutorService mockExecutor = createNiceMock(ScheduledExecutorService.class); + expect(mockExecutor.schedule(anyObject(Runnable.class), anyLong(), anyObject(TimeUnit.class))) + .andAnswer( + new IAnswer() { + @Override + public ScheduledFuture answer() throws Throwable { + Object[] args = EasyMock.getCurrentArguments(); + Runnable command = (Runnable) args[0]; + long delay = (Long) args[1]; + TimeUnit unit = (TimeUnit) args[2]; + MockScheduledFuture future = + new MockScheduledFuture<>(command, delay, unit); + scheduledDestroyTasks.add(future); + return future; + } + }) + .anyTimes(); + replay(mockExecutor); + return mockExecutor; + } + } + + protected static class MockScheduledFuture implements java.util.concurrent.ScheduledFuture { + private boolean cancelled; + private boolean finished; + final Runnable command; + final long delay; + final TimeUnit unit; + + MockScheduledFuture(Runnable command, long delay, TimeUnit unit) { + this.command = command; + this.delay = delay; + this.unit = unit; + } + + void runTask() { + command.run(); + finished = true; + } + + @Override + public boolean cancel(boolean interrupt) { + if (cancelled || finished) { + return false; + } + cancelled = true; + return true; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public long getDelay(TimeUnit targetUnit) { + return targetUnit.convert(this.delay, this.unit); + } + + @Override + public int compareTo(Delayed o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDone() { + return cancelled || finished; + } + + @Override + public V get() { + throw new UnsupportedOperationException(); + } + + @Override + public V get(long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/google-cloud-core-http/pom.xml b/google-cloud-core-http/pom.xml index e889527679..2d6ea31631 100644 --- a/google-cloud-core-http/pom.xml +++ b/google-cloud-core-http/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-http - 2.9.0 + 2.9.1 jar Google Cloud Core HTTP https://github.com/googleapis/java-core @@ -13,7 +13,7 @@ com.google.cloud google-cloud-core-parent - 2.9.0 + 2.9.1 google-cloud-core-http diff --git a/google-cloud-core/pom.xml b/google-cloud-core/pom.xml index af552f9f76..0798324582 100644 --- a/google-cloud-core/pom.xml +++ b/google-cloud-core/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core - 2.9.0 + 2.9.1 jar Google Cloud Core https://github.com/googleapis/java-core @@ -13,7 +13,7 @@ com.google.cloud google-cloud-core-parent - 2.9.0 + 2.9.1 google-cloud-core diff --git a/google-cloud-core/src/main/java/com/google/cloud/Condition.java b/google-cloud-core/src/main/java/com/google/cloud/Condition.java index 3854883796..c3c4c83fb9 100644 --- a/google-cloud-core/src/main/java/com/google/cloud/Condition.java +++ b/google-cloud-core/src/main/java/com/google/cloud/Condition.java @@ -18,6 +18,7 @@ import com.google.api.core.BetaApi; import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; /** * Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access @@ -32,9 +33,11 @@ @AutoValue public abstract class Condition { /** Get IAM Policy Binding Condition Title */ + @Nullable public abstract String getTitle(); /** Get IAM Policy Binding Condition Description */ + @Nullable public abstract String getDescription(); /** Get IAM Policy Binding Condition Expression */ @@ -51,10 +54,10 @@ public static Builder newBuilder() { @AutoValue.Builder public abstract static class Builder { /** Set IAM Policy Binding Condition Title */ - public abstract Builder setTitle(String title); + public abstract Builder setTitle(@Nullable String title); /** Set IAM Policy Binding Condition Description */ - public abstract Builder setDescription(String description); + public abstract Builder setDescription(@Nullable String description); /** Set IAM Policy Binding Condition Expression */ public abstract Builder setExpression(String expression); diff --git a/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java b/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java index 781326fa7a..effc33ee8e 100644 --- a/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java +++ b/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java @@ -37,6 +37,7 @@ public interface ReadChannel extends ReadableByteChannel, Closeable, Restorable< @Override void close(); + /** Set the offset to read from. */ void seek(long position) throws IOException; /** @@ -62,6 +63,23 @@ public interface ReadChannel extends ReadableByteChannel, Closeable, Restorable< *

If used in conjunction with {@link #seek(long)} the total number of returned bytes from this * channel will be reduced by the number of bytes specified to seek. * + *

The value provided as {@code limit} will define a left-closed, + * right-open interval along with either {@code 0} or any value provided to {@link + * #seek(long)}, i.e. {@code [}{@link #seek(long)}{@code , }{@link #limit(long)}{@code )}. + * + *

An example to help illustrate the relationship

+ * + * Imagine some data {@code [A, B, C, D, E, F, G, H, I, J]}, 10 bytes total. + * + *
    + *
  1. {@code limit(5)} would produce {@code [A, B, C, D, E]} + *
  2. {@code seek(8)} would produce {@code [I, J]} + *
  3. {@code seek(2)} {@code limit(5)} would produce {@code [C, D, E]} + *
  4. {@code seek(3)} {@code limit(3)} would produce {@code []} + *
+ * *

NOTE:Implementers are not required to return a new instance from this method, however * they are allowed to. Users of this method should always use the instance returned from this * method. diff --git a/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java b/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java new file mode 100644 index 0000000000..67e0e6c2b5 --- /dev/null +++ b/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public final class ConditionTest { + + @Test + public void title_nullable() { + Condition condition = + Condition.newBuilder().setTitle(null).setDescription("desc").setExpression("expr").build(); + + assertThat(condition.getTitle()).isNull(); + } + + @Test + public void description_nullable() { + Condition condition = + Condition.newBuilder().setTitle("title").setDescription(null).setExpression("expr").build(); + + assertThat(condition.getDescription()).isNull(); + } +} diff --git a/pom.xml b/pom.xml index fc165b0ace..cea71fc54b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-core-parent pom - 2.9.0 + 2.9.1 Google Cloud Core Parent https://github.com/googleapis/java-core @@ -151,26 +151,26 @@ UTF-8 github google-cloud-core-parent - 2.20.1 - 2.2.2 - 2.11.0 - 1.6.22 - 1.13.0 - 2.1.1 + 2.21.0 + 2.4.0 + 2.12.0 + 1.7.0 + 1.14.0 + 2.1.2 1.42.3 - 1.51.0 - 3.21.10 + 1.51.1 + 3.21.12 0.31.1 1.3.2 31.1-jre 4.13.2 1.1.3 - 5.0.1 + 5.1.0 3.0.2 - 1.6.4 + 1.6.5 3.3 - 2.16 - 2.10 + 2.18.0 + 2.10.1 diff --git a/versions.txt b/versions.txt index 8efc8816bf..377fa07991 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-core:2.9.0:2.9.0 +google-cloud-core:2.9.1:2.9.1