package com.workshare.msnos.usvc;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.workshare.msnos.core.Cloud;
import com.workshare.msnos.core.Cloud.Listener;
import com.workshare.msnos.core.Gateways;
import com.workshare.msnos.core.LocalAgent;
import com.workshare.msnos.core.Message;
import com.workshare.msnos.core.MessageBuilder;
import com.workshare.msnos.core.MsnosException;
import com.workshare.msnos.core.geo.Location;
import com.workshare.msnos.core.payloads.QnePayload;
import com.workshare.msnos.core.protocols.ip.AddressResolver;
import com.workshare.msnos.core.protocols.ip.BaseEndpoint;
import com.workshare.msnos.core.protocols.ip.Endpoint;
import com.workshare.msnos.core.protocols.ip.Endpoint.Type;
import com.workshare.msnos.core.protocols.ip.Network;
import com.workshare.msnos.soup.json.Json;
import com.workshare.msnos.soup.threading.ExecutorServices;
import com.workshare.msnos.usvc.api.RestApi;
import com.workshare.msnos.usvc.api.routing.strategies.PriorityRoutingStrategy;
public class Microservice implements IMicroservice {
private static final AddressResolver ADDRESS_RESOLVER = new AddressResolver();
private static final Logger log = LoggerFactory.getLogger("STANDARD");
private final String name;
private final LocalAgent agent;
private final Location location;
private final List<RestApi> localApis;
transient private final Listener listener;
transient private Microcloud cloud;
public Microservice(String name) {
this(name, new LocalAgent(UUID.randomUUID()), ExecutorServices.newSingleThreadScheduledExecutor());
}
public Microservice(String name, LocalAgent agent) {
this(name, agent, ExecutorServices.newSingleThreadScheduledExecutor());
}
public Microservice(String name, LocalAgent agent, ScheduledExecutorService executor) {
this.name = name;
this.agent = agent;
this.location = computeLocation(agent);
this.localApis = new CopyOnWriteArrayList<RestApi>();
this.listener = new Cloud.Listener() {
@Override
public void onMessage(Message message) {
try {
process(message);
} catch (MsnosException e) {
log.error("Error processing message {}", e);
}
}
};
}
private Location computeLocation(LocalAgent agent) {
final Set<Endpoint> points = endpoints(agent);
try {
Network externalIP = ADDRESS_RESOLVER.findRouterIP();
if (externalIP != null) {
points.add(new BaseEndpoint(Type.HTTP, externalIP));
}
} catch (Throwable ex) {
log.warn("Unable to compute external IP", ex);
}
return Location.computeMostPreciseLocation(points);
}
public Set<Endpoint> endpoints(LocalAgent agent) {
Set<Endpoint> points = new HashSet<Endpoint>();
try {
points.addAll(Gateways.allEndpoints());
} catch (MsnosException e) {
log.warn("Unable to endpoints from gateways... wooot?", e);
}
points.addAll(agent.getEndpoints());
return points;
}
public Microcloud getCloud() {
return cloud;
}
@Override
public String getName() {
return name;
}
@Override
public LocalAgent getAgent() {
return agent;
}
@Override
public Location getLocation() {
return location;
}
public RestApi searchApi(String path) {
return cloud.searchApi(this, path);
}
public List<RestApi> getLocalApis() {
return localApis;
}
public void publish(RestApi... apis) throws MsnosException {
final RestApi[] all = enforcePriorityIfRequired(apis);
localApis.addAll(Arrays.asList(all));
cloud.publish(this, all);
}
public void join(final Microcloud cumulus) throws MsnosException {
if (this.cloud != null)
throw new IllegalArgumentException("The same instance of a microservice cannot join different clouds!");
cloud = cumulus;
cloud.onJoin(this);
cloud.addListener(listener);
cloud.getCloud().getRing().onMicroserviceJoin(this);
}
public void leave() throws MsnosException {
if (cloud == null)
return;
log.debug("Leaving cloud {}", cloud);
cloud.removeListener(listener);
cloud.onLeave(this);
cloud = null;
log.debug("So long {}", cloud);
}
private void process(Message message) throws MsnosException {
if (!message.getFrom().equals(agent.getIden())) {
if (message.getType() == Message.Type.ENQ) {
processENQ();
}
}
}
private void processENQ() throws MsnosException {
Message message = new MessageBuilder(Message.Type.QNE, agent, cloud.getCloud()).with(new QnePayload(name, new HashSet<RestApi>(getLocalApis()))).make();
agent.send(message);
}
@Override
public String toString() {
return Json.toJsonString(agent);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Microservice that = (Microservice) o;
return this.hashCode() == that.hashCode();
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + agent.hashCode();
result = 31 * result + location.hashCode();
result = 31 * result + cloud.getCloud().hashCode();
return result;
}
private RestApi[] enforcePriorityIfRequired(RestApi[] apis) {
if (!PriorityRoutingStrategy.isEnabled())
return apis;
Integer priority = PriorityRoutingStrategy.getDefaultLevel();
if (priority != null) {
for (int i = 0; i < apis.length; i++) {
apis[i] = apis[i].withPriority(priority);
}
}
return apis;
}
@Override
public Set<RestApi> getApis() {
return new HashSet<RestApi>(localApis);
}
}