/*******************************************************************************
 * Copyright (c) 2009, 2010 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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
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.Credentials;
import org.apache.commons.httpclient.Header;
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.auth.AuthScope;
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.apache.commons.httpclient.methods.StringRequestEntity;
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.core.runtime.SubProgressMonitor;
import org.eclipse.mylyn.commons.net.AbstractWebLocation;
import org.eclipse.mylyn.commons.net.AuthenticationCredentials;
import org.eclipse.mylyn.commons.net.AuthenticationType;
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.RepositoryResponse.ResponseKind;
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;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

/**
 * Base class from which to implement an OSLC client
 * 
 * @author Robert Elves
 */
public abstract class AbstractOslcClient {

	private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$

	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;
		configureHttpCredentials(location);
	}

	protected void configureHttpCredentials(AbstractWebLocation location) {
		AuthScope authScope = new AuthScope(WebUtil.getHost(location.getUrl()), WebUtil.getPort(location.getUrl()),
				null, AuthScope.ANY_SCHEME);

		AuthenticationCredentials credentials = location.getCredentials(AuthenticationType.REPOSITORY);

		if (credentialsValid(credentials)) {
			Credentials creds = WebUtil.getHttpClientCredentials(credentials, WebUtil.getHost(location.getUrl()));
			httpClient.getState().setCredentials(authScope, creds);
			this.httpClient.getParams().setAuthenticationPreemptive(true);
		} else {
			httpClient.getState().clearCredentials();
		}
	}

	protected boolean credentialsValid(AuthenticationCredentials credentials) {
		return credentials != null && credentials.getUserName().length() > 0;
	}

	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();
			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;
			}
			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);
			}
		}

	}

	protected OslcServiceDescriptor getConfiguration(IProgressMonitor monitor) throws CoreException {
		monitor = Policy.monitorFor(monitor);
		if (configuration.getFactories().isEmpty()) {
			updateRepositoryConfiguration(new SubProgressMonitor(monitor, 1));
		}

		return configuration;
	}

	public Collection<AbstractChangeRequest> performQuery(String query, IProgressMonitor monitor) throws CoreException {
		try {
			query = URLEncoder.encode(query, "UTF-8"); //$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			query = URLEncoder.encode(query);
		}
		final String requestUrl = getConfiguration(monitor).getSimpleQueryUrl() + "?oslc_cm.query=" + query; //$NON-NLS-1$

		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(requestUrl), 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;

	public RepositoryResponse postTaskData(TaskData taskData, IProgressMonitor monitor) throws CoreException {

		Element root = new Element(IOslcCoreConstants.ELEMENT_CHANGEREQUEST, IOslcCoreConstants.NAMESPACE_OSLC_CM_1_0);
		final Document doc = new Document(root);

		TaskAttribute attribute = taskData.getRoot().getMappedAttribute(IOslcCoreConstants.ELEMENT_TITLE);
		if (attribute != null) {
			// TODO: Store namespace on attribute
			Element e = new Element(attribute.getId(), IOslcCoreConstants.NAMESPACE_DC);
			e.setText(attribute.getValue());
			root.addContent(e);
		}

		attribute = taskData.getRoot().getMappedAttribute(IOslcCoreConstants.ELEMENT_DESCRIPTION);
		if (attribute != null) {
			Element e = new Element(attribute.getId(), IOslcCoreConstants.NAMESPACE_DC);
			e.setText(attribute.getValue());
			root.addContent(e);
		}

		attribute = taskData.getRoot().getMappedAttribute(IOslcCoreConstants.ELEMENT_TYPE);
		if (attribute != null) {
			Element e = new Element(attribute.getId(), IOslcCoreConstants.NAMESPACE_DC);
			e.setText(attribute.getValue());
			root.addContent(e);
		}

		attribute = taskData.getRoot().getMappedAttribute(IOslcCoreConstants.ELEMENT_SUBJECT);
		if (attribute != null) {
			Element e = new Element(attribute.getId(), IOslcCoreConstants.NAMESPACE_DC);
			e.setText(attribute.getValue());
			root.addContent(e);
		}

		// For RTC Tests
		attribute = taskData.getRoot().getMappedAttribute("filedAgainst"); //$NON-NLS-1$
		if (attribute != null) {
			Element e = new Element(attribute.getId(), IOslcCoreConstants.NAMESPACE_RTC_CM_1_0);
			e.setText(attribute.getValue());
			root.addContent(e);
		}

		PostMethod method = createPostMethod(getConfiguration(monitor).getDefaultFactory().getUrl());
		method.setRequestHeader("Accept", "application/x-oslc-cm-change-request+xml"); //$NON-NLS-1$ //$NON-NLS-2$
		method.setRequestHeader("Content-Type", "application/x-oslc-cm-change-request+xml"); //$NON-NLS-1$ //$NON-NLS-2$

		XMLOutputter out = new XMLOutputter(Format.getCompactFormat());
		try {
			method.setRequestEntity(new StringRequestEntity(out.outputString(doc),
					IOslcCoreConstants.CONTENT_TYPE_CHANGE_REQUEST, UTF_8));
		} catch (UnsupportedEncodingException e1) {
			throw new CoreException(new Status(IStatus.ERROR, IOslcCoreConstants.ID_PLUGIN,
					"Error creating new change request: " + e1.getMessage(), e1)); //$NON-NLS-1$
		}

		RequestHandler<RepositoryResponse> handler = new RequestHandler<RepositoryResponse>("Creating") { //$NON-NLS-1$

			@Override
			public RepositoryResponse run(HttpMethodBase method, IProgressMonitor monitor) throws CoreException {
				Header header = method.getResponseHeader("Location"); //$NON-NLS-1$

				if (header != null && header.getValue() != null) {

					String location = header.getValue();
					// TODO: delegate extraction of 'task id' to protected method and add to repository response
					return new RepositoryResponse(ResponseKind.TASK_CREATED, location);
				}
				return null;
			}
		};

		return executeMethod(method, handler, monitor);

	}

	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 (but some may not play nice)
		method.setRequestHeader("Accept", "application/xml"); //$NON-NLS-1$ //$NON-NLS-2$
		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.")); 
//		}
		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);

			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) {
				WebUtil.releaseConnection(method, monitor);
			}
			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);
	}

	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()));
		}
	}

	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;
		}

	}
}