/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.protocol.sip.xcap;
import java.io.*;
import java.net.*;
import java.net.URI;
import javax.sip.address.*;
import net.java.sip.communicator.impl.protocol.sip.*;
import net.java.sip.communicator.impl.protocol.sip.xcap.model.*;
import net.java.sip.communicator.impl.protocol.sip.xcap.model.xcaperror.*;
import net.java.sip.communicator.impl.protocol.sip.xcap.utils.*;
import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.httputil.*;
import net.java.sip.communicator.util.*;
import org.apache.http.*;
import org.apache.http.auth.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.*;
import org.apache.http.impl.client.*;
import org.osgi.framework.*;
/**
* Base HTTP XCAP client implementation.
* <p/>
* Compliant with rfc4825
*
* @author Grigorii Balutsel
*/
public abstract class BaseHttpXCapClient implements HttpXCapClient
{
/**
* Class logger.
*/
private static final Logger logger =
Logger.getLogger(BaseHttpXCapClient.class);
/**
* HTTP Content-Type header.
*/
public static final String HEADER_CONTENT_TYPE = "Content-Type";
/**
* HTTP ETag header.
*/
public static final String HEADER_ETAG = "ETag";
/**
* HTTP If-None-Match header.
*/
public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
/**
* XCap-error content type.
*/
public static final String XCAP_ERROR_CONTENT_TYPE
= "application/xcap-error+xml";
/**
* Current server uri.
*/
protected URI uri;
/**
* Current user.
*/
protected Address userAddress;
/**
* Current user loginname.
*/
private String username;
/**
* Current user password.
*/
private String password;
/**
* Indicates whether or not client is connected.
*/
private boolean connected;
/**
* The service we use to interact with user regarding certificates.
*/
private CertificateService certificateVerification;
/**
* Creates an instance of this XCAP client.
*/
public BaseHttpXCapClient()
{
ServiceReference guiVerifyReference
= SipActivator.getBundleContext().getServiceReference(
CertificateService.class.getName());
if(guiVerifyReference != null)
certificateVerification
= (CertificateService)SipActivator.getBundleContext()
.getService(guiVerifyReference);
}
/**
* Connects user to XCap server.
*
* @param uri the server location.
* @param userAddress the URI of the user used for requests
* @param username the user name.
* @param password the user password.
* @throws XCapException if there is some error during operation.
*/
public void connect(URI uri, Address userAddress, String username, String password)
throws XCapException
{
if (!userAddress.getURI().isSipURI())
{
throw new IllegalArgumentException("Address must contains SipUri");
}
this.uri = uri;
this.userAddress = (Address) userAddress.clone();
this.username = username;
this.password = password == null ? "" : password;
connected = true;
}
/**
* Checks if user is connected to the XCAP server.
*
* @return true if user is connected.
*/
public boolean isConnected()
{
return connected;
}
/**
* Disconnects user from the XCAP server.
*/
public void disconnect()
{
this.uri = null;
this.userAddress = null;
this.password = null;
connected = false;
}
/**
* Gets the resource from the server.
*
* @param resourceId resource identifier.
* @return the server response.
* @throws IllegalStateException if the user has not been connected.
* @throws XCapException if there is some error during operation.
*/
public XCapHttpResponse get(XCapResourceId resourceId)
throws XCapException
{
return get(getResourceURI(resourceId));
}
/**
* Gets resource from the server.
*
* @param uri the resource uri.
* @return the server response.
* @throws XCapException if there is error during reading the resource's
* content.
*/
protected XCapHttpResponse get(URI uri)
throws XCapException
{
DefaultHttpClient httpClient = null;
try
{
httpClient = createHttpClient();
HttpGet getMethod = new HttpGet(uri);
getMethod.setHeader("Connection", "close");
HttpResponse response = httpClient.execute(getMethod);
XCapHttpResponse result = createResponse(response);
if (logger.isDebugEnabled())
{
byte[] contentBytes = result.getContent();
String contenString;
// for debug purposes print only xmls
// skip the icon queries
if(contentBytes != null && result.getContentType() != null
&& !result.getContentType()
.startsWith(PresContentClient.CONTENT_TYPE))
contenString = new String(contentBytes);
else
contenString = "";
String logMessage = String.format(
"Getting resource %1s from the server content:%2s",
uri.toString(),
contenString
);
logger.debug(logMessage);
}
return result;
}
catch(UnknownHostException uhe)
{
showError(uhe, null, null);
disconnect();
throw new XCapException(uhe.getMessage(), uhe);
}
catch (IOException e)
{
String errorMessage =
SipActivator.getResources().getI18NString(
"impl.protocol.sip.XCAP_ERROR_RESOURCE_ERR",
new String[]{uri.toString(),
userAddress.getDisplayName()});
showError(e, null, errorMessage);
throw new XCapException(errorMessage, e);
}
finally
{
if(httpClient != null)
httpClient.getConnectionManager().shutdown();
}
}
/**
* Shows an error and a short description.
* @param ex the exception
*/
static void showError(Exception ex, String title, String message)
{
try
{
if(title == null)
title = SipActivator.getResources().getI18NString(
"impl.protocol.sip.XCAP_ERROR_TITLE");
if(message == null)
message = title + "\n" +
ex.getClass().getName() + ": " +
ex.getLocalizedMessage();
if(SipActivator.getUIService() != null)
SipActivator.getUIService().getPopupDialog()
.showMessagePopupDialog(
message,
title,
PopupDialog.ERROR_MESSAGE);
}
catch(Throwable t)
{
logger.error("Error for error dialog", t);
}
}
/**
* Puts the resource to the server.
*
* @param resource the resource to be saved on the server.
* @return the server response.
* @throws IllegalStateException if the user has not been connected.
* @throws XCapException if there is some error during operation.
*/
public XCapHttpResponse put(XCapResource resource)
throws XCapException
{
DefaultHttpClient httpClient = null;
try
{
httpClient = createHttpClient();
URI resourceUri = getResourceURI(resource.getId());
HttpPut putMethod = new HttpPut(resourceUri);
putMethod.setHeader("Connection", "close");
StringEntity stringEntity = new StringEntity(resource.getContent());
stringEntity.setContentType(resource.getContentType());
stringEntity.setContentEncoding("UTF-8");
putMethod.setEntity(stringEntity);
if (logger.isDebugEnabled())
{
String logMessage = String.format(
"Puting resource %1s to the server %2s",
resource.getId().toString(),
resource.getContent()
);
logger.debug(logMessage);
}
HttpResponse response = httpClient.execute(putMethod);
return createResponse(response);
}
catch (IOException e)
{
String errorMessage = String.format(
"%1s resource cannot be put",
resource.getId().toString());
throw new XCapException(errorMessage, e);
}
finally
{
if(httpClient != null)
httpClient.getConnectionManager().shutdown();
}
}
/**
* Deletes the resource from the server.
*
* @param resourceId resource identifier.
* @return the server response.
* @throws IllegalStateException if the user has not been connected.
* @throws XCapException if there is some error during operation.
*/
public XCapHttpResponse delete(XCapResourceId resourceId)
throws XCapException
{
assertConnected();
DefaultHttpClient httpClient = null;
try
{
httpClient = createHttpClient();
URI resourceUri = getResourceURI(resourceId);
HttpDelete deleteMethod = new HttpDelete(resourceUri);
deleteMethod.setHeader("Connection", "close");
if (logger.isDebugEnabled())
{
String logMessage = String.format(
"Deleting resource %1s from the server",
resourceId.toString()
);
logger.debug(logMessage);
}
HttpResponse response = httpClient.execute(deleteMethod);
return createResponse(response);
}
catch (IOException e)
{
String errorMessage = String.format(
"%1s resource cannot be deleted",
resourceId.toString());
throw new XCapException(errorMessage, e);
}
finally
{
if(httpClient != null)
httpClient.getConnectionManager().shutdown();
}
}
/**
* Gets user name.
*
* @return the user name.
*/
public String getUserName()
{
return username;
}
/**
* Gets server uri.
*
* @return the server uri.
*/
public URI getUri()
{
return uri;
}
/**
* Utility method throwing an exception if the user is not connected.
*
* @throws IllegalStateException if the user is not connected.
*/
protected void assertConnected()
{
if (!connected)
{
throw new IllegalStateException(
"User is not connected to the server");
}
}
/**
* Gets resource uri from XCAP resource identifier.
*
* @param resourceId the resource identifier.
* @return the resource uri.
*/
protected URI getResourceURI(XCapResourceId resourceId)
{
try
{
return new URI(uri.toString() + "/" + resourceId);
}
catch (URISyntaxException e)
{
throw new IllegalArgumentException(
"Invalid XCAP resource identifier", e);
}
}
/**
* Creates HTTP client with special parameters.
*
* @return the HTTP client.
*/
private DefaultHttpClient createHttpClient()
throws IOException
{
XCapCredentialsProvider credentialsProvider
= new XCapCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(getUserName(), password));
return HttpUtils.getHttpClient(
null , null, uri.getHost(), credentialsProvider);
}
/**
* Creates XCAP response from HTTP response.
* If HTTP code is 200, 201 or 409 the HTTP content would be read.
*
* @param response the HTTP response.
* @return the XCAP response.
* @throws IOException if there is error during reading the HTTP content.
*/
private XCapHttpResponse createResponse(HttpResponse response)
throws IOException
{
XCapHttpResponse xcapHttpResponse = new XCapHttpResponse();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK ||
statusCode == HttpStatus.SC_CREATED ||
statusCode == HttpStatus.SC_CONFLICT)
{
String contentType = getSingleHeaderValue(response,
HEADER_CONTENT_TYPE);
byte[] content = StreamUtils.read(
response.getEntity().getContent());
String eTag = getSingleHeaderValue(response, HEADER_ETAG);
xcapHttpResponse.setContentType(contentType);
xcapHttpResponse.setContent(content);
xcapHttpResponse.setETag(eTag);
}
xcapHttpResponse.setHttpCode(statusCode);
return xcapHttpResponse;
}
/**
* Reads response from http.
* @param response the response
* @return the result String.
* @throws IOException
*/
private static String readResponse(HttpResponse response)
throws IOException
{
HttpEntity responseEntity = response.getEntity();
if (responseEntity.getContentLength() == 0)
{
return "";
}
byte[] content = StreamUtils.read(responseEntity.getContent());
return new String(content, "UTF-8");
}
/**
* Gets HTTP header value.
*
* @param response the HTTP response.
* @param headerName the header name.
* @return the header value.
*/
protected static String getSingleHeaderValue(
HttpResponse response,
String headerName)
{
Header[] headers = response.getHeaders(headerName);
if (headers != null && headers.length > 0)
{
return headers[0].getValue();
}
return null;
}
/**
* Analyzes the response and returns xcap error or null
* if response doesn't have it.
*
* @param response the server response.
* @return xcap error or null.
*/
protected String getXCapErrorMessage(XCapHttpResponse response)
{
int httpCode = response.getHttpCode();
String contentType = response.getContentType();
try
{
if (httpCode != HttpStatus.SC_CONFLICT || contentType == null ||
!contentType.startsWith(XCAP_ERROR_CONTENT_TYPE))
{
return null;
}
String content = new String(response.getContent());
XCapErrorType xCapError = XCapErrorParser.fromXml(content);
XCapError error = xCapError.getError();
if (error == null)
{
return null;
}
return error.getPhrase();
}
catch (ParsingException e)
{
logger.error("XCapError cannot be parsed.");
return null;
}
}
/**
* Our credentials provider simple impl.
*/
private class XCapCredentialsProvider
implements CredentialsProvider
{
/**
* The credentials to use.
*/
private Credentials credentials;
/**
* Sets credentials no matter of the scope.
* @param authscope the scope is not used.
* @param credentials the credentials to use
*/
public void setCredentials(AuthScope authscope,
Credentials credentials)
{
this.credentials = credentials;
}
/**
* Returns the credentials no matter of the scope.
* @param authscope not important
* @return the credentials.
*/
public Credentials getCredentials(AuthScope authscope)
{
return credentials;
}
/**
* Clears credentials.
*/
public void clear()
{
credentials = null;
}
}
}