package {{invokerPackage}}; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import {{invokerPackage}}.auth.ApiKeyAuth; import {{invokerPackage}}.auth.HttpBasicAuth; import com.perforce.hwsclient.models.HWSStatus; import com.perforce.hwsclient.models.LoginResponse; import com.squareup.okhttp.Call; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import retrofit.ErrorHandler; import retrofit.RestAdapter; import retrofit.RetrofitError; import retrofit.client.OkClient; import retrofit.converter.ConversionException; import retrofit.converter.Converter; import retrofit.converter.GsonConverter; import retrofit.mime.TypedByteArray; import retrofit.mime.TypedInput; import retrofit.mime.TypedOutput; /** * The Java client which fronts the HelixWebServices REST api. * Note that this is generated from a mustache template and * should not be edited directly. */ public class ApiClient { /** Map of authorized accessors. */ private Map apiAuthorizations; /** Http request processor. */ private OkHttpClient okClient; /** Rest adapter factory. */ private RestAdapter.Builder adapterBuilder; /** Static /api path for requests. */ private static final String API_PATH = "/api"; /** Constant current version. */ private static final String SUPPORTED_VERSION = "v16.1"; /** Top level path for requests. */ private String basePath; /** * Apply configuration header overrides to our requests. *

* This method will provide the typical prefix to each configuration option, * so keys in this map should just be the configuration value name. For * example, P4PORT. * * @param overrides * A map of configuration option to configuration value. */ public void addOverrides(final Map overrides) { if (overrides != null && !overrides.isEmpty()) { adapterBuilder.setRequestInterceptor(request -> { overrides.entrySet().forEach(e -> { request.addHeader( "X-Perforce-Helix-Web-Services-" + e.getKey(), e.getValue()); }); }); } } /** * The base URL to your HWS instance. * * @return The root to your HWS server, e.g., * https://perforce.mycompany.com/hws */ public String getBasePath() { return basePath; } /** * Change the base URL to the HWS instance. * * Changes will not be reflected until createDefaultAdapter is called again. * * @param basePath * The HWS root URL */ public void setBasePath(final String basePath) { this.basePath = basePath; } /** * A special construction option that allows your client to trust all * certificates. * * By default, all HWS instances start with a self-signed certificate. You * may need to construct using this variation to validate your code is * working in a non-production system. * * @param trustAllSsl * When true, we do not validate SSL. * @param basePath * The HWS root URL */ public ApiClient(final boolean trustAllSsl, final String basePath) { this.basePath = basePath; apiAuthorizations = new LinkedHashMap<>(); createDefaultAdapter(trustAllSsl); } /** * Construct an ApiClient with the indicated basePath. * * If you are testing out a new installation, this will likely not work due * to the use of a self-signed certificate on the server. * * @param basePath * The HWS root URL */ public ApiClient(final String basePath) { this(false, basePath); } /** * Construct an ApiClient, establishing a login session with the "project" * login server. * * This will generate an ApiClient instance, and, establish the security * header by calling the POST /projects/v1/login method. * * @param basePath * The HWS root URL * @param username * The Perforce user to connect as * @param password * The Perforce password to authenticate with (discarded after * ticket is acquired) * @return The ApiClient handle ready to make authenticated requests. */ public static ApiClient createWithTicket( final String basePath, final String username, final String password) { return createWithTicket(false, basePath, username, password); } /** * Construct an ApiClient, establishing a login session with the "project" * login server. * * This will generate an ApiClient instance, and, establish the security * header by calling the POST /projects/v1/login method. * * @param trustAllSsl * When true, we do not validate SSL. * @param basePath * The HWS root URL * @param username * The Perforce user to connect as * @param password * The Perforce password to authenticate with (discarded after * ticket is acquired) * @return The ApiClient handle ready to make authenticated requests. */ public static ApiClient createWithTicket( final boolean trustAllSsl, final String basePath, final String username, final String password) { ApiClient apiClient = new ApiClient(trustAllSsl, basePath); apiClient.setCredentials(username, password); DefaultApi api = apiClient.createDefaultService(); LoginResponse loginResponse = api.login(); apiClient.setApiKey(loginResponse.getTicket()); return apiClient; } /** * Checks status of the API against the currently configured server. * * @return true if the server is not currently reporting any problems. */ public boolean isOK() { try { HWSStatus status = getStatus(); return status != null && "OK".equals(status.getStatus()); } catch (Exception e) { return false; } } /** * Gets the status. * * @return the status */ public HWSStatus getStatus() { return createDefaultService().getStatus(); } /** * Will do a custom fetch of the URL at "/api", and will see if our * API_PATH string pops up in the mix. * * @return False if the server does not tell us we're a valid version. * (Might happen if you can't access the server too.) */ public boolean isSupported() { try { Request request = new Request.Builder().url(basePath + "/api/hws/version").method("GET", null) .addHeader("Accept", "application/json").build(); Call call = okClient.newCall(request); Response response = call.execute(); Gson gson = new Gson(); Type mapType = new TypeToken>() { }.getType(); Map info = gson.fromJson(response.body().string(), mapType); @SuppressWarnings("unchecked") List versions = (List) info.get("supportedVersions"); return versions.stream().anyMatch(v -> SUPPORTED_VERSION.equals(v)); } catch (Exception e) { return false; } } /** * Initializes the adapterBuilder, typically called during construction. * * @param trustAllSsl * when true will configure the adapter to trust all connections. */ public void createDefaultAdapter(final boolean trustAllSsl) { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create(); okClient = new OkHttpClient(); if (trustAllSsl) { // Create a trust manager that does not validate certificate chains final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted( final java.security.cert.X509Certificate[] chain, final String authType) throws CertificateException { } @Override public void checkServerTrusted( final java.security.cert.X509Certificate[] chain, final String authType) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[] {}; } } }; // Install the all-trusting trust manager final SSLContext sslContext; try { sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } // Create an ssl socket factory with our all-trusting manager final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); okClient.setSslSocketFactory(sslSocketFactory); okClient.setHostnameVerifier((hostname, session) -> true); } adapterBuilder = new RestAdapter.Builder().setEndpoint(basePath + API_PATH) .setClient(new OkClient(okClient)) .setErrorHandler(new ErrorHandler() { @Override public Throwable handleError(final RetrofitError arg0) { return new HwsRestException(arg0); } }) .setConverter(new GsonConverterWrapper(gson)); } /** * Obtain a handle to the service object to interact with HWS. * * @return The primary API object to interact with HWS. */ public DefaultApi createDefaultService() { return createService(DefaultApi.class); } /** * Create a service object to interact with the server. * * In general, you'll probably just want the DefaultApi here. * * @param * The interface API (probably just DefaultApi.class) * @param serviceClass * The interface class indicating the API you want to use. * @return The interface object to interact with the remote server. */ public S createService(final Class serviceClass) { return adapterBuilder.build().create(serviceClass); } /** * Helper method to configure the username/password for basic auth or * password oauth. * * @param username * The Perforce login * @param ticket * The Perforce ticket (not the user's password) */ public void setCredentials(final String username, final String ticket) { HttpBasicAuth auth = new HttpBasicAuth(); auth.setUsername(username); auth.setPassword(ticket); updateAuthorization("ticket_auth", auth); } /** * Helper method to set the API key as the "ticket_auth" scheme. * * @param apiKey the token to set for authorization */ public void setApiKey(final String apiKey) { ApiKeyAuth auth = new ApiKeyAuth("header", "Authorization"); auth.setApiKey(apiKey); updateAuthorization("ticket_auth", auth); } /** * Add an authorization token. * @param authName the name associated with a token * @param authorization the token */ private void updateAuthorization(final String authName, final Interceptor authorization) { okClient.interceptors().clear(); okClient.interceptors().add(authorization); apiAuthorizations.put(authName, authorization); } /** * Get the adapter builder. * @return the current builder of adapters. */ public RestAdapter.Builder getAdapterBuilder() { return adapterBuilder; } /** * Set a new adapter builder. * @param adapterBuilder the new adapter builder to use */ public void setAdapterBuilder(final RestAdapter.Builder adapterBuilder) { this.adapterBuilder = adapterBuilder; } /** * Get the http client. * @return the current http client */ public OkHttpClient getOkClient() { return okClient; } /** * Set the current authorizations in the http client. * @param okClient the http client to be modified. */ public void addAuthsToOkClient(final OkHttpClient okClient) { for (Interceptor apiAuthorization : apiAuthorizations.values()) { okClient.interceptors().add(apiAuthorization); } } /** * Clones the okClient given in parameter, adds the auth interceptors and * uses it to configure the RestAdapter. * * @param okClient * The OKHttpClient to clone */ public void configureFromOkclient(final OkHttpClient okClient) { OkHttpClient clone = okClient.clone(); addAuthsToOkClient(clone); adapterBuilder.setClient(new OkClient(clone)); } /** * Wrapper class for the retrofit error to indicate that it was * handled by a perforce api. */ public class HwsRestException extends RuntimeException { /** Generated id, not expected to change. */ private static final long serialVersionUID = -8177702544598405834L; /** The original retrofit error. */ private final RetrofitError retrofitError; /** * Instantiate a new exception to wrap the retrofit error. * @param retrofitError the original rest error */ HwsRestException(final RetrofitError retrofitError) { super(retrofitError.getLocalizedMessage()); this.retrofitError = retrofitError; } /** * Get the original retrofit error, for further analysis. * @return RetrofitError the original error */ public RetrofitError getRetrofitError() { return retrofitError; } } } /** * This wrapper is to take care of this case: when the deserialization fails due * to JsonParseException and the expected type is String, then just return the * body string. */ class GsonConverterWrapper implements Converter { /** The actual converter instance. */ private GsonConverter converter; /** * Constructor with a converter instance. * @param gson the java <-> json converter */ GsonConverterWrapper(final Gson gson) { converter = new GsonConverter(gson); } @Override public Object fromBody(final TypedInput body, final Type type) throws ConversionException { byte[] bodyBytes = readInBytes(body); TypedByteArray newBody = new TypedByteArray(body.mimeType(), bodyBytes); try { return converter.fromBody(newBody, type); } catch (ConversionException e) { if (e.getCause() instanceof JsonParseException && type.equals(String.class)) { return new String(bodyBytes); } else { throw e; } } } @Override public TypedOutput toBody(final Object object) { return converter.toBody(object); } /** * Read a content body one byte at a time. * @param body the content * @return a byte array equivalent of the content * @throws ConversionException if it cannot be converted */ private byte[] readInBytes(final TypedInput body) throws ConversionException { InputStream in = null; try { in = body.in(); ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[0xFFFF]; for (int len; (len = in.read(buffer)) != -1;) { os.write(buffer, 0, len); } os.flush(); return os.toByteArray(); } catch (IOException e) { throw new ConversionException(e); } finally { if (in != null) { try { in.close(); } catch (IOException ignored) { } } } } }