blob: e1a55b172f633a483205b2174d4165e59309f17b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* 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:
* Otto von Wesendonk - initial API and implementation
******************************************************************************/
package org.eclipse.emf.emfstore.internal.client.model.connectionmanager.xmlrpc;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.XmlRpcSun15HttpTransportFactory;
import org.eclipse.emf.emfstore.client.exceptions.ESCertificateException;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionElement;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPoint;
import org.eclipse.emf.emfstore.internal.client.model.Configuration;
import org.eclipse.emf.emfstore.internal.client.model.ServerInfo;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.ConnectionManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.KeyStoreManager;
import org.eclipse.emf.emfstore.internal.common.ESCollections;
import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.internal.common.model.util.SerializationException;
import org.eclipse.emf.emfstore.internal.server.connection.xmlrpc.util.EObjectTypeFactory;
import org.eclipse.emf.emfstore.internal.server.exceptions.ConnectionException;
import org.eclipse.emf.emfstore.internal.server.model.ProjectId;
import org.eclipse.emf.emfstore.internal.server.model.SessionId;
import org.eclipse.emf.emfstore.internal.server.model.versioning.AbstractChangePackage;
import org.eclipse.emf.emfstore.internal.server.model.versioning.ChangePackageEnvelope;
import org.eclipse.emf.emfstore.internal.server.model.versioning.ChangePackageProxy;
import org.eclipse.emf.emfstore.internal.server.model.versioning.FileBasedChangePackage;
import org.eclipse.emf.emfstore.internal.server.model.versioning.VersioningFactory;
import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.util.ChangePackageUtil;
import org.eclipse.emf.emfstore.server.exceptions.ESException;
import org.xml.sax.SAXException;
import com.google.common.base.Optional;
/**
* Manager for XML RPC server calls.
*
* @author wesendon
*/
public class XmlRpcClientManager {
private final String serverInterface;
private XmlRpcClient client;
private static boolean serializationOptionsInitialized;
private static boolean gzipCompressionEnabled;
private static boolean gzipRequestingEnabled;
/**
* Default constructor.
*
* @param serverInterface name of interface
*/
public XmlRpcClientManager(String serverInterface) {
this.serverInterface = serverInterface;
}
/**
* Initializes the connection.
*
* @param serverInfo server info
* @throws ConnectionException in case of failure
*/
public void initConnection(ServerInfo serverInfo) throws ConnectionException {
try {
initSerializationOptions();
final XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(createURL(serverInfo));
config.setEnabledForExceptions(true);
config.setEnabledForExtensions(true);
config.setConnectionTimeout(Configuration.getXMLRPC().getXMLRPCConnectionTimeout());
config.setReplyTimeout(Configuration.getXMLRPC().getXMLRPCReplyTimeout());
config.setContentLengthOptional(true);
config.setGzipCompressing(gzipCompressionEnabled);
config.setGzipRequesting(gzipRequestingEnabled);
client = new XmlRpcClient();
client.setTypeFactory(new EObjectTypeFactory(client));
final XmlRpcSun15HttpTransportFactory factory = new XmlRpcSun15HttpTransportFactory(client);
try {
factory.setSSLSocketFactory(KeyStoreManager.getInstance().getSSLContext().getSocketFactory());
} catch (final ESCertificateException e) {
throw new ConnectionException(Messages.XmlRpcClientManager_Could_Not_Load_Certificate, e);
}
client.setTransportFactory(factory);
client.setConfig(config);
} catch (final MalformedURLException e) {
throw new ConnectionException(Messages.XmlRpcClientManager_Malformed_URL_Or_Port, e);
}
}
private URL createURL(ServerInfo serverInfo) throws MalformedURLException {
checkUrl(serverInfo.getUrl());
return new URL("https", serverInfo.getUrl(), serverInfo.getPort(), "/xmlrpc"); //$NON-NLS-1$ //$NON-NLS-2$
}
private void checkUrl(String url) throws MalformedURLException {
if (url != null && !url.equals(StringUtils.EMPTY)) {
if (!(url.contains(":") || url.contains("/"))) { //$NON-NLS-1$ //$NON-NLS-2$
return;
}
}
throw new MalformedURLException();
}
/**
* Executes a server call with return value.
*
* @param <T> return type
* @param methodName method name
* @param returnType return type
* @param parameters parameters
* @return returned object from server
* @throws ESException in case of failure
*/
public <T> T callWithResult(String methodName, Class<T> returnType, Object... parameters) throws ESException {
return executeCall(methodName, returnType, parameters);
}
/**
* Executes a server call with list return value.
*
* @param <T> return type
* @param methodName method name
* @param returnType list return type
* @param parameters parameters
* @return list return type
* @throws ESException in case of failure
*/
@SuppressWarnings("unchecked")
public <T> List<T> callWithListResult(String methodName, Class<T> returnType, Object... parameters)
throws ESException {
final List<T> result = new ArrayList<T>();
final Object[] callResult = executeCall(methodName, Object[].class, parameters);
if (callResult == null) {
return result;
}
for (final Object obj : callResult) {
result.add((T) obj);
}
return result;
}
/**
* Executes a server call without return value.
*
* @param methodName method name
* @param parameters parameters
* @throws ESException in case of failure
*/
public void call(String methodName, Object... parameters) throws ESException {
executeCall(methodName, null, parameters);
}
@SuppressWarnings("unchecked")
private <T> T executeCall(String methodName, Class<T> returnType, Object[] params) throws ESException {
if (client == null) {
throw new ConnectionException(ConnectionManager.REMOTE);
}
final Object[] adjustedParams = adjustParameters(params);
try {
final T result = (T) client.execute(serverInterface + "." + methodName, adjustedParams); //$NON-NLS-1$
return adjustResult(ESCollections.find(params, SessionId.class), result);
} catch (final XmlRpcException e) {
if (e.getCause() instanceof ESException) {
throw (ESException) e.getCause();
} else if (e.linkedException instanceof SAXException
&& ((SAXException) e.linkedException).getException() instanceof SerializationException) {
final SerializationException serialE = (SerializationException) ((SAXException) e.linkedException)
.getException();
throw new org.eclipse.emf.emfstore.internal.server.exceptions.SerializationException(serialE);
} else {
throw new ConnectionException(ConnectionManager.REMOTE + e.getMessage(), e);
}
}
}
private Object[] adjustParameters(final Object[] params) throws ESException {
if (!Configuration.getClientBehavior().getChangePackageFragmentSize().isPresent()) {
return params;
}
final Optional<SessionId> maybeSessionId = ESCollections.find(params, SessionId.class);
final Optional<ProjectId> maybeProjectId = ESCollections.find(params, ProjectId.class);
if (!maybeSessionId.isPresent() || !maybeProjectId.isPresent()) {
// do not attempt to split
return params;
}
for (int i = 0; i < params.length; i++) {
final Object param = params[i];
if (AbstractChangePackage.class.isInstance(param) && !ChangePackageProxy.class.isInstance(param)) {
params[i] = uploadInFragments(
maybeSessionId.get(),
maybeProjectId.get(),
AbstractChangePackage.class.cast(param));
}
}
return params;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> T adjustResult(final Optional<SessionId> maybeSessionId, final T result) throws ESException {
if (result instanceof Object[]) {
final Object[] objects = (Object[]) result;
for (int i = 0; i < objects.length; i++) {
final Object item = objects[i];
objects[i] = adjustResult(maybeSessionId, item);
}
return (T) objects;
} else if (result instanceof List) {
final List l = (List) result;
for (int i = 0; i < l.size(); i++) {
l.set(i, adjustResult(maybeSessionId, result));
}
} else if (result instanceof ChangePackageProxy) {
return (T) downloadAndResolveChangePackage((ChangePackageProxy) result, maybeSessionId);
}
return result;
}
private AbstractChangePackage downloadAndResolveChangePackage(final ChangePackageProxy proxy,
final Optional<SessionId> maybeSession) throws ESException {
if (!maybeSession.isPresent()) {
throw new ESException(Messages.XmlRpcClientManager_NoValidSessionId);
}
int fragmentIndex = 0;
final FileBasedChangePackage changePackage = VersioningFactory.eINSTANCE
.createFileBasedChangePackage();
changePackage.initialize(FileUtil.createLocationForTemporaryChangePackage());
ChangePackageEnvelope envelope;
do {
envelope = executeCall("downloadChangePackageFragment", ChangePackageEnvelope.class, new Object[] { //$NON-NLS-1$
maybeSession.get(),
proxy.getId(),
fragmentIndex
});
changePackage.addAll(envelope.getFragment());
fragmentIndex += 1;
} while (!envelope.isLast());
try {
changePackage.setLogMessage(
ModelUtil.clone(proxy.getLogMessage()));
changePackage.save();
} catch (final IOException ex) {
throw new ESException(Messages.XmlRpcClientManager_SaveChangePackageFailed, ex);
}
return changePackage;
}
private ChangePackageProxy uploadInFragments(SessionId sessionId,
ProjectId projectId, AbstractChangePackage changePackage)
throws ESException {
// get() is guarded
final Iterator<ChangePackageEnvelope> envelopes = ChangePackageUtil.splitChangePackage(
changePackage,
Configuration.getClientBehavior().getChangePackageFragmentSize().get());
String proxyId = null;
try {
while (envelopes.hasNext()) {
proxyId = uploadChangePackageFragment(
sessionId,
projectId,
envelopes.next()
);
}
} catch (final XmlRpcException ex) {
throw new ESException(Messages.XmlRpcClientManager_UploadChangePackageFragmentCallFailed, ex);
}
final ChangePackageProxy proxy = VersioningFactory.eINSTANCE.createChangePackageProxy();
proxy.setLogMessage(ModelUtil.clone(changePackage.getLogMessage()));
proxy.setId(proxyId);
return proxy;
}
private String uploadChangePackageFragment(final SessionId sessionId,
final ProjectId projectId, final ChangePackageEnvelope envelope) throws XmlRpcException {
return (String) client.execute(serverInterface + "." + "uploadChangePackageFragment", //$NON-NLS-1$ //$NON-NLS-2$
new Object[] {
sessionId,
projectId,
envelope
});
}
/**
* Initializes the serialization options for compressed server communication.
*/
private static void initSerializationOptions() {
if (serializationOptionsInitialized) {
return;
}
// init compression with false if not configured
gzipRequestingEnabled = false;
gzipCompressionEnabled = false;
final ESExtensionElement element = new ESExtensionPoint(
"org.eclipse.emf.emfstore.common.model.serializationOptions") //$NON-NLS-1$
.getFirst();
if (element != null) {
gzipCompressionEnabled = element.getBoolean("GzipCompression"); //$NON-NLS-1$
gzipRequestingEnabled = element.getBoolean("GzipRequesting"); //$NON-NLS-1$
}
serializationOptionsInitialized = true;
}
}