blob: be0c3cdcfd150315b2cce533d388c9d5c3bf820c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.oslc.core.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.net.AbstractWebLocation;
import org.eclipse.mylyn.commons.net.Policy;
import org.eclipse.mylyn.commons.net.WebUtil;
import org.eclipse.mylyn.internal.oslc.core.IOslcCoreConstants;
import org.eclipse.mylyn.internal.oslc.core.OslcCreationDialogDescriptor;
import org.eclipse.mylyn.internal.oslc.core.OslcSelectionDialogDescriptor;
import org.eclipse.mylyn.internal.oslc.core.OslcServiceDescriptor;
import org.eclipse.mylyn.internal.oslc.core.OslcServiceFactory;
import org.eclipse.mylyn.internal.oslc.core.OslcServiceProvider;
import org.eclipse.mylyn.internal.oslc.core.OslcServiceProviderCatalog;
import org.eclipse.mylyn.internal.oslc.core.ServiceHome;
import org.eclipse.mylyn.internal.oslc.core.cm.AbstractChangeRequest;
import org.eclipse.mylyn.tasks.core.RepositoryResponse;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.filter.ElementFilter;
import org.jdom.input.SAXBuilder;
/**
* Base class from which to implement an OSLC client
*
* @author Robert Elves
*/
public abstract class AbstractOslcClient {
protected final AbstractWebLocation location;
protected final HttpClient httpClient;
protected final OslcServiceDescriptor configuration;
public AbstractOslcClient(AbstractWebLocation location, OslcServiceDescriptor data) {
this.location = location;
this.httpClient = createHttpClient();
this.configuration = data;
}
protected HttpClient createHttpClient() {
HttpClient httpClient = new HttpClient();
httpClient.setHttpConnectionManager(WebUtil.getConnectionManager());
httpClient.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
// See: https://jazz.net/jazz/web/projects/Rational%20Team%20Concert#action=com.ibm.team.workitem.viewWorkItem&id=85127\
// Added to support fix session cookie issue when talking to tomcat
httpClient.getParams().setParameter("http.protocol.single-cookie-header", true); //$NON-NLS-1$
WebUtil.configureHttpClient(httpClient, getUserAgent());
return httpClient;
}
/**
* Return your unique connector identifier i.e. com.mycompany.myconnector
*/
public abstract String getUserAgent();
/**
* Exposed at connector level via IOslcCoreConnector.getAvailableServices()
*/
public List<OslcServiceProvider> getAvailableServices(String url, IProgressMonitor monitor) throws CoreException {
RequestHandler<List<OslcServiceProvider>> handler = new RequestHandler<List<OslcServiceProvider>>(
"Requesting Available Services") { //$NON-NLS-1$
@Override
public List<OslcServiceProvider> run(HttpMethodBase method, IProgressMonitor monitor) throws CoreException {
try {
final List<OslcServiceProvider> result = new ArrayList<OslcServiceProvider>();
parseServices(method.getResponseBodyAsStream(), result, monitor);
return result;
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Network error occurred retrieving available services: " + e.getMessage(), e)); //$NON-NLS-1$
}
}
};
return executeMethod(createGetMethod(url), handler, monitor);
}
protected Document getDocumentFromMethod(HttpMethodBase method) throws CoreException {
try {
return getDocumentFromStream(method.getResponseBodyAsStream());
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Network error obtaining response from server: " + e.getMessage(), e)); //$NON-NLS-1$
}
}
protected Document getDocumentFromStream(InputStream inStream) throws CoreException {
SAXBuilder builder = new SAXBuilder();
builder.setExpandEntities(false);
try {
return builder.build(inStream);
} catch (JDOMException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Error parsing response: " + e.getMessage(), e)); //$NON-NLS-1$
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Network error parsing response: " + e.getMessage(), e)); //$NON-NLS-1$
}
}
/**
* public for testing
*/
public void parseServices(InputStream inStream, Collection<OslcServiceProvider> providers, IProgressMonitor monitor)
throws CoreException {
Document doc = getDocumentFromStream(inStream);
Iterator<?> itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_SERVICE_PROVIDER_CATALOG));
while (itr.hasNext()) {
Element element = (Element) itr.next();
if (element != doc.getRootElement()) {
Attribute attrAbout = element.getAttribute(IOslcCoreConstants.ATTRIBUTE_ABOUT,
IOslcCoreConstants.NAMESPACE_RDF);
String title = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC)
.getText();
if (attrAbout != null && attrAbout.getValue().length() > 0) {
providers.add(new OslcServiceProviderCatalog(title, attrAbout.getValue()));
}
}
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_SERVICE_PROVIDER));
while (itr.hasNext()) {
Element element = (Element) itr.next();
String title = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC)
.getText();
Element service = element.getChild(IOslcCoreConstants.ELEMENT_SERVICES,
IOslcCoreConstants.NAMESPACE_OSLC_DISCOVERY_1_0);
if (service != null) {
String resource = service.getAttributeValue(IOslcCoreConstants.ATTRIBUTE_RESOURCE,
IOslcCoreConstants.NAMESPACE_RDF);
providers.add(new OslcServiceProvider(title, resource));
}
}
}
/**
* Retrieve a service descriptor for the given service provider. Exposed at connector level by
* IOslcConnector.getServiceDescriptor()
*
* @throws CoreException
*/
public OslcServiceDescriptor getServiceDescriptor(OslcServiceProvider provider, IProgressMonitor monitor)
throws CoreException {
OslcServiceDescriptor configuration = new OslcServiceDescriptor(provider.getUrl());
downloadServiceDescriptor(configuration, monitor);
return configuration;
}
/**
* Populate the provided configuration with new data from the remote repository.
*/
protected void downloadServiceDescriptor(final OslcServiceDescriptor config, IProgressMonitor monitor)
throws CoreException {
RequestHandler<OslcServiceDescriptor> handler = new RequestHandler<OslcServiceDescriptor>(
"Retrieving Service Descriptor") { //$NON-NLS-1$
@Override
public OslcServiceDescriptor run(HttpMethodBase method, IProgressMonitor monitor) throws CoreException,
IOException {
config.clear();
parseServiceDescriptor(method.getResponseBodyAsStream(), config, monitor);
return config;
}
};
executeMethod(createGetMethod(config.getAboutUrl()), handler, monitor);
}
/**
* public for testing
*/
public void parseServiceDescriptor(InputStream inStream, OslcServiceDescriptor config, IProgressMonitor monitor)
throws CoreException {
Document doc = getDocumentFromStream(inStream);
Iterator<?> itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_TITLE,
IOslcCoreConstants.NAMESPACE_DC));
if (itr.hasNext()) {
Element element = (Element) itr.next();
config.setTitle(element.getText());
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_DESCRIPTION,
IOslcCoreConstants.NAMESPACE_DC));
if (itr.hasNext()) {
Element element = (Element) itr.next();
config.setDescription(element.getText());
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_CREATIONDIALOG,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0));
while (itr.hasNext()) {
boolean isDefault = false;
Element element = (Element) itr.next();
String label = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC)
.getText();
String url = element.getChild(IOslcCoreConstants.ELEMENT_URL, IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0)
.getText();
Attribute attrDefault = element.getAttribute(IOslcCoreConstants.ATTRIBUTE_DEFAULT,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (attrDefault != null && attrDefault.getValue().equals("true")) { //$NON-NLS-1$
isDefault = true;
}
OslcCreationDialogDescriptor recordType = new OslcCreationDialogDescriptor(label, url);
config.addCreationDialog(recordType);
if (isDefault) {
config.setDefaultCreationDialog(recordType);
}
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_SIMPLEQUERY));
if (itr.hasNext()) {
Element element = (Element) itr.next();
String url = element.getChild(IOslcCoreConstants.ELEMENT_URL, IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0)
.getText();
if (url != null) {
config.setSimpleQueryUrl(url);
}
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_FACTORY));
while (itr.hasNext()) {
boolean isDefault = false;
Element element = (Element) itr.next();
String title = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC)
.getText();
String url = element.getChild(IOslcCoreConstants.ELEMENT_URL, IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0)
.getText();
if (element.getAttribute(IOslcCoreConstants.ATTRIBUTE_DEFAULT) != null
&& element.getAttribute(IOslcCoreConstants.ATTRIBUTE_DEFAULT).getValue().equals("true")) { //$NON-NLS-1$
isDefault = true;
}
OslcServiceFactory factory = new OslcServiceFactory(title, url);
if (isDefault) {
config.setDefaultFactory(factory);
}
config.addServiceFactory(factory);
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_HOME));
if (itr.hasNext()) {
Element element = (Element) itr.next();
Element childTitle = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC);
Element childUrl = element.getChild(IOslcCoreConstants.ELEMENT_URL,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (childTitle != null && childTitle.getText().length() > 0 && childUrl != null
&& childUrl.getText().length() > 0) {
ServiceHome home = new ServiceHome(childTitle.getText(), childUrl.getText());
config.setHome(home);
}
}
itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_SELECTIONDIALOG));
if (itr.hasNext()) {
Element element = (Element) itr.next();
Element childTitle = element.getChild(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC);
Element childUrl = element.getChild(IOslcCoreConstants.ELEMENT_URL,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (childTitle != null && childTitle.getText().length() > 0 && childUrl != null
&& childUrl.getText().length() > 0) {
OslcSelectionDialogDescriptor selection = new OslcSelectionDialogDescriptor(childTitle.getText(),
childUrl.getText());
String isDefault = element.getAttributeValue(IOslcCoreConstants.ATTRIBUTE_DEFAULT,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (isDefault != null) {
selection.setDefault(isDefault.equals("true")); //$NON-NLS-1$
}
String hintHeight = element.getAttributeValue(IOslcCoreConstants.ATTRIBUTE_HINTHEIGHT,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (hintHeight != null) {
selection.setHintHeight(hintHeight);
}
String hintWidth = element.getAttributeValue(IOslcCoreConstants.ATTRIBUTE_HINTWIDTH,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (hintWidth != null) {
selection.setHintWidth(hintWidth);
}
String label = element.getChildText(IOslcCoreConstants.ELEMENT_LABEL,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
if (label != null) {
selection.setLabel(label);
}
config.addSelectionDialog(selection);
}
}
}
public Collection<AbstractChangeRequest> performQuery(String queryUrl, IProgressMonitor monitor)
throws CoreException {
RequestHandler<Collection<AbstractChangeRequest>> handler = new RequestHandler<Collection<AbstractChangeRequest>>(
"Performing Query") { //$NON-NLS-1$
@Override
public Collection<AbstractChangeRequest> run(HttpMethodBase method, IProgressMonitor monitor)
throws CoreException, IOException {
Collection<AbstractChangeRequest> result = new ArrayList<AbstractChangeRequest>();
parseQueryResponse(method.getResponseBodyAsStream(), result, monitor);
return result;
}
};
return executeMethod(createGetMethod(queryUrl), handler, monitor);
}
// TODO: Handle pagination
public void parseQueryResponse(InputStream inStream, Collection<AbstractChangeRequest> requests,
IProgressMonitor monitor) throws CoreException {
Document doc = getDocumentFromStream(inStream);
Iterator<?> itr = doc.getDescendants(new ElementFilter(IOslcCoreConstants.ELEMENT_CHANGEREQUEST,
IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0));
while (itr.hasNext()) {
Element element = (Element) itr.next();
String title = element.getChildText(IOslcCoreConstants.ELEMENT_TITLE, IOslcCoreConstants.NAMESPACE_DC);
String id = element.getChildText(IOslcCoreConstants.ELEMENT_IDENTIFIER, IOslcCoreConstants.NAMESPACE_DC);
if (title != null && id != null) {
AbstractChangeRequest request = createChangeRequest(id, title);
request.setType(element.getChildText(IOslcCoreConstants.ELEMENT_TYPE, IOslcCoreConstants.NAMESPACE_DC));
request.setDescription(element.getChildText(IOslcCoreConstants.ELEMENT_DESCRIPTION,
IOslcCoreConstants.NAMESPACE_DC));
request.setSubject(element.getChildText(IOslcCoreConstants.ELEMENT_SUBJECT,
IOslcCoreConstants.NAMESPACE_DC));
request.setCreator(element.getChildText(IOslcCoreConstants.ELEMENT_CREATOR,
IOslcCoreConstants.NAMESPACE_DC));
request.setModified(element.getChildText(IOslcCoreConstants.ELEMENT_MODIFIED,
IOslcCoreConstants.NAMESPACE_DC));
requests.add(request);
}
}
}
protected abstract AbstractChangeRequest createChangeRequest(String id, String title);
/**
* Updates this clients 'repository configuration'. If old types were in use (locally cached) and still exist they
* are re-read from repository.
*/
public void updateRepositoryConfiguration(IProgressMonitor monitor) throws CoreException {
configuration.clear();
downloadServiceDescriptor(configuration, monitor);
}
public abstract TaskData getTaskData(final String encodedTaskId, TaskAttributeMapper mapper,
IProgressMonitor monitor) throws CoreException;
public abstract RepositoryResponse putTaskData(TaskData taskData, Set<TaskAttribute> oldValues,
IProgressMonitor monitor) throws CoreException;
protected GetMethod createGetMethod(String requestPath) {
GetMethod method = new GetMethod(getRequestPath(requestPath));
method.setFollowRedirects(true);
method.setDoAuthentication(true);
// application/xml is returned by oslc servers by default
//method.setRequestHeader("Accept", "application/xml");
return method;
}
protected PostMethod createPostMethod(String requestPath) {
PostMethod method = new PostMethod(getRequestPath(requestPath));
method.setFollowRedirects(false);
method.setDoAuthentication(true);
// this.entity = getRequestEntity(method);
// if (pairs != null) {
// method.setRequestBody(pairs);
// } else if (entity != null) {
// method.setRequestEntity(entity);
// } else {
// StatusHandler.log(new Status(IStatus.WARNING, IOslcCoreConstants.ID_PLUGIN,
// "Request body or entity missing upon post.")); //$NON-NLS-1$
// }
return method;
}
protected PutMethod createPutMethod(String requestPath) {
PutMethod method = new PutMethod(getRequestPath(requestPath));
method.setFollowRedirects(false);
method.setDoAuthentication(true);
return method;
}
protected <T> T executeMethod(HttpMethodBase method, RequestHandler<T> handler, IProgressMonitor monitor)
throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
monitor.beginTask(handler.getRequestName(), IProgressMonitor.UNKNOWN);
HostConfiguration hostConfiguration = WebUtil.createHostConfiguration(httpClient, location, monitor);
int code = WebUtil.execute(httpClient, hostConfiguration, method, monitor);
handler.handleReturnCode(code, method);
return handler.run(method, monitor);
} catch (IOException e) {
throw new CoreException(new Status(IStatus.WARNING, IOslcCoreConstants.ID_PLUGIN,
"An unexpected network error has occurred: " + e.getMessage(), e)); //$NON-NLS-1$
} finally {
if (method != null) {
method.releaseConnection();
}
monitor.done();
}
}
public String getRequestPath(String repositoryUrl) {
if (repositoryUrl.startsWith("./")) { //$NON-NLS-1$
return WebUtil.getRequestPath(location.getUrl()) + repositoryUrl.substring(1);
} else if (repositoryUrl.startsWith("/")) { //$NON-NLS-1$
return WebUtil.getRequestPath(location.getUrl()) + repositoryUrl;
}
return WebUtil.getRequestPath(repositoryUrl);
}
public abstract class RequestHandler<T> {
private final String requestName;
public RequestHandler(String requestName) {
this.requestName = requestName;
}
public abstract T run(HttpMethodBase method, IProgressMonitor monitor) throws CoreException, IOException;
public String getRequestName() {
return requestName;
}
protected void handleReturnCode(int code, HttpMethodBase method) throws CoreException {
try {
if (code == java.net.HttpURLConnection.HTTP_OK) {
return;// Status.OK_STATUS;
} else if (code == java.net.HttpURLConnection.HTTP_MOVED_TEMP
|| code == java.net.HttpURLConnection.HTTP_CREATED) {
// A new resource created...
return;// Status.OK_STATUS;
} else if (code == java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|| code == java.net.HttpURLConnection.HTTP_FORBIDDEN) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Unable to log into server, ensure repository credentials are correct.")); //$NON-NLS-1$
} else if (code == java.net.HttpURLConnection.HTTP_PRECON_FAILED) {
// Mid-air collision
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"Mid-air collision occurred.")); //$NON-NLS-1$
} else if (code == java.net.HttpURLConnection.HTTP_CONFLICT) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
"A conflict occurred.")); //$NON-NLS-1$
} else {
throw new CoreException(
new Status(
IStatus.ERROR,
IOslcCoreConstants.ID_PLUGIN,
"Unknown error occurred. Http Code: " + code + " Request: " + method.getURI() + " Response: " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ method.getResponseBodyAsString()));
}
} catch (URIException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN, "Network Error: " //$NON-NLS-1$
+ e.getMessage()));
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN, "Network Error: " //$NON-NLS-1$
+ e.getMessage()));
}
}
}
}