/*******************************************************************************
* Copyright (c) 2006-2011 Gluster, Inc. <http://www.gluster.com>
* This file is part of Gluster Management Console.
*
* Gluster Management Console is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Gluster Management Console is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*******************************************************************************/
package org.gluster.storage.management.client;
import static org.gluster.storage.management.client.constants.ClientConstants.ALGORITHM_SUNX509;
import static org.gluster.storage.management.client.constants.ClientConstants.KEYSTORE_TYPE_JKS;
import static org.gluster.storage.management.client.constants.ClientConstants.PROTOCOL_TLS;
import static org.gluster.storage.management.client.constants.ClientConstants.TRUSTED_KEYSTORE;
import static org.gluster.storage.management.client.constants.ClientConstants.TRUSTED_KEYSTORE_ACCESS;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.URI;
import java.security.KeyStore;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.gluster.storage.management.client.utils.ClientUtil;
import org.gluster.storage.management.core.exceptions.GlusterRuntimeException;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.representation.Form;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.multipart.FormDataMultiPart;
public abstract class AbstractClient {
private static final String HTTP_HEADER_AUTH = "Authorization";
protected static final MultivaluedMap<String, String> NO_PARAMS = new MultivaluedMapImpl();
protected static String clusterName;
protected static String securityToken;
protected WebResource resource;
private String authHeader;
private Client client;
/**
* This constructor will work only after the data model manager has been initialized.
*/
public AbstractClient() {
this(securityToken, clusterName);
}
/**
* This constructor will work only after the data model manager has been initialized.
*/
public AbstractClient(String clusterName) {
this(securityToken, clusterName);
}
public AbstractClient(String securityToken, String clusterName) {
AbstractClient.clusterName = clusterName;
setSecurityToken(securityToken);
createClient();
// this must be after setting clusterName as sub-classes may refer to cluster name in the getResourcePath method
resource = client.resource(ClientUtil.getServerBaseURI()).path(getResourcePath());
}
private void createClient() {
SSLContext context = initializeSSLContext();
DefaultClientConfig config = createClientConfig(context);
client = Client.create(config);
}
private DefaultClientConfig createClientConfig(SSLContext context) {
DefaultClientConfig config = new DefaultClientConfig();
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
new HTTPSProperties(createHostnameVerifier(), context));
return config;
}
private HostnameVerifier createHostnameVerifier() {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
};
return hostnameVerifier;
}
private SSLContext initializeSSLContext() {
SSLContext context = null;
try {
context = SSLContext.getInstance(PROTOCOL_TLS);
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS);
keyStore.load(loadResource(TRUSTED_KEYSTORE), TRUSTED_KEYSTORE_ACCESS.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ALGORITHM_SUNX509);
keyManagerFactory.init(keyStore, TRUSTED_KEYSTORE_ACCESS.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(ALGORITHM_SUNX509);
trustManagerFactory.init(keyStore);
context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (Exception e) {
throw new GlusterRuntimeException(
"Couldn't initialize SSL Context with Gluster Management Gateway! Error: " + e, e);
}
return context;
}
private InputStream loadResource(String resourcePath) {
return this.getClass().getClassLoader().getResourceAsStream(resourcePath);
}
/**
* Fetches the given resource by dispatching a GET request
*
* @param res
* Resource to be fetched
* @param queryParams
* Query parameters to be sent for the GET request
* @param responseClass
* Expected class of the response
* @return Object of responseClass received as a result of the GET request
*/
private <T> T fetchResource(WebResource res, MultivaluedMap<String, String> queryParams, Class<T> responseClass) {
try {
return res.queryParams(queryParams).header(HTTP_HEADER_AUTH, authHeader).accept(MediaType.APPLICATION_XML)
.get(responseClass);
} catch (Exception e1) {
throw createGlusterException(e1);
}
}
private GlusterRuntimeException createGlusterException(Exception e) {
if (e instanceof GlusterRuntimeException) {
return (GlusterRuntimeException) e;
}
if (e instanceof UniformInterfaceException) {
UniformInterfaceException uie = (UniformInterfaceException) e;
if ((uie.getResponse().getStatus() == Response.Status.UNAUTHORIZED.getStatusCode())) {
// authentication failed. clear security token.
setSecurityToken(null);
return new GlusterRuntimeException("Invalid credentials!");
} else {
return new GlusterRuntimeException("[" + uie.getResponse().getStatus() + "]["
+ uie.getResponse().getEntity(String.class) + "]");
}
} else {
Throwable cause = e.getCause();
if (cause != null && cause instanceof ConnectException) {
return new GlusterRuntimeException("Couldn't connect to Gluster Management Gateway!");
}
return new GlusterRuntimeException("Exception in REST communication! [" + e.getMessage() + "]", e);
}
}
protected void downloadResource(WebResource res, String filePath) {
ClientResponse response = null;
try {
response = res.header(HTTP_HEADER_AUTH, authHeader).accept(MediaType.APPLICATION_OCTET_STREAM)
.get(ClientResponse.class);
checkResponseStatus(response);
} catch (Exception e1) {
throw createGlusterException(e1);
}
try {
if (!response.hasEntity()) {
throw new GlusterRuntimeException("No entity in response!");
}
InputStream inputStream = response.getEntityInputStream();
FileOutputStream outputStream = new FileOutputStream(filePath);
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
inputStream.close();
outputStream.close();
} catch (IOException e) {
throw new GlusterRuntimeException("Error while downloading resource [" + res.getURI().getPath() + "]", e);
}
}
public void uploadResource(WebResource res, FormDataMultiPart form) {
try {
res.header(HTTP_HEADER_AUTH, authHeader).type(MediaType.MULTIPART_FORM_DATA_TYPE).post(String.class, form);
} catch (Exception e) {
throw createGlusterException(e);
}
}
/**
* Fetches the default resource (the one returned by {@link AbstractClient#getResourcePath()}) by dispatching a GET
* request on the resource
*
* @param queryParams
* Query parameters to be sent for the GET request
* @param responseClass
* Expected class of the response
* @return Object of responseClass received as a result of the GET request
*/
protected <T> T fetchResource(MultivaluedMap<String, String> queryParams, Class<T> responseClass) {
return fetchResource(resource, queryParams, responseClass);
}
/**
* Fetches the default resource (the one returned by {@link AbstractClient#getResourcePath()}) by dispatching a GET
* request on the resource
*
* @param responseClass
* Expected class of the response
* @return Object of responseClass received as a result of the GET request
*/
protected <T> T fetchResource(Class<T> responseClass) {
return fetchResource(resource, NO_PARAMS, responseClass);
}
/**
* Fetches the resource whose name is arrived at by appending the "subResourceName" parameter to the default
* resource (the one returned by {@link AbstractClient#getResourcePath()})
*
* @param subResourceName
* Name of the sub-resource
* @param responseClass
* Expected class of the response
* @return Object of responseClass received as a result of the GET request on the sub-resource
*/
protected <T> T fetchSubResource(String subResourceName, Class<T> responseClass) {
return fetchResource(resource.path(subResourceName), NO_PARAMS, responseClass);
}
protected void downloadSubResource(String subResourceName, String filePath) {
downloadResource(resource.path(subResourceName), filePath);
}
/**
* Fetches the resource whose name is arrived at by appending the "subResourceName" parameter to the default
* resource (the one returned by {@link AbstractClient#getResourcePath()})
*
* @param subResourceName
* Name of the sub-resource
* @param queryParams
* Query parameters to be sent for the GET request
* @param responseClass
* Expected class of the response
* @return Object of responseClass received as a result of the GET request on the sub-resource
*/
protected <T> T fetchSubResource(String subResourceName, MultivaluedMap<String, String> queryParams,
Class<T> responseClass) {
return fetchResource(resource.path(subResourceName), queryParams, responseClass);
}
private ClientResponse postRequest(WebResource resource, Form form) {
try {
ClientResponse response = prepareFormRequestBuilder(resource).post(ClientResponse.class, form);
checkResponseStatus(response);
return response;
} catch (UniformInterfaceException e) {
throw new GlusterRuntimeException(e.getResponse().getEntity(String.class));
}
}
/**
* Submits given object to the resource and returns the object received as response
*
* @param responseClass
* Class of the object expected as response
* @param requestObject
* the Object to be submitted
* @return Object of given class received as response
*/
protected <T> T postObject(Class<T> responseClass, Object requestObject) {
return resource.type(MediaType.APPLICATION_XML).header(HTTP_HEADER_AUTH, authHeader)
.accept(MediaType.APPLICATION_XML).post(responseClass, requestObject);
}
/**
* Submits given Form using POST method to the resource and returns the object received as response
*
* @param form
* Form to be submitted
*/
protected URI postRequest(Form form) {
return postRequest(resource, form).getLocation();
}
/**
* Submits given Form using POST method to the given sub-resource and returns the object received as response
*
* @param subResourceName
* Name of the sub-resource to which the request is to be posted
* @param form
* Form to be submitted
*/
protected void postRequest(String subResourceName, Form form) {
postRequest(resource.path(subResourceName), form);
}
private ClientResponse putRequest(WebResource resource, Form form) {
try {
ClientResponse response = prepareFormRequestBuilder(resource).put(ClientResponse.class, form);
checkResponseStatus(response);
return response;
} catch (Exception e) {
throw createGlusterException(e);
}
}
private void checkResponseStatus(ClientResponse response) {
if ((response.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode())) {
// authentication failed. clear security token.
setSecurityToken(null);
throw new GlusterRuntimeException("Invalid credentials!");
}
if (response.getStatus() >= 300) {
throw new GlusterRuntimeException(response.getEntity(String.class));
}
}
public Builder prepareFormRequestBuilder(WebResource resource) {
return resource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).header(HTTP_HEADER_AUTH, authHeader)
.accept(MediaType.APPLICATION_XML);
}
/**
* Submits given Form using PUT method to the given sub-resource and returns the object received as response
*
* @param subResourceName
* Name of the sub-resource to which the request is to be posted
* @param form
* Form to be submitted
*/
protected void putRequest(String subResourceName, Form form) {
putRequest(resource.path(subResourceName), form);
}
protected URI putRequestURI(String subResourceName, Form form) {
ClientResponse response = putRequest(resource.path(subResourceName), form);
return response.getLocation();
}
/**
* Submits given Form using PUT method to the given sub-resource and returns the object received as response
*
* @param form
* Form to be submitted
*/
protected void putRequest(Form form) {
putRequest(resource, form);
}
/**
* Submits given Form using PUT method to the given sub-resource and returns the object received as response
*
* @param subResourceName
* Name of the sub-resource to which the request is to be posted
*/
protected void putRequest(String subResourceName) {
try {
prepareFormRequestBuilder(resource.path(subResourceName)).put();
} catch (UniformInterfaceException e) {
throw new GlusterRuntimeException(e.getResponse().getEntity(String.class));
}
}
private void deleteResource(WebResource resource, MultivaluedMap<String, String> queryParams) {
try {
resource.queryParams(queryParams).header(HTTP_HEADER_AUTH, authHeader).delete();
} catch (UniformInterfaceException e) {
throw new GlusterRuntimeException(e.getResponse().getEntity(String.class));
}
}
protected void deleteResource(MultivaluedMap<String, String> queryParams) {
deleteResource(resource, queryParams);
}
protected void deleteSubResource(String subResourceName, MultivaluedMap<String, String> queryParams) {
deleteResource(resource.path(subResourceName), queryParams);
}
protected void deleteSubResource(String subResourceName) {
try {
resource.path(subResourceName).header(HTTP_HEADER_AUTH, authHeader).delete();
} catch (UniformInterfaceException e) {
throw new GlusterRuntimeException(e.getResponse().getEntity(String.class));
}
}
public abstract String getResourcePath();
/**
* @return the securityToken
*/
protected String getSecurityToken() {
return securityToken;
}
/**
* @param securityToken
* the securityToken to set
*/
protected void setSecurityToken(String securityToken) {
AbstractClient.securityToken = securityToken;
authHeader = "Basic " + securityToken;
}
/**
* @param uri
* The URI to be fetched using GET API
* @param responseClass
* Expected type of response object
* @return Object of the given class
*/
protected <T> T fetchResource(URI uri, Class<T> responseClass) {
return fetchResource(client.resource(uri), NO_PARAMS, responseClass);
}
}