/* * Copyright 2013 Qubell, Inc. * * 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.qubell.services.ws; import com.qubell.jenkinsci.plugins.qubell.Configuration; import com.qubell.jenkinsci.plugins.qubell.JsonParser; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.jaxrs.impl.RuntimeDelegateImpl; import org.apache.cxf.transport.http.HTTPConduit; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.ws.rs.HttpMethod; import javax.ws.rs.client.ClientException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.RuntimeDelegate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; /** * Base Web Service for Qubell API integration * * @author Alex Krupnov */ public abstract class WebServiceBase { /** * App configuration */ protected final Configuration configuration; public WebServiceBase(Configuration configuration) { this.configuration = configuration; } /** * Returns an Apache CXF Web Client * * @return client new instance */ protected WebClient getWebClient() { setRuntimeDelegate(); List<Object> providerList = new ArrayList<Object>(); providerList.add(new org.codehaus.jackson.jaxrs.JacksonJsonProvider()); String url = configuration.getUrl(); if (!url.endsWith("/")) { url = url.concat("/"); } WebClient client = WebClient.create(url.concat("api/1/"), providerList); if (configuration.isSkipCertificateChecks()) { configurePassThroughSSLCheck(client); } // Replace 'user' and 'password' by the actual values String authorizationHeader = "Basic " + org.apache.cxf.common.util.Base64Utility.encode(String.format("%s:%s", configuration.getLogin(), configuration.getPassword()).getBytes()); client.header("Authorization", authorizationHeader); client.accept(MediaType.APPLICATION_JSON_TYPE); client.header("Content-Type", MediaType.APPLICATION_JSON_TYPE); if (configuration.isEnableMessageLogging()) { WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor()); WebClient.getConfig(client).getOutInterceptors().add(new LoggingOutInterceptor()); } return client; } private void configurePassThroughSSLCheck(WebClient client) { TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { //do nothing, you're the client } public X509Certificate[] getAcceptedIssuers() { return null; } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } }; System.setProperty("jsse.enableSNIExtension", "false"); HTTPConduit conduit = WebClient.getConfig(client) .getHttpConduit(); TLSClientParameters params = conduit.getTlsClientParameters(); if (params == null) { params = new TLSClientParameters(); } params.setTrustManagers(new TrustManager[]{tm}); params.setDisableCNCheck(true); conduit.setTlsClientParameters(params); } //Due to jenkins container issue, Apache CXF runtime delegate is not found and hence has to be set manually private void setRuntimeDelegate() { try { RuntimeDelegate.getInstance(); } catch (Exception e) { RuntimeDelegate.setInstance(new RuntimeDelegateImpl()); } } /** * Invokes a specified method on http client, doing retry logic for SSL context * @param method method to invoke * @param client prepared client * @param responseClass response class * @param <T> type of response class * @return response object */ protected <T> T invoke(String method, WebClient client, Class<T> responseClass) { return invoke(method, client, null, responseClass); } /** * Invokes a specified method on http client, doing retry logic for SSL context * @param method method to invoke * @param client prepared client * @param body optional request body * @param responseClass response class * @param <T> type of response class * @return response object */ protected <T> T invoke(String method, WebClient client, Object body, Class<T> responseClass) { int attempt = 0; if (method.equals(HttpMethod.POST)) { return client.post(body, responseClass); } if(method.equals(HttpMethod.PUT)){ return client.put(body, responseClass); } while (true) { try { if (method.equals(HttpMethod.GET)) { return client.get(responseClass); } } catch (ClientException ex) { if (attempt > 5 || !causedBySsl(ex)) { throw ex; } attempt++; } } } protected <T> Collection<? extends T> invokeAndGetCollection(String method, WebClient client, Object body, Class<T> memberClass) { int attempt = 0; while (true) { try { if (method.equals(HttpMethod.GET)) { return client.getCollection(memberClass); } if (method.equals(HttpMethod.POST)) { return client.postAndGetCollection(body, memberClass); } } catch (ClientException ex) { if (attempt > 5 || !causedBySsl(ex)) { throw ex; } attempt++; } } } protected String parseJsonErrorMessage(Response response, String defaultMessage) { return isContentTypeJson(response) && hasBodyContent(response) ? prepareErrorMessage(parseJsonErrors(response.getEntity())) : defaultMessage; } private String prepareErrorMessage(List<String> errors) { StringBuilder sb = new StringBuilder(); for (String error : errors) { sb.append(error).append("\n"); } // remove last \n sb.deleteCharAt(sb.length() - 1); return sb.toString(); } private List<String> parseJsonErrors(Object entity) { String body = String.valueOf(entity); Map<String, Object> jsonMap = JsonParser.parseMap(body); return (List<String>) (jsonMap != null && jsonMap.get("errors") != null && jsonMap.get("errors") instanceof List ? jsonMap.get("errors") : Collections.singletonList(body)); } private boolean isContentTypeJson(Response response) { String contentType = response.getHeaderString("Content-Type"); return contentType != null && contentType.startsWith("application/json"); } private boolean hasBodyContent(Response response) { return response.getEntity() != null; } private boolean causedBySsl(Throwable ex) { return ex instanceof SSLException || ex.getCause() != null && causedBySsl(ex.getCause()); } }