/* * Copyright 2013 the original author or authors. * * 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 org.gradle.plugin.use.resolve.service.internal; import com.google.common.base.Objects; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.cache.PersistentCache; import org.gradle.cache.PersistentIndexedCache; import org.gradle.cache.PersistentIndexedCacheParameters; import org.gradle.internal.Factory; import org.gradle.internal.concurrent.CompositeStoppable; import org.gradle.internal.serialize.AbstractSerializer; import org.gradle.internal.serialize.Decoder; import org.gradle.internal.serialize.Encoder; import org.gradle.internal.serialize.Serializer; import org.gradle.plugin.management.internal.PluginRequestInternal; import java.io.IOException; public class PersistentCachingPluginResolutionServiceClient implements PluginResolutionServiceClient { public static final String PLUGIN_USE_METADATA_CACHE_NAME = "plugin-use-metadata"; public static final String CLIENT_STATUS_CACHE_NAME = "client-status"; private final PluginResolutionServiceClient delegate; private final PersistentCache cacheAccess; private final PersistentIndexedCache<PluginRequestKey, Response<PluginUseMetaData>> pluginUseMetadataCache; private final PersistentIndexedCache<ClientStatusKey, Response<ClientStatus>> clientStatusCache; public PersistentCachingPluginResolutionServiceClient(PluginResolutionServiceClient delegate, PersistentCache persistentCache) { this.delegate = delegate; this.cacheAccess = persistentCache; this.pluginUseMetadataCache = persistentCache.createCache(PersistentIndexedCacheParameters.of( PLUGIN_USE_METADATA_CACHE_NAME, new PluginRequestKey.Serializer(), ResponseSerializer.of(new PluginUseMetaData.Serializer())) ); this.clientStatusCache = persistentCache.createCache(PersistentIndexedCacheParameters.of( CLIENT_STATUS_CACHE_NAME, new ClientStatusKey.Serializer(), ResponseSerializer.of(new ClientStatus.Serializer())) ); } public Response<PluginUseMetaData> queryPluginMetadata(final String portalUrl, final boolean shouldValidate, final PluginRequestInternal pluginRequest) { PluginRequestKey key = PluginRequestKey.of(portalUrl, pluginRequest); Factory<Response<PluginUseMetaData>> factory = new Factory<Response<PluginUseMetaData>>() { public Response<PluginUseMetaData> create() { return delegate.queryPluginMetadata(portalUrl, shouldValidate, pluginRequest); } }; if (shouldValidate) { return fetch(pluginUseMetadataCache, key, factory); } else { return maybeFetch(pluginUseMetadataCache, key, factory); } } public Response<ClientStatus> queryClientStatus(final String portalUrl, final boolean shouldValidate, final String checksum) { ClientStatusKey key = new ClientStatusKey(portalUrl); Factory<Response<ClientStatus>> factory = new Factory<Response<ClientStatus>>() { public Response<ClientStatus> create() { return delegate.queryClientStatus(portalUrl, shouldValidate, checksum); } }; if (shouldValidate) { return fetch(clientStatusCache, key, factory); } else { return maybeFetch(clientStatusCache, key, factory, new Spec<Response<ClientStatus>>() { public boolean isSatisfiedBy(Response<ClientStatus> element) { return !element.getClientStatusChecksum().equals(checksum); } }); } } private <K, V extends Response<?>> V maybeFetch(final PersistentIndexedCache<K, V> cache, final K key, Factory<V> factory) { return maybeFetch(cache, key, factory, Specs.SATISFIES_NONE); } private <K, V extends Response<?>> V maybeFetch(final PersistentIndexedCache<K, V> cache, final K key, Factory<V> factory, Spec<? super V> shouldFetch) { V cachedValue = cacheAccess.useCache(new Factory<V>() { public V create() { return cache.get(key); } }); boolean fetch = cachedValue == null || shouldFetch.isSatisfiedBy(cachedValue); if (fetch) { return fetch(cache, key, factory); } else { return cachedValue; } } private <K, V extends Response<?>> V fetch(final PersistentIndexedCache<K, V> cache, final K key, Factory<V> factory) { final V value = factory.create(); if (value.isError()) { return value; } cacheAccess.useCache(new Runnable() { public void run() { cache.put(key, value); } }); return value; } public void close() throws IOException { CompositeStoppable.stoppable(delegate, cacheAccess).stop(); } private static class ResponseSerializer<T> extends AbstractSerializer<Response<T>> { private final Serializer<T> payloadSerializer; private static <T> ResponseSerializer<T> of(Serializer<T> payloadSerializer) { return new ResponseSerializer<T>(payloadSerializer); } private ResponseSerializer(Serializer<T> payloadSerializer) { this.payloadSerializer = payloadSerializer; } public Response<T> read(Decoder decoder) throws Exception { return new SuccessResponse<T>( payloadSerializer.read(decoder), decoder.readSmallInt(), decoder.readString(), decoder.readNullableString() ); } public void write(Encoder encoder, Response<T> value) throws Exception { T response = value.getResponse(); payloadSerializer.write(encoder, response); encoder.writeSmallInt(value.getStatusCode()); encoder.writeString(value.getUrl()); encoder.writeNullableString(value.getClientStatusChecksum()); } @Override public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } ResponseSerializer rhs = (ResponseSerializer) obj; return Objects.equal(payloadSerializer, rhs.payloadSerializer); } @Override public int hashCode() { return Objects.hashCode(super.hashCode(), payloadSerializer); } } static class PluginRequestKey { private final String id; private final String version; private final String url; private static PluginRequestKey of(String url, PluginRequestInternal pluginRequest) { return new PluginRequestKey(pluginRequest.getId().toString(), pluginRequest.getVersion(), url); } private PluginRequestKey(String id, String version, String url) { this.id = id; this.version = version; this.url = url; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PluginRequestKey key = (PluginRequestKey) o; return id.equals(key.id) && url.equals(key.url) && version.equals(key.version); } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + version.hashCode(); result = 31 * result + url.hashCode(); return result; } private static class Serializer extends AbstractSerializer<PluginRequestKey> { public PluginRequestKey read(Decoder decoder) throws Exception { return new PluginRequestKey(decoder.readString(), decoder.readString(), decoder.readString()); } public void write(Encoder encoder, PluginRequestKey value) throws Exception { encoder.writeString(value.id); encoder.writeString(value.version); encoder.writeString(value.url); } } } public static class ClientStatusKey { private final String portalUrl; public ClientStatusKey(String portalUrl) { this.portalUrl = portalUrl; } public static class Serializer extends AbstractSerializer<ClientStatusKey> { public ClientStatusKey read(Decoder decoder) throws Exception { return new ClientStatusKey(decoder.readString()); } public void write(Encoder encoder, ClientStatusKey value) throws Exception { encoder.writeString(value.portalUrl); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClientStatusKey that = (ClientStatusKey) o; return portalUrl.equals(that.portalUrl); } @Override public int hashCode() { return portalUrl.hashCode(); } } }