From 1edfd4e0be335ca6932b47663c2ac76d22d74a39 Mon Sep 17 00:00:00 2001 From: khituras Date: Fri, 28 Aug 2020 18:18:12 +0200 Subject: [PATCH] Fixes #122. Solved the synchronization issues with the GePiDataService. 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. --- .../gepi/core/retrieval/data/GePiData.java | 51 ++++++++ ...sDataManager.java => GePiDataService.java} | 40 +++++- .../gepi/core/services/GepiCoreModule.java | 2 +- .../core/services/IChartsDataManager.java | 29 ----- .../gepi/core/services/IGePiDataService.java | 58 +++++++++ .../core/services/ChartsDataManagerTest.java | 2 +- .../webapp/components/BarChartWidget.java | 4 +- .../gepi/webapp/components/CircleWidget.java | 4 - .../gepi/webapp/components/GepiInput.java | 42 ++++++- .../gepi/webapp/components/GepiWidget.java | 34 +++-- .../webapp/components/GepiWidgetLayout.java | 25 ++-- .../webapp/components/PieChartWidget.java | 6 +- .../gepi/webapp/components/SankeyWidget.java | 7 -- .../SankeyWidgetCommonPartners.java | 4 +- .../de/julielab/gepi/webapp/pages/Index.java | 110 +++++++---------- .../gepi/webapp/services/AppModule.java | 116 ++++++++++-------- .../META-INF/modules/gepi/charts/data.js | 13 +- .../modules/gepi/charts/sankeychart.js | 2 +- .../gepi/webapp/components/GepiInput.tml | 6 +- .../gepi/webapp/components/SankeyWidget.tml | 2 +- .../webapp/components/TableResultWidget.tml | 2 +- .../de/julielab/gepi/webapp/pages/Index.tml | 6 +- 22 files changed, 349 insertions(+), 216 deletions(-) create mode 100644 gepi/gepi-core/src/main/java/de/julielab/gepi/core/retrieval/data/GePiData.java rename gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/{ChartsDataManager.java => GePiDataService.java} (87%) delete mode 100644 gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IChartsDataManager.java create mode 100644 gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IGePiDataService.java diff --git a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/retrieval/data/GePiData.java b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/retrieval/data/GePiData.java new file mode 100644 index 00000000..ead31b73 --- /dev/null +++ b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/retrieval/data/GePiData.java @@ -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 aggregatedResult; + private Future unrolledResult; + private Future listAIdConversionResult; + private Future listBIdConversionResult; + + public GePiData(Future aggregatedResult, Future unrolledResult, Future listAIdConversionResult, Future 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 getAggregatedResult() { + return aggregatedResult; + } + + public Future getUnrolledResult() { + return unrolledResult; + } + + public Future getListAIdConversionResult() { + return listAIdConversionResult; + } + + public Future getListBIdConversionResult() { + return listBIdConversionResult; + } +} diff --git a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/ChartsDataManager.java b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GePiDataService.java similarity index 87% rename from gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/ChartsDataManager.java rename to gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GePiDataService.java index 43749978..db48ae95 100644 --- a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/ChartsDataManager.java +++ b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GePiDataService.java @@ -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; @@ -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 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 diff --git a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GepiCoreModule.java b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GepiCoreModule.java index 8a11ae9d..de84e2c4 100644 --- a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GepiCoreModule.java +++ b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/GepiCoreModule.java @@ -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); } } diff --git a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IChartsDataManager.java b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IChartsDataManager.java deleted file mode 100644 index df704530..00000000 --- a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IChartsDataManager.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.julielab.gepi.core.services; - -import java.util.List; - -import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult; -import org.apache.tapestry5.json.JSONArray; - -import de.julielab.gepi.core.retrieval.data.Event; -import org.apache.tapestry5.json.JSONObject; - -public interface IChartsDataManager { - /** - * input structure for pie chart and bar chart - * @return JSONArray - json array of tuples (itself realised as an json array) - */ - JSONArray getTargetArgCount(List e); - - /** - * input structure required for sankey graph - * @return JSONArray - array of triplets ([, count]) - */ - JSONObject getPairedArgsCount(List e); - - JSONObject getPairedArgsCount(AggregatedEventsRetrievalResult aggregatedEvents); - - JSONObject getPairsWithCommonTarget(List evtList); - - JSONArray convertToJson(List eventList); -} diff --git a/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IGePiDataService.java b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IGePiDataService.java new file mode 100644 index 00000000..489e49be --- /dev/null +++ b/gepi/gepi-core/src/main/java/de/julielab/gepi/core/services/IGePiDataService.java @@ -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 { + + /** + *

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 data. The final session ID is returned.

+ * + * + * @param dataSessionId + * @param data The data of a new session. + */ + void putData(long dataSessionId, GePiData data); + + /** + *

Generates a new ID for a data session that can be used in {@link #getData(long)}}.

+ * + * @return A new data session ID. + */ + long newSession(); + + /** + *

Returns the data associated with the given session ID.

+ * + * @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 e); + + /** + * input structure required for sankey graph + * + * @return JSONArray - array of triplets ([, count]) + */ + JSONObject getPairedArgsCount(List e); + + JSONObject getPairedArgsCount(AggregatedEventsRetrievalResult aggregatedEvents); + + JSONObject getPairsWithCommonTarget(List evtList); + + JSONArray convertToJson(List eventList); +} diff --git a/gepi/gepi-core/src/test/java/de/julielab/gepi/core/services/ChartsDataManagerTest.java b/gepi/gepi-core/src/test/java/de/julielab/gepi/core/services/ChartsDataManagerTest.java index ea9c4643..a960ec7d 100644 --- a/gepi/gepi-core/src/test/java/de/julielab/gepi/core/services/ChartsDataManagerTest.java +++ b/gepi/gepi-core/src/test/java/de/julielab/gepi/core/services/ChartsDataManagerTest.java @@ -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); diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/BarChartWidget.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/BarChartWidget.java index cca9e456..c3ef1b75 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/BarChartWidget.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/BarChartWidget.java @@ -7,7 +7,7 @@ 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 { @@ -15,7 +15,7 @@ public class BarChartWidget extends GepiWidget { private JavaScriptSupport javaScriptSupport; @Inject - private IChartsDataManager gChartMnger; + private IGePiDataService gChartMnger; @Property private JSONArray eventsJSON; diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/CircleWidget.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/CircleWidget.java index 8bca2245..2c8486e3 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/CircleWidget.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/CircleWidget.java @@ -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 diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiInput.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiInput.java index bd835781..c3b81a04 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiInput.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiInput.java @@ -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; @@ -60,6 +60,9 @@ public class GepiInput { @InjectComponent private TextArea listb; + @InjectComponent + private TextField dataSessionIdField; + @Property @Persist private String listATextAreaValue; @@ -80,6 +83,9 @@ public class GepiInput { @Inject private IGeneIdService geneIdService; + @Inject + private IGePiDataService dataService; + @Parameter private CompletableFuture esResult; @@ -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); @@ -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"); diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidget.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidget.java index 3a3682e0..aaaf7bfa 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidget.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidget.java @@ -2,22 +2,24 @@ 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; @@ -25,25 +27,19 @@ public class GepiWidget { @InjectComponent private GepiWidgetLayout gepiWidgetLayout; + @Parameter + @Property + private long dataSessionId; + @InjectPage private Index index; - public CompletableFuture getEsResult() { - try { - log.debug("Trying to access index for ES result."); - return index.getEsResult(); - } finally { - log.debug("Retrieved ES result."); - } + public Future getEsResult() { + return dataService.getData(dataSessionId).getUnrolledResult(); } - public CompletableFuture getNeo4jResult() { - try { - log.debug("Trying to access index for Neo4j result."); - return index.getNeo4jResult(); - } finally { - log.debug("Retrieved Neo4j result."); - } + public Future getNeo4jResult() { + return dataService.getData(dataSessionId).getAggregatedResult(); } public boolean isLargeView() { diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidgetLayout.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidgetLayout.java index 657e8294..e877c3b2 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidgetLayout.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/GepiWidgetLayout.java @@ -6,10 +6,13 @@ import java.io.InputStream; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.stream.Stream; import java.util.Optional; import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult; +import de.julielab.gepi.core.services.GePiDataService; +import de.julielab.gepi.core.services.IGePiDataService; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -46,10 +49,13 @@ @Import(stylesheet = {"context:css-components/gepiwidgetlayout.css"}) @SupportsInformalParameters final public class GepiWidgetLayout { - + @Inject private Logger log; + @Inject + private IGePiDataService dataService; + @Parameter(defaultPrefix = BindingConstants.LITERAL) @Property private String widgettitle; @@ -71,6 +77,9 @@ final public class GepiWidgetLayout { @Parameter(value = "false") @Property private boolean useTapestryZoneUpdates; + @Parameter + private long dataSessionId; + @InjectComponent private Zone widgetZone; @Inject @@ -79,7 +88,7 @@ final public class GepiWidgetLayout { private ComponentResources resources; @Environmental private JavaScriptSupport javaScriptSupport; - + @Persist @Property private String viewMode; @@ -101,12 +110,12 @@ void afterRender() { } } - public CompletableFuture getEsResult() { - return index.getEsResult(); + public Future getEsResult() { + return dataService.getData(dataSessionId).getUnrolledResult(); } - public CompletableFuture getNeo4jResult() { - return index.getNeo4jResult(); + public Future getNeo4jResult() { + return dataService.getData(dataSessionId).getAggregatedResult(); } /** @@ -124,6 +133,7 @@ public JSONObject getWidgetSettings() { widgetSettings.put("refreshContentsUrl", refreshContentEventLink.toAbsoluteURI()); widgetSettings.put("zoneElementId", widgetZone.getClientId()); widgetSettings.put("useTapestryZoneUpdates", useTapestryZoneUpdates); + widgetSettings.put("dataSessionId", dataSessionId); return widgetSettings; } @@ -157,8 +167,7 @@ void onRefreshContent() throws InterruptedException, ExecutionException { log.debug("Waiting for ElasticSearch to return its results."); getEsResult().get(); log.debug("ES result finished."); - } - else if (getNeo4jResult() != null) { + } else if (getNeo4jResult() != null) { log.debug("Waiting for Neo4j to return its results."); getNeo4jResult().get(); } diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/PieChartWidget.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/PieChartWidget.java index 3f8c8e92..513b6d19 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/PieChartWidget.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/PieChartWidget.java @@ -1,14 +1,12 @@ package de.julielab.gepi.webapp.components; -import java.util.concurrent.ExecutionException; - 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 de.julielab.gepi.core.retrieval.data.Event; -import de.julielab.gepi.core.services.IChartsDataManager; +import de.julielab.gepi.core.services.IGePiDataService; public class PieChartWidget extends GepiWidget { @@ -19,7 +17,7 @@ public class PieChartWidget extends GepiWidget { private JavaScriptSupport javaScriptSupport; @Inject - private IChartsDataManager gChartMnger; + private IGePiDataService gChartMnger; @Property private JSONArray eventsJSON; diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidget.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidget.java index b6c2009f..3e4bdfdd 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidget.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidget.java @@ -1,20 +1,13 @@ package de.julielab.gepi.webapp.components; -import java.awt.*; -import java.util.concurrent.ExecutionException; - import org.apache.tapestry5.BindingConstants; import org.apache.tapestry5.annotations.InjectComponent; -import org.apache.tapestry5.annotations.InjectContainer; 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.runtime.Component; import org.apache.tapestry5.services.javascript.JavaScriptSupport; -import de.julielab.gepi.core.services.IChartsDataManager; - public class SankeyWidget extends GepiWidget { @Inject diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidgetCommonPartners.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidgetCommonPartners.java index 1cf20b8d..a887519a 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidgetCommonPartners.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/components/SankeyWidgetCommonPartners.java @@ -7,7 +7,7 @@ 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 SankeyWidgetCommonPartners extends GepiWidget { @@ -15,7 +15,7 @@ public class SankeyWidgetCommonPartners extends GepiWidget { private JavaScriptSupport javaScriptSupport; @Inject - private IChartsDataManager gChartMnger; + private IGePiDataService gChartMnger; @Property private JSONArray eventsJSON; diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/pages/Index.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/pages/Index.java index 2da849dc..9f7e8118 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/pages/Index.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/pages/Index.java @@ -1,20 +1,17 @@ package de.julielab.gepi.webapp.pages; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import de.julielab.gepi.core.retrieval.data.AggregatedEventsRetrievalResult; -import de.julielab.gepi.core.services.IChartsDataManager; +import de.julielab.gepi.core.retrieval.data.GePiData; +import de.julielab.gepi.core.services.IGePiDataService; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.EventContext; import org.apache.tapestry5.SymbolConstants; -import org.apache.tapestry5.annotations.ActivationRequestParameter; -import org.apache.tapestry5.annotations.Environmental; -import org.apache.tapestry5.annotations.Import; -import org.apache.tapestry5.annotations.InjectComponent; -import org.apache.tapestry5.annotations.InjectPage; -import org.apache.tapestry5.annotations.Persist; -import org.apache.tapestry5.annotations.Property; +import org.apache.tapestry5.annotations.*; import org.apache.tapestry5.corelib.components.Zone; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Symbol; @@ -33,9 +30,9 @@ @Import(stylesheet = {"context:css-pages/index.less"}, library = {"context:mybootstrap/js/dropdown.js"}) public class Index { @Inject - ComponentResources resources; + private ComponentResources resources; @Inject - Request request; + private Request request; @Inject private Logger log; @Environmental @@ -50,59 +47,20 @@ public class Index { private Zone outputZone; @InjectComponent private Zone inputZone; - @Persist - private CompletableFuture esResult; - @Persist - private CompletableFuture neo4jResult; @Property private Event eventItem; + @Property + @Persist + private long dataSessionId; + @Parameter + private long persistDataSessionId; @Persist private boolean hasLargeWidget; private boolean resultNonNullOnLoad; - /** - * This is an emergency exit against being locked in an error during development. - */ - @ActivationRequestParameter - private boolean reset; - @Inject - private IChartsDataManager chartMnger; - - public CompletableFuture getEsResult() { - try { - log.debug("Trying to access ES result."); - return esResult; - } finally { - log.debug("Retrieved ES result"); - } - } - - public CompletableFuture getNeo4jResult() { - - try { - log.debug("Trying to access index for Neo4j result."); - return neo4jResult; - } finally { - log.debug("Retrieved Neo4j result."); - } - } - public void setEsResult(CompletableFuture esResult) { - try { - log.debug("Trying to access index to set ES result."); - this.esResult = esResult; - } finally { - log.debug("Set ES result."); - } - } + @Inject + private IGePiDataService dataService; - public void setNeo4jResult(CompletableFuture neo4jResult) { - try { - log.debug("Trying to access index to set Neo4j result."); - this.neo4jResult = neo4jResult; - } finally { - log.debug("Set Neo4j result."); - } - } public Zone getOutputZone() { return outputZone; @@ -113,15 +71,18 @@ public Zone getInputZone() { } void setupRender() { - resultNonNullOnLoad = esResult != null || neo4jResult != null; + if (dataSessionId == 0) { + dataSessionId = dataService.newSession(); + log.debug("Current dataSessionId is 0, initializing GePi session with ID {}", dataSessionId); + } else { + log.debug("Existing dataSessionId is {}", dataSessionId); + } + GePiData data = dataService.getData(dataSessionId); + resultNonNullOnLoad = data != null && (data.getUnrolledResult() != null || data.getAggregatedResult() != null); } // Handle call with an unwanted context Object onActivate(EventContext eventContext) { - if (reset) { - esResult = null; - neo4jResult = null; - } return eventContext.getCount() > 0 ? new HttpError(404, "Resource not found") : null; } @@ -136,7 +97,18 @@ void afterRender() { } } + private Future getEsResult() { + System.out.println("persistent dataSessionId for getEsResult " + persistDataSessionId); + return dataService.getData(persistDataSessionId).getUnrolledResult(); + } + + private Future getNeo4jResult() { + return dataService.getData(persistDataSessionId).getAggregatedResult(); + } + public boolean isResultPresent() { + Future esResult = getEsResult(); + Future neo4jResult = getNeo4jResult(); return (esResult != null && esResult.isDone()) || (neo4jResult != null && neo4jResult.isDone()); } @@ -150,14 +122,14 @@ public String getShowOutputClass() { } public String getShowInputClass() { - if (esResult == null && neo4jResult == null) + if (getEsResult() == null && getNeo4jResult() == null) return "into"; return ""; } public Object onReset() { - esResult = null; - neo4jResult = null; + dataSessionId = 0; + persistDataSessionId = 0; return this; } @@ -184,16 +156,18 @@ public String getWidgetOverlayShowClass() { */ JSONObject onLoadDataToClient() { String datasource = request.getParameter("datasource"); - log.debug("Received data request for '{}' from the client.", datasource); + long dataSessionId = Long.parseLong(Optional.ofNullable(request.getParameter("dataSessionId")).orElse("0")); + log.debug("Received data request for '{}' for dataSessionId {} from the client.", datasource, dataSessionId); if (!datasource.equals("relationCounts")) throw new IllegalArgumentException("Unknown data source " + datasource); log.debug("Checked datasource name"); - if (getEsResult() == null && getNeo4jResult() == null) - throw new IllegalStateException("The ES result and the Neo4j result are both null."); + GePiData data = dataService.getData(dataSessionId); + if (data.getUnrolledResult() == null && data.getAggregatedResult() == null) + throw new IllegalStateException("The ES result and the Neo4j result for dataSessionId "+dataSessionId+" are both null."); log.debug("Checked if results are null."); try { log.debug("Creating JSON object from results.'"); - JSONObject jsonObject = neo4jResult != null ? chartMnger.getPairedArgsCount(neo4jResult.get()) : chartMnger.getPairedArgsCount(esResult.get().getEventList()); + JSONObject jsonObject = data.getAggregatedResult() != null ? dataService.getPairedArgsCount(data.getAggregatedResult().get()) : dataService.getPairedArgsCount(data.getUnrolledResult().get().getEventList()); log.debug("Sending data of type {} with {} nodes and {} links to the client ", datasource, jsonObject.getJSONArray("nodes").length(), jsonObject.getJSONArray("links").length()); return jsonObject; } catch (InterruptedException | ExecutionException e) { diff --git a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/services/AppModule.java b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/services/AppModule.java index 102f70c1..b5118d80 100644 --- a/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/services/AppModule.java +++ b/gepi/gepi-webapp/src/main/java/de/julielab/gepi/webapp/services/AppModule.java @@ -2,20 +2,17 @@ import java.io.IOException; +import de.julielab.gepi.webapp.pages.Index; +import org.apache.tapestry5.Link; import org.apache.tapestry5.SymbolConstants; +import org.apache.tapestry5.annotations.Service; import org.apache.tapestry5.ioc.MappedConfiguration; import org.apache.tapestry5.ioc.OrderedConfiguration; import org.apache.tapestry5.ioc.ServiceBinder; -import org.apache.tapestry5.ioc.annotations.Autobuild; -import org.apache.tapestry5.ioc.annotations.Contribute; -import org.apache.tapestry5.ioc.annotations.ImportModule; -import org.apache.tapestry5.ioc.annotations.Local; +import org.apache.tapestry5.ioc.annotations.*; import org.apache.tapestry5.ioc.services.ApplicationDefaults; import org.apache.tapestry5.ioc.services.SymbolProvider; -import org.apache.tapestry5.services.Request; -import org.apache.tapestry5.services.RequestFilter; -import org.apache.tapestry5.services.RequestHandler; -import org.apache.tapestry5.services.Response; +import org.apache.tapestry5.services.*; import org.slf4j.Logger; import de.julielab.gepi.core.services.ConfigurationSymbolProvider; @@ -26,15 +23,13 @@ * configure and extend Tapestry, or to place your own service definitions. */ @ImportModule(GepiCoreModule.class) -public class AppModule -{ - public static void contributeSymbolSource(@Autobuild ConfigurationSymbolProvider symbolProvider, - final OrderedConfiguration configuration) { - configuration.add("GePiConfigurationSymbols", symbolProvider, "before:ApplicationDefaults"); - } - - public static void bind(ServiceBinder binder) - { +public class AppModule { + public static void contributeSymbolSource(@Autobuild ConfigurationSymbolProvider symbolProvider, + final OrderedConfiguration configuration) { + configuration.add("GePiConfigurationSymbols", symbolProvider, "before:ApplicationDefaults"); + } + + public static void bind(ServiceBinder binder) { // binder.bind(MyServiceInterface.class, MyServiceImpl.class); // Make bind() calls on the binder object to define most IoC services. @@ -44,8 +39,7 @@ public static void bind(ServiceBinder binder) } public static void contributeFactoryDefaults( - MappedConfiguration configuration) - { + MappedConfiguration configuration) { // The values defined here (as factory default overrides) are themselves // overridden with application defaults by DevelopmentModule and QaModule. @@ -59,8 +53,7 @@ public static void contributeFactoryDefaults( } public static void contributeApplicationDefaults( - MappedConfiguration configuration) - { + MappedConfiguration configuration) { // Contributions to ApplicationDefaults will override any contributions to // FactoryDefaults (with the same key). Here we're restricting the supported // locales to just "en" (English). As you add localised message catalogs and other assets, @@ -68,24 +61,23 @@ public static void contributeApplicationDefaults( // the first locale name is the default when there's no reasonable match). configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en"); - // You should change the passphrase immediately; the HMAC passphrase is used to secure + // You should change the passphrase immediately; the HMAC passphrase is used to secure // the hidden field data stored in forms to encrypt and digitally sign client-side data. configuration.add(SymbolConstants.HMAC_PASSPHRASE, "juliegepipassphrase"); } - /** - * Use annotation or method naming convention: contributeApplicationDefaults - */ - @Contribute(SymbolProvider.class) - @ApplicationDefaults - public static void setupEnvironment(MappedConfiguration configuration) - { + /** + * Use annotation or method naming convention: contributeApplicationDefaults + */ + @Contribute(SymbolProvider.class) + @ApplicationDefaults + public static void setupEnvironment(MappedConfiguration configuration) { // Support for jQuery is new in Tapestry 5.4 and will become the only supported // option in 5.5. - configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery"); - configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "context:mybootstrap"); - configuration.add(SymbolConstants.MINIFICATION_ENABLED, false); - } + configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery"); + configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "context:mybootstrap"); + configuration.add(SymbolConstants.MINIFICATION_ENABLED, false); + } /** @@ -94,36 +86,32 @@ public static void setupEnvironment(MappedConfiguration configur * RequestHandler service configuration. Tapestry IoC is responsible for passing in an * appropriate Logger instance. Requests for static resources are handled at a higher level, so * this filter will only be invoked for Tapestry related requests. - * - * + *

+ *

* Service builder methods are useful when the implementation is inline as an inner class * (as here) or require some other kind of special initialization. In most cases, * use the static bind() method instead. - * - * + *

+ *

* If this method was named "build", then the service id would be taken from the * service interface and would be "RequestFilter". Since Tapestry already defines * a service named "RequestFilter" we use an explicit service id that we can reference * inside the contribution method. */ - public RequestFilter buildTimingFilter(final Logger log) - { - return new RequestFilter() - { + @ServiceId("timingFilter") + public RequestFilter buildTimingFilter(final Logger log) { + return new RequestFilter() { public boolean service(Request request, Response response, RequestHandler handler) - throws IOException - { + throws IOException { long startTime = System.currentTimeMillis(); - try - { + try { // The responsibility of a filter is to invoke the corresponding method // in the handler. When you chain multiple filters together, each filter // received a handler that is a bridge to the next filter. return handler.service(request, response); - } finally - { + } finally { long elapsed = System.currentTimeMillis() - startTime; log.info("Request time: {} ms", elapsed); @@ -132,6 +120,33 @@ public boolean service(Request request, Response response, RequestHandler handle }; } + @ServiceId("sessionCheckFilter") + public RequestFilter buildSessionCheckFilter(final Logger log, PageRenderLinkSource pageRenderLinkSource) { + return (request, response, handler) -> { + Session session = request.getSession(false); +// log.debug("Session is {}", session); + if (session != null){ + for (String name : session.getAttributeNames()) { + log.debug("Session attribute {} has value {}", name, session.getAttribute(name)); + } + log.debug("dataSessionId is {}", session.getAttribute("dataSessionId")); + } +// Link linkToRequestedPage = pageRenderLinkSource.createPageRenderLink(Index.class.getSimpleName()); +// boolean targetsIndex = request.getPath().contains(Index.class.getSimpleName()); +// if (!targetsIndex && session == null) { +// log.debug("Sending redirect to Index page because the session is null."); +// response.sendRedirect(linkToRequestedPage); +// } else if (!targetsIndex) { +// Object dataSessionId = session.getAttribute("dataSessionId"); +// if (dataSessionId == null || ((long) dataSessionId) == 0) { +// log.debug("Sending redirect to Index page because dataSessionId is 0."); +// response.sendRedirect(linkToRequestedPage); +// } +// } + return handler.service(request, response); + }; + } + /** * This is a contribution to the RequestHandler service configuration. This is how we extend * Tapestry using the timing filter. A common use for this kind of filter is transaction @@ -141,13 +156,14 @@ public boolean service(Request request, Response response, RequestHandler handle */ @Contribute(RequestHandler.class) public void addTimingFilter(OrderedConfiguration configuration, - @Local - RequestFilter filter) - { + @InjectService("timingFilter") + RequestFilter filter, + @InjectService("sessionCheckFilter") RequestFilter sessionCheckFilter) { // Each contribution to an ordered configuration has a name, When necessary, you may // set constraints to precisely control the invocation order of the contributed filter // within the pipeline. // configuration.add("Timing", filter); +// configuration.add("SessionCheck", sessionCheckFilter); } } diff --git a/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/data.js b/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/data.js index a6fbbf4b..d68d2ffc 100644 --- a/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/data.js +++ b/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/data.js @@ -35,9 +35,10 @@ define(["jquery", "t5/core/ajax", "gepi/charts/sankey/weightfunctions"], functio requestedData = new Map(); } - function loadData(source) { - console.log("Loading data with source " + source + " from " + dataUrl); - $.get(dataUrl, "datasource=" + source, data => setData(source, data)); + function loadData(source, dataSessionId) { + parameters = "datasource="+source+"&dataSessionId="+dataSessionId; + console.log("Loading data with parameters " + parameters + " from " + dataUrl); + $.get(dataUrl, parameters, data => setData(source, data)); } function setData(name, dataset) { @@ -50,14 +51,14 @@ define(["jquery", "t5/core/ajax", "gepi/charts/sankey/weightfunctions"], functio return data.get(name); } - function awaitData(sourceName) { - console.log("Data with source name " + sourceName + " was requested"); + function awaitData(sourceName, dataSessionId) { + console.log("Data with source name " + sourceName + " was requested for dataSessionId " + dataSessionId); let promise = requestedData.get(sourceName); if (!promise) { console.log("Creating new promise for data " + sourceName); promise = $.Deferred(); requestedData.set(sourceName, promise); - loadData(sourceName); + loadData(sourceName, dataSessionId); } else { console.log("Data with source name " + sourceName + " was already requested and is not loaded again."); } diff --git a/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/sankeychart.js b/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/sankeychart.js index 5707ec29..ead154d8 100644 --- a/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/sankeychart.js +++ b/gepi/gepi-webapp/src/main/resources/META-INF/modules/gepi/charts/sankeychart.js @@ -17,7 +17,7 @@ define(['jquery', 'gepi/charts/data', 'gepi/pages/index', 'gepi/components/widge index.getReadySemaphor().done(() => { console.log('Chart drawing has green light from the central index semaphor, requesting data'); - data.awaitData('relationCounts').done(() => { + data.awaitData('relationCounts', this.widgetSettings.dataSessionId).done(() => { console.log('Loading data was successful. Checking if the input column also gives green light.'); const inputcolReadyPromise = $('#inputcol').data('animationtimer'); if (inputcolReadyPromise) { diff --git a/gepi/gepi-webapp/src/main/resources/de/julielab/gepi/webapp/components/GepiInput.tml b/gepi/gepi-webapp/src/main/resources/de/julielab/gepi/webapp/components/GepiInput.tml index 8b95e020..d66a548b 100644 --- a/gepi/gepi-webapp/src/main/resources/de/julielab/gepi/webapp/components/GepiInput.tml +++ b/gepi/gepi-webapp/src/main/resources/de/julielab/gepi/webapp/components/GepiInput.tml @@ -7,6 +7,8 @@

+ +