/******************************************************************************* * Copyright (c) 2010, 2011 Steffen Pingel 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: * Steffen Pingel - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.internal.commons.xmlrpc; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.httpclient.auth.NTLMScheme; import org.apache.xmlrpc.XmlRpcException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.mylyn.commons.core.CoreUtil; 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.UnsupportedRequestException; /** * @author Steffen Pingel */ public abstract class XmlRpcOperation<T> { private static final Pattern RPC_METHOD_NOT_FOUND_PATTERN = Pattern.compile("No such handler: "); //$NON-NLS-1$ protected static final int XML_FAULT_GENERAL_ERROR = 1; protected static final int XML_FAULT_PERMISSION_DENIED = 403; private final CommonXmlRpcClient client; public XmlRpcOperation(CommonXmlRpcClient client) { this.client = client; } protected Object call(IProgressMonitor monitor, String method, Object... parameters) throws XmlRpcException { monitor = Policy.monitorFor(monitor); XmlRpcException lastException = null; for (int attempt = 0; attempt < 3; attempt++) { // if (!client.isProbed()) { // try { // probeAuthenticationScheme(monitor); // } finally { // client.setProbed(true); // } // } try { return executeCall(monitor, method, parameters); } catch (XmlRpcLoginException e) { try { client.getLocation().requestCredentials(AuthenticationType.REPOSITORY, null, monitor); } catch (UnsupportedRequestException ignored) { throw e; } lastException = e; } catch (XmlRpcPermissionDeniedException e) { try { client.getLocation().requestCredentials(AuthenticationType.REPOSITORY, null, monitor); } catch (UnsupportedRequestException ignored) { throw e; } lastException = e; } catch (XmlRpcProxyAuthenticationException e) { try { client.getLocation().requestCredentials(AuthenticationType.PROXY, null, monitor); } catch (UnsupportedRequestException ignored) { throw e; } lastException = e; } catch (XmlRpcSslCertificateException e) { try { client.getLocation().requestCredentials(AuthenticationType.CERTIFICATE, null, monitor); } catch (UnsupportedRequestException ignored) { throw e; } lastException = e; } } if (lastException != null) { throw lastException; } else { // this path should never be reached throw new IllegalStateException(); } } private void checkForException(Object result) throws NumberFormatException, XmlRpcException { if (result instanceof Map<?, ?>) { Map<?, ?> exceptionData = (Map<?, ?>) result; if (exceptionData.containsKey("faultCode") && exceptionData.containsKey("faultString")) { //$NON-NLS-1$ //$NON-NLS-2$ throw new XmlRpcException(Integer.parseInt(exceptionData.get("faultCode").toString()), //$NON-NLS-1$ (String) exceptionData.get("faultString")); //$NON-NLS-1$ } else if (exceptionData.containsKey("title")) { //$NON-NLS-1$ String message = (String) exceptionData.get("title"); //$NON-NLS-1$ String detail = (String) exceptionData.get("_message"); //$NON-NLS-1$ if (detail != null) { message += ": " + detail; //$NON-NLS-1$ } throw new XmlRpcException(XML_FAULT_GENERAL_ERROR, message); } } } protected boolean credentialsValid(AuthenticationCredentials credentials) { return credentials != null; } public abstract T execute() throws XmlRpcException; protected Object executeCall(IProgressMonitor monitor, String method, Object... parameters) throws XmlRpcException { try { if (CommonXmlRpcClient.DEBUG_XMLRPC) { System.err.println("Calling " + client.getLocation().getUrl() + ": " + method + " " + CoreUtil.toString(parameters)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } AuthenticationCredentials credentials = client.updateCredentials(); XmlRpcClientRequest request = new XmlRpcClientRequest(client.getClient().getClientConfig(), getXmlRpcUrl(credentials), method, parameters, monitor); return client.getClient().execute(request); } catch (XmlRpcHttpException e) { handleAuthenticationException(e.code, e.getAuthScheme()); // if not handled, re-throw exception throw e; } catch (XmlRpcIllegalContentTypeException e) { throw e; } catch (XmlRpcException e) { // XXX work-around for http://trac-hacks.org/ticket/5848 if ("XML_RPC privileges are required to perform this operation".equals(e.getMessage()) //$NON-NLS-1$ || e.code == XML_FAULT_PERMISSION_DENIED) { handleAuthenticationException(HttpStatus.SC_FORBIDDEN, null); // should never happen as call above should always throw an exception throw new XmlRpcRemoteException(e); } else if (isNoSuchMethodException(e)) { throw new XmlRpcNoSuchMethodException(e); } else { throw new XmlRpcRemoteException(e); } } catch (OperationCanceledException e) { throw e; } catch (Exception e) { throw new XmlRpcException("Unexpected exception", e); //$NON-NLS-1$ } } protected final CommonXmlRpcClient getClient() { return client; } protected Object getMultiCallResult(Object item) { return ((Object[]) item)[0]; } protected URL getXmlRpcUrl(AuthenticationCredentials credentials) { try { return new URL(client.getLocation().getUrl()); } catch (MalformedURLException e) { throw new RuntimeException("Encoding of URL failed", e); //$NON-NLS-1$ } } protected boolean handleAuthenticationException(int code, AuthScheme authScheme) throws XmlRpcException { if (code == HttpStatus.SC_UNAUTHORIZED) { if (CommonXmlRpcClient.DEBUG_AUTH) { System.err.println(client.getLocation().getUrl() + ": Unauthorized (" + code + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } client.digestScheme = null; XmlRpcLoginException exception = new XmlRpcLoginException(); exception.setNtlmAuthRequested(authScheme instanceof NTLMScheme); throw exception; } else if (code == HttpStatus.SC_FORBIDDEN) { if (CommonXmlRpcClient.DEBUG_AUTH) { System.err.println(client.getLocation().getUrl() + ": Forbidden (" + code + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } client.digestScheme = null; throw new XmlRpcPermissionDeniedException(); } else if (code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) { if (CommonXmlRpcClient.DEBUG_AUTH) { System.err.println(client.getLocation().getUrl() + ": Proxy authentication required (" + code + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } throw new XmlRpcProxyAuthenticationException(); } return false; } protected boolean isNoSuchMethodException(XmlRpcException e) { if (RPC_METHOD_NOT_FOUND_PATTERN.matcher(e.getMessage()).find()) { return true; } return false; } // protected Object[] multicall(IProgressMonitor monitor, Map<String, Object>... calls) throws XmlRpcException { // Object[] result = (Object[]) call(monitor, "system.multicall", new Object[] { calls }); //$NON-NLS-1$ // for (Object item : result) { // checkForException(item); // } // return result; // } protected MulticallResult call(IProgressMonitor monitor, Multicall call) throws XmlRpcException { Object[] response = (Object[]) call(monitor, "system.multicall", new Object[] { call.getCalls() }); //$NON-NLS-1$ for (Object item : response) { checkForException(item); } return new MulticallResult(response); } }