/*
* Copyright 2014 Avanza Bank AB
*
* 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 com.avanza.astrix.http;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.SerializableEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import com.avanza.astrix.core.ServiceUnavailableException;
import com.avanza.astrix.core.remoting.RoutingKey;
import com.avanza.astrix.remoting.client.AstrixServiceInvocationRequest;
import com.avanza.astrix.remoting.client.AstrixServiceInvocationResponse;
import com.avanza.astrix.remoting.client.RemotingTransportSpi;
import com.avanza.astrix.remoting.client.RoutedServiceInvocationRequest;
/**
*
* @author Elias Lindholm
*
*/
public final class HttpRemotingTransport implements RemotingTransportSpi {
private final CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
private final Map<Integer, ClusterMember> clusterMembers = new ConcurrentHashMap<>();
private final int clusterSize; // may be larger than clusterMembers.size in case not all members are discovered yet.
public HttpRemotingTransport(Collection<ClusterMember> clusterMembers, int clusterSize) {
this.clusterSize = clusterSize;
for (ClusterMember clusterMember : clusterMembers) {
this.clusterMembers.put(clusterMember.getClusterInstanceId(), clusterMember);
}
}
@Override
public Observable<AstrixServiceInvocationResponse> submitRoutedRequest(
final AstrixServiceInvocationRequest request, final RoutingKey routingKey) {
ClusterMember clusterMember = getTargetMember(routingKey);
final HttpPost postRequest = new HttpPost(clusterMember.getRemoteEndpointUri());
postRequest.setEntity(new SerializableEntity(request));
return Observable.create(new OnSubscribe<AstrixServiceInvocationResponse>() {
@Override
public void call(final Subscriber<? super AstrixServiceInvocationResponse> t1) {
try {
httpclient.execute(postRequest, serviceResponseCallback(t1));
} catch (Exception e) {
t1.onError(e);
}
}
});
}
@Override
public Observable<List<AstrixServiceInvocationResponse>> submitRoutedRequests(
Collection<RoutedServiceInvocationRequest> requests) {
Observable<AstrixServiceInvocationResponse> result = Observable.empty();
for (RoutedServiceInvocationRequest request : requests) {
result = result.mergeWith(submitRoutedRequest(request.getRequest(), request.getRoutingkey()));
}
return result.toList();
}
@Override
public Observable<List<AstrixServiceInvocationResponse>> submitBroadcastRequest(
AstrixServiceInvocationRequest request) {
Observable<AstrixServiceInvocationResponse> result = Observable.empty();
for (ClusterMember clusterMember : getAllClusterMembers()) {
final HttpPost postRequest = new HttpPost(clusterMember.getRemoteEndpointUri());
postRequest.setEntity(new SerializableEntity(request));
result = result.mergeWith(Observable.create(new OnSubscribe<AstrixServiceInvocationResponse>() {
@Override
public void call(final Subscriber<? super AstrixServiceInvocationResponse> t1) {
try {
httpclient.execute(postRequest, serviceResponseCallback(t1));
} catch (Exception e) {
t1.onError(e);
}
}
}));
}
return result.toList();
}
private Collection<ClusterMember> getAllClusterMembers() {
return this.clusterMembers.values();
}
private ClusterMember getTargetMember(RoutingKey routingKey) {
int targetPartition = routingKey.hashCode() % partitionCount();
ClusterMember target = this.clusterMembers.get(targetPartition);
if (target == null) {
throw new ServiceUnavailableException("Failed to find cluster member with id: " + targetPartition);
}
return target;
}
private FutureCallback<HttpResponse> serviceResponseCallback(
final Subscriber<? super AstrixServiceInvocationResponse> t1) {
return new FutureCallback<HttpResponse>() {
public void completed(final HttpResponse response) {
try {
t1.onNext(getResponse(response));
t1.onCompleted();
} catch (Exception e) {
t1.onError(e);
}
}
public void failed(final Exception ex) {
t1.onError(ex);
}
public void cancelled() {
t1.onError(new RuntimeException("Request cancelled"));
}
};
}
private AstrixServiceInvocationResponse getResponse(final HttpResponse response){
try {
HttpEntity entity = response.getEntity();
ObjectInputStream inputStream = new ObjectInputStream(entity.getContent());
return (AstrixServiceInvocationResponse) inputStream.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public int partitionCount() {
return this.clusterSize;
}
@PostConstruct
public void init() {
httpclient.start();
}
@PreDestroy
public void destroy() throws IOException {
httpclient.close();
}
private static class ClusterMember {
private String remoteEndpoint;
private int clusterInstanceId;
public ClusterMember(String remoteEndpoint, int clusterInstanceId) {
this.remoteEndpoint = remoteEndpoint;
this.clusterInstanceId = clusterInstanceId;
}
public String getRemoteEndpointUri() {
return remoteEndpoint;
}
public int getClusterInstanceId() {
return clusterInstanceId;
}
}
}