package combo;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
final class HttpCombo implements Combo {
private final FactProvider factProvider;
private final FactPublisher factPublisher;
private final TopicSubscriber topicSubscriber;
HttpCombo(final RestTemplate restTemplate) {
this.factProvider = new FactProvider(restTemplate);
this.factPublisher = new FactPublisher(restTemplate);
this.topicSubscriber = new TopicSubscriber(restTemplate);
}
@Override public <T> Subscription<T> subscribeTo(final String topicName, final Class<? extends T> factClass) {
final SubscriptionId subscriptionId = topicSubscriber.subscribeTo(topicName);
return new BlockingSubscription<>(factProvider, subscriptionId, factClass);
}
@Override public <T> void publishFact(final String topicName, final T fact) {
factPublisher.publishFact(topicName, fact);
}
@Override public List<Map<String, Object>> allFacts(final String topicName) {
return factProvider.allFacts(topicName, 0);
}
@Override public List<Map<String, Object>> allFacts(final String topicName, final int afterId) {
return factProvider.allFacts(topicName, afterId);
}
private static final class BlockingSubscription<T> implements Subscription<T> {
private final FactProvider factProvider;
private final SubscriptionId subscriptionId;
private final Class<? extends T> factClass;
private BlockingSubscription(final FactProvider factProvider,
final SubscriptionId subscriptionId,
final Class<? extends T> factClass) {
this.factProvider = factProvider;
this.subscriptionId = subscriptionId;
this.factClass = factClass;
}
@Override public Optional<T> nextFact() {
return factProvider.nextFact(subscriptionId, factClass);
}
@Override public void forEach(final Consumer<T> factConsumer) {
//noinspection InfiniteLoopStatement
while (true) {
nextFact().ifPresent(factConsumer);
}
}
}
private static final class FactProvider {
private final RestTemplate restTemplate;
private FactProvider(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private <T> Optional<T> nextFact(final SubscriptionId subscriptionId, final Class<? extends T> classOfFact) {
try {
System.out.println(" [x] Fetching next fact");
final ResponseEntity<? extends T> response = restTemplate.getForEntity(Paths.nextFact(subscriptionId), classOfFact);
if (response.getStatusCode() == HttpStatus.NO_CONTENT) {
System.out.println(" [x] No content");
return Optional.empty();
} else {
System.out.printf(" [x] Next fact for %s: '%s'%n", subscriptionId, response.getBody());
}
return Optional.ofNullable(response.getBody());
} catch (final HttpServerErrorException hsee) {
System.out.println(" [x] Server Error fetching facts");
hsee.printStackTrace(System.err);
return Optional.empty();
} catch (final HttpClientErrorException hcee) {
System.out.println(" [x] Client Error fetching facts");
hcee.printStackTrace(System.err);
return Optional.empty();
}
}
public List<Map<String, Object>> allFacts(final String topicName, final int afterId) {
return Arrays.asList(restTemplate.getForEntity(Paths.facts(topicName, afterId), Map[].class).getBody());
}
}
private static final class TopicSubscriber {
private final RestTemplate restTemplate;
private TopicSubscriber(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private SubscriptionId subscribeTo(final String topicName) {
final ResponseEntity<Map> response = restTemplate.postForEntity(Paths.subscriptions(topicName), null, Map.class);
final String comboId = (String) response.getBody().get("subscription_id");
System.out.printf(" [x] Subscribed to %s with id %s%n", topicName, comboId);
return new SubscriptionId(topicName, comboId);
}
}
private static final class FactPublisher {
private final RestTemplate restTemplate;
private FactPublisher(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private <T> void publishFact(final String topicName, final T fact) {
System.out.printf(" [x] Publishing [%s]: '%s'%n", topicName, fact);
restTemplate.postForEntity(Paths.facts(topicName), fact, Void.class);
}
}
private static final class Paths {
private static URI subscriptions(final String topicName) {
return new UriTemplate("/topics/{topicName}/subscriptions").expand(topicName);
}
private static URI nextFact(final SubscriptionId subscriptionId) {
return new UriTemplate("/topics/{topicName}/subscriptions/{subscriptionId}/next")
.expand(subscriptionId.topicName(), subscriptionId.comboId());
}
public static URI facts(final String topicName) {
return new UriTemplate("/topics/{topicName}/facts").expand(topicName);
}
public static URI facts(final String topicName, final int afterId) {
return new UriTemplate("/topics/{topicName}/facts?after_id=" + afterId).expand(topicName);
}
}
}