package io.hummer.prefetch.client; import io.hummer.osm.util.Util; import io.hummer.prefetch.PrefetchingService.PrefetchingResultReceiver; import io.hummer.prefetch.context.Context; import io.hummer.prefetch.context.TimeClock; import io.hummer.prefetch.context.Context.ContextChangeListener; import io.hummer.prefetch.context.Time; import io.hummer.prefetch.impl.InvocationComparator; import io.hummer.prefetch.impl.PrefetchingServiceImpl; import io.hummer.prefetch.sim.util.AuditEvent; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.xml.soap.SOAPEnvelope; import org.apache.log4j.Logger; import org.w3c.dom.Element; import io.hummer.util.coll.CollectionsUtil.MapBuilder; import io.hummer.util.xml.XMLUtil; /** * Implementation of a client which is capable of service prefetching. * @author Waldemar Hummer */ public class PrefetchingCapableClient extends PrefetchingServiceImpl implements PrefetchingResultReceiver, ContextChangeListener<Object> { private static final Logger LOG = Logger.getLogger(PrefetchingCapableClient.class); private static final XMLUtil xmlUtil = new XMLUtil(); private Context<Object> context; private Map<ServiceInvocation,PrefetchEntry> prefetchedResults = new HashMap<>(); private boolean deleteCachedResultsWhenReturned = false; private double lastCleanupTime = 0; private double deleteCachedResultsAfter = 20*60; // TODO make configurable private static class PrefetchEntry { SOAPEnvelope result; double prefetchTime; final List<Double> accessTimes = new LinkedList<>(); @Override public String toString() { return "PrefetchEntry[result=" + result + ",time=" + prefetchTime + "]"; } } public PrefetchingCapableClient(Context<Object> context) { super(context); this.context = context; } public SOAPEnvelope invoke(ServiceInvocation inv) throws IOException { return invoke(inv, new InvocationComparator.DefaultComparator()); } public SOAPEnvelope invoke(ServiceInvocation inv, InvocationComparator comp) throws IOException { //Time ctxTime = (Time)context.getAttribute(Context.ATTR_TIME); double time = getTime(); try { Boolean netAvail = (Boolean)context.getAttribute(Context.ATTR_NETWORK_AVAILABLE); if(netAvail != null && netAvail) { LOG.debug("Invocation: " + Util.toString(inv.serviceCall)); SOAPEnvelope res = performInvocation(inv); AuditEvent.addEvent(time, AuditEvent.E_INV_SUCCESS); return res; } } catch (IOException e) { LOG.debug("Cannot invoke service.", e); throw e; } AuditEvent.addEvent(time, AuditEvent.E_INV_FAILED); ServiceInvocation existing = comp.matchOne(prefetchedResults.keySet(), inv); if(existing == null) { if(!prefetchedResults.isEmpty()) LOG.debug("prefetchedResults: " + Util.toString(prefetchedResults.keySet().iterator().next())); String msg = "Network unavailable and " + "currently no prefetched result available " + "for service invocation: " + xmlUtil. getSOAPBodyAsString((Element)inv.serviceCall); LOG.info(msg); if(LOG.isDebugEnabled()) { for(ServiceInvocation existInv : prefetchedResults.keySet()) { LOG.debug("Existing: " + xmlUtil. getSOAPBodyAsString((Element)existInv.serviceCall)); } } AuditEvent.addEvent(time, AuditEvent.E_PREFETCH_MISS); throw new IllegalStateException(msg); } PrefetchEntry existingEntry = prefetchedResults.get(existing); SOAPEnvelope result = existingEntry.result; LOG.debug("Prefetch hit: " + Util.toString(existing.serviceCall)); AuditEvent.addEvent(time, AuditEvent.E_PREFETCH_HIT, MapBuilder.map(Context.ATTR_TIME, prefetchedResults.get(existing).prefetchTime)); existingEntry.accessTimes.add(TimeClock.now()); if(deleteCachedResultsWhenReturned) { prefetchedResults.remove(existing); } cleanCacheIfNecessary(time); return result; } private void cleanCacheIfNecessary(double currentTime) { //System.out.println(lastCleanupTime + " - " + deleteCachedResultsAfter + " -" + now); if(lastCleanupTime + deleteCachedResultsAfter > currentTime) { return; } for(ServiceInvocation i : new HashSet<>(prefetchedResults.keySet())) { PrefetchEntry e = prefetchedResults.get(i); if(e.prefetchTime < currentTime - deleteCachedResultsAfter) { if(e.accessTimes.isEmpty()) { AuditEvent.addEvent(currentTime, AuditEvent.E_UNUSED_RESULT); } LOG.debug("Cleanup client: " + e); prefetchedResults.remove(i); } } lastCleanupTime = currentTime; } @Override public PrefetchResponse setPrefetchingStrategy( PrefetchRequest request) { PrefetchResponse r = super.setPrefetchingStrategy( request.setNotifyLocal(this)); return r; } public Context<Object> getContext() { return context; } @Override public void notify(PrefetchNotification notification) { if(LOG.isDebugEnabled()) LOG.debug("Got prefetch result for " + Util.toString(notification.serviceInvocation.serviceCall)); if(notification.result != null) { ServiceInvocation existing = new InvocationComparator.DefaultComparator() .matchOne(prefetchedResults.keySet(), notification.serviceInvocation); PrefetchEntry entry = new PrefetchEntry(); entry.prefetchTime = TimeClock.now(); entry.result = notification.result; if(existing == null) { prefetchedResults.put(notification.serviceInvocation, entry); } else { PrefetchEntry e = prefetchedResults.get(existing); if(e.accessTimes.isEmpty()) { AuditEvent.addEvent(getTime(), AuditEvent.E_UNUSED_RESULT); } prefetchedResults.put(existing, entry); } //System.out.println("prefetchedResults: " + prefetchedResults); } } private double getTime() { Time ctxTime = (Time)context.getAttribute(Context.ATTR_TIME); if(ctxTime != null) { return ctxTime.time; } return TimeClock.now(); } public void onContextChanged(Map<String,Object> attrs, Map<String,Object> oldAttrs) { LOG.trace("Context has changed. Checking if we need to prefetch."); for(PrefetchSubscription sub : prefetchings.values()) { handleRequest(sub); } } }