Skip to content

Commit

Permalink
Fixes #122. Solved the synchronization issues with the GePiDataService.
Browse files Browse the repository at this point in the history
All data now has a unique state associated with it. This dataSessionId is given around into the client to use as request context to identify the data to request. This circumvents the timing issues caused by synchronizing with respect to the Tapestry Session.
  • Loading branch information
khituras committed Aug 28, 2020
1 parent cf7f6de commit 1edfd4e
Show file tree
Hide file tree
Showing 22 changed files with 349 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.julielab.gepi.core.retrieval.data;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/**
* A single class to hold the input and result data of a GePi request.
*/
public class GePiData {
public static final GePiData EMPTY = new GePiData();
private long sessionId;
private Future<AggregatedEventsRetrievalResult> aggregatedResult;
private Future<EventRetrievalResult> unrolledResult;
private Future<IdConversionResult> listAIdConversionResult;
private Future<IdConversionResult> listBIdConversionResult;

public GePiData(Future<AggregatedEventsRetrievalResult> aggregatedResult, Future<EventRetrievalResult> unrolledResult, Future<IdConversionResult> listAIdConversionResult, Future<IdConversionResult> listBIdConversionResult) {
this.aggregatedResult = aggregatedResult;
this.unrolledResult = unrolledResult;
this.listAIdConversionResult = listAIdConversionResult;
this.listBIdConversionResult = listBIdConversionResult;
}

private GePiData() {
// for the EMPTY constant
}

public long getSessionId() {
return sessionId;
}

public void setSessionId(long sessionId) {
this.sessionId = sessionId;
}

public Future<AggregatedEventsRetrievalResult> getAggregatedResult() {
return aggregatedResult;
}

public Future<EventRetrievalResult> getUnrolledResult() {
return unrolledResult;
}

public Future<IdConversionResult> getListAIdConversionResult() {
return listAIdConversionResult;
}

public Future<IdConversionResult> getListBIdConversionResult() {
return listBIdConversionResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult;
import de.julielab.gepi.core.retrieval.data.GePiData;
import org.apache.commons.collections.CollectionUtils;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject;
Expand All @@ -16,9 +19,42 @@
import de.julielab.gepi.core.retrieval.data.Argument.ComparisonMode;
import de.julielab.gepi.core.retrieval.data.Event;

public class ChartsDataManager implements IChartsDataManager {
public class GePiDataService implements IGePiDataService {

private static final Logger log = LoggerFactory.getLogger(ChartsDataManager.class);
private static final Logger log = LoggerFactory.getLogger(GePiDataService.class);

private Cache<Long, GePiData> dataCache;

public GePiDataService() {
// We use weak keys and values. So when a user session is evicted,
// its GePi data can also be removed as soon as possible.
dataCache = CacheBuilder.newBuilder().weakValues().build();
}


@Override
public void putData(long dataSessionId, GePiData data) {
data.setSessionId(dataSessionId);
dataCache.put(dataSessionId, data);
}

@Override
public long newSession() {
long id;
synchronized (dataCache) {
do {
id = System.currentTimeMillis();
} while (dataCache.getIfPresent(id) != null);
}
return id;
}

@Override
public GePiData getData(long sessionId) {
GePiData data = dataCache.getIfPresent(sessionId);
log.trace("Data for dataSessionId {} was {}.", sessionId, data != null ? "found" : "not found");
return data != null ? data : GePiData.EMPTY;
}

/**
* sets json formated input list for google charts that accept one entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public static void bind (ServiceBinder binder) {
binder.bind(IEventResponseProcessingService.class, EventResponseProcessingService.class);
binder.bind(IEventPostProcessingService.class, EventPostProcessingService.class);
binder.bind(IGeneIdService.class, GeneIdService.class);
binder.bind(IChartsDataManager.class, ChartsDataManager.class);
binder.bind(IGePiDataService.class, GePiDataService.class);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package de.julielab.gepi.core.services;

import java.util.List;

import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult;
import de.julielab.gepi.core.retrieval.data.GePiData;
import org.apache.tapestry5.json.JSONArray;

import de.julielab.gepi.core.retrieval.data.Event;
import org.apache.tapestry5.json.JSONObject;

public interface IGePiDataService {

/**
* <p>Puts data to the data cache. If the data already has a session ID, the respective cache entry will be updated.
* Otherwise, a new ID will be generated and set to <tt>data</tt>. The final session ID is returned.</p>
*
*
* @param dataSessionId
* @param data The data of a new session.
*/
void putData(long dataSessionId, GePiData data);

/**
* <p>Generates a new ID for a data session that can be used in {@link #getData(long)}}.</p>
*
* @return A new data session ID.
*/
long newSession();

/**
* <p>Returns the data associated with the given session ID.</p>
*
* @param sessionId The session ID for which the data should be retrieved.
* @return The data of the session or {@link GePiData#EMPTY} if no such data exists.
*/
GePiData getData(long sessionId);

/**
* input structure for pie chart and bar chart
*
* @return JSONArray - json array of tuples (itself realised as an json array)
*/
JSONArray getTargetArgCount(List<Event> e);

/**
* input structure required for sankey graph
*
* @return JSONArray - array of triplets ([<from, <to>, count])
*/
JSONObject getPairedArgsCount(List<Event> e);

JSONObject getPairedArgsCount(AggregatedEventsRetrievalResult aggregatedEvents);

JSONObject getPairsWithCommonTarget(List<Event> evtList);

JSONArray convertToJson(List<Event> eventList);
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void testGetPairesWithCommonTarget() {
for (int i = 0; i < 10; i++)
events.add(e6);

final ChartsDataManager manager = new ChartsDataManager();
final GePiDataService manager = new GePiDataService();
final JSONObject nodesNLinks = manager.getPairsWithCommonTarget(events);
final JSONArray pairs = nodesNLinks.getJSONArray("links");
System.out.println(pairs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

import de.julielab.gepi.core.services.IChartsDataManager;
import de.julielab.gepi.core.services.IGePiDataService;

public class BarChartWidget extends GepiWidget {

@Inject
private JavaScriptSupport javaScriptSupport;

@Inject
private IChartsDataManager gChartMnger;
private IGePiDataService gChartMnger;

@Property
private JSONArray eventsJSON;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package de.julielab.gepi.webapp.components;

import de.julielab.gepi.core.services.IChartsDataManager;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

import java.util.concurrent.ExecutionException;

public class CircleWidget extends GepiWidget {

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import java.util.stream.Stream;

import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult;
import de.julielab.gepi.core.retrieval.data.GePiData;
import de.julielab.gepi.core.retrieval.services.IAggregatedEventsRetrievalService;
import de.julielab.gepi.core.retrieval.data.IdConversionResult;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.SelectModel;
import org.apache.tapestry5.ValueEncoder;
import de.julielab.gepi.core.services.IGePiDataService;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.*;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.TextArea;
import org.apache.tapestry5.corelib.components.TextField;
import org.apache.tapestry5.internal.OptionModelImpl;
import org.apache.tapestry5.internal.SelectModelImpl;
import org.apache.tapestry5.internal.services.StringValueEncoder;
Expand Down Expand Up @@ -60,6 +60,9 @@ public class GepiInput {
@InjectComponent
private TextArea listb;

@InjectComponent
private TextField dataSessionIdField;

@Property
@Persist
private String listATextAreaValue;
Expand All @@ -80,6 +83,9 @@ public class GepiInput {
@Inject
private IGeneIdService geneIdService;

@Inject
private IGePiDataService dataService;

@Parameter
private CompletableFuture<EventRetrievalResult> esResult;

Expand Down Expand Up @@ -109,9 +115,32 @@ public class GepiInput {
@Property
private String filterString;

/**
* This is not an ID for the servlet session but to the current data state.
*/
@Parameter
@Property
private long dataSessionId;

@Persist(PersistenceConstants.FLASH)
private boolean newSearch;

/**
* Do not access this field. It is only here to store the data in the session. Data access should happen through
* {@link de.julielab.gepi.core.services.GePiDataService}. There, the data is cached with weak keys and values.
* The idea is that the data is evicted when the session ends.
*/
@Persist
private GePiData data;
/**
* This is an emergency exit against being locked in an error during development.
*/
@ActivationRequestParameter
private boolean reset;
void onActivate(EventContext eventContext) {
if (reset) {
data = null;
}
}

public ValueEncoder getEventTypeEncoder() {
return new EnumValueEncoder(typeCoercer, EventTypes.class);
Expand Down Expand Up @@ -161,6 +190,9 @@ void onSuccessFromInputForm() {
fetchEventsFromNeo4j(selectedEventTypeNames, isAListPresent, isABSearchRequest);
// }

data = new GePiData(neo4jResult, esResult, listAGePiIds, listBGePiIds);
log.debug("Setting newly retrieved data for dataSessionId: {}", dataSessionId);
dataService.putData(dataSessionId, data);
Index indexPage = (Index) resources.getContainer();
ajaxResponseRenderer.addRender(indexPage.getInputZone()).addRender(indexPage.getOutputZone());
log.debug("Ajax rendering commands sent, entering the output display mode");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,44 @@

import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult;
import de.julielab.gepi.core.retrieval.data.EventRetrievalResult;
import de.julielab.gepi.core.services.GePiDataService;
import de.julielab.gepi.core.services.IGePiDataService;
import de.julielab.gepi.webapp.pages.Index;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.*;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.slf4j.Logger;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

public class GepiWidget {

@Inject
private Logger log;

@Inject
private IGePiDataService dataService;

@Parameter(defaultPrefix = BindingConstants.LITERAL, name = "class")
@Property
private String classes;

@InjectComponent
private GepiWidgetLayout gepiWidgetLayout;

@Parameter
@Property
private long dataSessionId;

@InjectPage
private Index index;

public CompletableFuture<EventRetrievalResult> getEsResult() {
try {
log.debug("Trying to access index for ES result.");
return index.getEsResult();
} finally {
log.debug("Retrieved ES result.");
}
public Future<EventRetrievalResult> getEsResult() {
return dataService.getData(dataSessionId).getUnrolledResult();
}

public CompletableFuture<AggregatedEventsRetrievalResult> getNeo4jResult() {
try {
log.debug("Trying to access index for Neo4j result.");
return index.getNeo4jResult();
} finally {
log.debug("Retrieved Neo4j result.");
}
public Future<AggregatedEventsRetrievalResult> getNeo4jResult() {
return dataService.getData(dataSessionId).getAggregatedResult();
}

public boolean isLargeView() {
Expand Down
Loading

0 comments on commit 1edfd4e

Please sign in to comment.