/* * Copyright 2014 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.escape.Escaper; import com.google.common.net.UrlEscapers; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Nullable; import org.gradle.authentication.Authentication; import org.gradle.internal.Actions; import org.gradle.internal.resource.ResourceExceptions; import org.gradle.internal.resource.transport.http.*; import org.gradle.plugin.management.internal.PluginRequestInternal; import org.gradle.util.GradleVersion; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; public class HttpPluginResolutionServiceClient implements PluginResolutionServiceClient { private static final Escaper PATH_SEGMENT_ESCAPER = UrlEscapers.urlPathSegmentEscaper(); private static final String CLIENT_REQUEST_BASE = String.format("%s", PATH_SEGMENT_ESCAPER.escape(GradleVersion.current().getVersion())); private static final String PLUGIN_USE_REQUEST_URL = "/plugin/use/%s/%s"; private static final String JSON = "application/json"; public static final String CLIENT_STATUS_CHECKSUM_HEADER = "X-Gradle-Client-Status-Checksum"; private final SslContextFactory sslContextFactory; private HttpResourceAccessor resourceAccessor; public HttpPluginResolutionServiceClient(SslContextFactory sslContextFactory) { this(sslContextFactory, null); } public HttpPluginResolutionServiceClient(SslContextFactory sslContextFactory, HttpResourceAccessor resourceAccessor) { this.sslContextFactory = sslContextFactory; this.resourceAccessor = resourceAccessor; } @Nullable public Response<PluginUseMetaData> queryPluginMetadata(String portalUrl, boolean shouldValidate, final PluginRequestInternal pluginRequest) { String escapedId = PATH_SEGMENT_ESCAPER.escape(pluginRequest.getId().toString()); String escapedPluginVersion = PATH_SEGMENT_ESCAPER.escape(pluginRequest.getVersion()); final String requestUrl = toRequestUrl(portalUrl, String.format(PLUGIN_USE_REQUEST_URL, escapedId, escapedPluginVersion)); return request(requestUrl, PluginUseMetaData.class, new Action<PluginUseMetaData>() { public void execute(PluginUseMetaData pluginUseMetaData) { validate(requestUrl, pluginUseMetaData); } }); } public Response<ClientStatus> queryClientStatus(String portalUrl, boolean shouldValidate, String checksum) { final String requestUrl = toRequestUrl(portalUrl, ""); return request(requestUrl, ClientStatus.class, Actions.doNothing()); } private String toRequestUrl(String portalUrl, String path) { return portalUrl + "/" + CLIENT_REQUEST_BASE + path; } private <T> Response<T> request(final String requestUrl, final Class<T> type, final Action<? super T> validator) { final URI requestUri = toUri(requestUrl, "plugin request"); try { HttpResponseResource response = getResourceAccessor().getRawResource(requestUri, false); try { final int statusCode = response.getStatusCode(); String contentType = response.getContentType(); if (contentType == null || !contentType.equalsIgnoreCase(JSON)) { final String message = String.format("content type is '%s', expected '%s' (status code: %s)", contentType == null ? "" : contentType, JSON, statusCode); throw new OutOfProtocolException(requestUrl, message); } final String clientStatusChecksum = response.getHeaderValue(CLIENT_STATUS_CHECKSUM_HEADER); Reader reader = new InputStreamReader(response.openStream(), "utf-8"); try { if (statusCode == 200) { T payload = new Gson().fromJson(reader, type); validator.execute(payload); return new SuccessResponse<T>(payload, statusCode, requestUrl, clientStatusChecksum); } else if (statusCode >= 400 && statusCode < 600) { ErrorResponse errorResponse = validate(requestUrl, new Gson().fromJson(reader, ErrorResponse.class)); return new ErrorResponseResponse<T>(errorResponse, statusCode, requestUrl, clientStatusChecksum); } else { throw new OutOfProtocolException(requestUrl, "unexpected HTTP response status " + statusCode); } } catch (JsonSyntaxException e) { throw new OutOfProtocolException(requestUrl, "could not parse response JSON", e); } catch (JsonIOException e) { throw new OutOfProtocolException(requestUrl, "could not parse response JSON", e); } } finally { response.close(); } } catch (IOException e) { throw ResourceExceptions.getFailed(requestUri, e); } } public void close() { } private PluginUseMetaData validate(String url, PluginUseMetaData pluginUseMetaData) { if (pluginUseMetaData.implementationType == null) { throw new OutOfProtocolException(url, "invalid plugin metadata - no implementation type specified"); } if (!pluginUseMetaData.implementationType.equals(PluginUseMetaData.M2_JAR)) { throw new OutOfProtocolException(url, String.format("invalid plugin metadata - unsupported implementation type '%s'", pluginUseMetaData.implementationType)); } if (pluginUseMetaData.implementation == null) { throw new OutOfProtocolException(url, "invalid plugin metadata - no implementation specified"); } if (pluginUseMetaData.implementation.get("gav") == null) { throw new OutOfProtocolException(url, "invalid plugin metadata - no module coordinates specified"); } if (pluginUseMetaData.implementation.get("repo") == null) { throw new OutOfProtocolException(url, "invalid plugin metadata - no module repository specified"); } return pluginUseMetaData; } private ErrorResponse validate(String url, ErrorResponse errorResponse) { if (errorResponse.errorCode == null) { throw new OutOfProtocolException(url, "invalid error response - no error code specified"); } if (errorResponse.message == null) { throw new OutOfProtocolException(url, "invalid error response - no message specified"); } return errorResponse; } private URI toUri(String url, String kind) { try { return new URI(url); } catch (URISyntaxException e) { throw new GradleException(String.format("Invalid %s URL: %s", kind, url), e); } } private HttpResourceAccessor getResourceAccessor() { if (resourceAccessor == null) { resourceAccessor = new HttpResourceAccessor(new HttpClientHelper(new DefaultHttpSettings(Collections.<Authentication>emptyList(), sslContextFactory))); } return resourceAccessor; } private static class OutOfProtocolException extends GradleException { private OutOfProtocolException(String requestUrl, String message) { super(toMessage(requestUrl, message)); } private OutOfProtocolException(String requestUrl, String message, Throwable cause) { super(toMessage(requestUrl, message), cause); } private static String toMessage(String requestUrl, String message) { return String.format("The response from %s was not a valid response from a Gradle Plugin Resolution Service: %s", requestUrl, message); } } }