package cloudone.client.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import cloudone.C1Services;
import cloudone.client.MultiResponse;
import cloudone.internal.ApplicationFullName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of MultiResponse which contruct is fulfilled when all callables are add. It means that no new item
* cannot be add after this item is returned.
*
* @author Martin Mares (martin.mares at oracle.com)
*/
public class MultiResponseImpl implements MultiResponse {
private static final Logger LOGGER = LoggerFactory.getLogger(MultiResponseImpl.class);
public static class IdentifiedResponseImpl implements IdentifiedResponse {
private final ApplicationFullName applicationFullName;
private final Future<Response> responseFuture;
private volatile Exception exception;
public IdentifiedResponseImpl(ApplicationFullName applicationFullName, Future<Response> responseFuture) {
this.applicationFullName = applicationFullName;
this.responseFuture = responseFuture;
}
@Override
public ApplicationFullName getApplicationFullName() {
return applicationFullName;
}
@Override
public Response getResponse() {
if (exception != null) {
if (exception instanceof WebApplicationException) {
return ((WebApplicationException) exception).getResponse();
} else {
return null;
}
}
try {
return responseFuture.get();
} catch (InterruptedException e) {
this.exception = e;
return null;
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception) {
exception = (Exception) e.getCause();
if (exception instanceof WebApplicationException) {
return ((WebApplicationException) exception).getResponse();
} else {
return null;
}
} else {
//This is very unexpected
throw new RuntimeException("Cannot reach " + applicationFullName, e.getCause());
}
}
}
public Exception getError() {
getResponse();
return exception;
}
boolean isDone() {
return responseFuture.isDone();
}
}
private final List<IdentifiedResponseImpl> responses = new ArrayList<>();
private final Map<ApplicationFullName, IdentifiedResponse> map = new HashMap<>();
private volatile CountDownLatch nextDoneLatch = new CountDownLatch(1);
/**
* Add new futer task based on provided callable. It MUST be called only as a part of construction process and
* task cannot be used based on it's contract before it is fully constructed.
*/
void add(ApplicationFullName applicationFullName, Callable<Response> responseCallable) {
ExecutorService executorService = C1Services.getInstance().getExecutorService();
FutureTask<Response> responseFuture = new FutureTask<>(responseCallable);
final IdentifiedResponseImpl identifiedResponse = new IdentifiedResponseImpl(applicationFullName, responseFuture);
map.put(applicationFullName, identifiedResponse);
executorService.execute(new FutureTask<Response>(responseFuture, null) {
@Override
protected void done() {
super.done();
synchronized (responses) {
responses.add(identifiedResponse);
nextDoneLatch.countDown();
nextDoneLatch = new CountDownLatch(1);
}
}
});
}
@Override
public Iterator<IdentifiedResponse> iterator() {
return new Iterator<IdentifiedResponse>() {
private int index = 0;
@Override
public boolean hasNext() {
if (index < responses.size()) {
return true;
}
CountDownLatch latch;
synchronized (responses) {
if (index < responses.size()) {
return true;
}
if (responses.size() == map.size()) {
return false;
}
latch = nextDoneLatch;
}
try {
latch.await();
return true;
} catch (InterruptedException e) {
throw new RuntimeException("Interuption during wait for next item!", e);
}
}
@Override
public IdentifiedResponse next() {
if (hasNext()) {
synchronized (responses) {
return responses.get(index++);
}
} else {
throw new NoSuchElementException();
}
}
};
}
public Set<ApplicationFullName> getApplicationNames() {
return map.keySet();
}
public IdentifiedResponse getResult(ApplicationFullName name) {
return map.get(name);
}
}