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 @@

+ +