diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index 5d943eeb607..e6afbfc6ff3 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -7,7 +7,7 @@ - + @@ -53,7 +53,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/Search.html b/java/com.sap.sailing.gwt.ui/Search.html new file mode 100755 index 00000000000..02da77912e7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Search.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + SAP Sailing Analytics Search Demonstration + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index b89a2d73a75..ecb6c42d0b6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -10,6 +10,7 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.RemoteService; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; @@ -56,6 +57,7 @@ import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; @@ -83,6 +85,7 @@ import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.search.KeywordQuery; /** * The client side stub for the RPC service. Usually, when a null date is passed to @@ -480,5 +483,31 @@ public interface SailingService extends RemoteService { Collection getGPSFixImporterTypes(); List getTrackFileImportDeviceIds(List uuids) - throws NoCorrespondingServiceRegisteredException, TransformationException; + throws NoCorrespondingServiceRegisteredException, TransformationException; + + /** + * A client should search a server in a two-step process. First, the client should ask the server which other + * servers are available for searching additional content. Then, in a second step, the client should fire the + * queries by parallel asynchronous calls to the one server, passing the name of the remote server reference to + * search, or null in order to search the server to which the query is sent by the call. This allows a + * client to asynchronously receive the results from various servers, not requiring the client to block until all + * results from all servers have been received. The key reason for this two-step process is that the GWT RPC does + * not support streaming of results. + * + * @return the list of server reference names, corresponding with {@link RemoteSailingServerReference#getName()}, to + * be used as parameter in {@link #search(String, KeywordQuery)}. This list does not contain the + * null value used to represent the search on the main server to which the query is sent. + */ + Iterable getSearchServerNames(); + + /** + * Call this method once for each result of {@link #getSearchServerNames()} and once with null for + * the serverNameOfNullForMain parameter. + * + * @param serverNameOrNullForMain + * use null to search on the server to which this request is sent; use a name as retrieved + * by {@link #getSearchServerNames()} which corresponds to a name of a + * {@link RemoteSailingServerReference}, to search a remote server. + */ + Iterable search(String serverNameOrNullForMain, KeywordQuery query) throws Exception; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index b54e1093b16..3e13a5023b3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.UUID; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; @@ -47,6 +48,7 @@ import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; @@ -73,6 +75,7 @@ import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.search.KeywordQuery; /** * The async counterpart of {@link SailingService} @@ -598,4 +601,32 @@ public interface SailingServiceAsync { void getGPSFixImporterTypes(AsyncCallback> callback); void getTrackFileImportDeviceIds(List uuids, AsyncCallback> callback); + + /** + * A client should search a server in a two-step process. First, the client should ask the server which other + * servers are available for searching additional content. Then, in a second step, the client should fire the + * queries by parallel asynchronous calls to the one server, passing the name of the remote server reference to + * search, or null in order to search the server to which the query is sent by the call. This allows a + * client to asynchronously receive the results from various servers, not requiring the client to block until all + * results from all servers have been received. The key reason for this two-step process is that the GWT RPC does + * not support streaming of results. + * + * @return the list of server reference names, corresponding with {@link RemoteSailingServerReference#getName()}, to + * be used as parameter in {@link #search(String, KeywordQuery)}. This list does not contain the + * null value used to represent the search on the main server to which the query is sent. + */ + void getSearchServerNames(AsyncCallback> callback); + + + /** + * Call this method once for each result of {@link #getSearchServerNames()} and once with null for + * the serverNameOfNullForMain parameter. + * + * @param serverNameOrNullForMain + * use null to search on the server to which this request is sent; use a name as retrieved + * by {@link #getSearchServerNames()} which corresponds to a name of a + * {@link RemoteSailingServerReference}, to search a remote server. + */ + void search(String serverNameOrNullForMain, KeywordQuery query, + AsyncCallback> callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java new file mode 100755 index 00000000000..6c300d20805 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/search/SearchEntryPoint.java @@ -0,0 +1,88 @@ +package com.sap.sailing.gwt.ui.client.search; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; +import com.sap.sailing.gwt.ui.client.SailingService; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; +import com.sap.sse.common.search.KeywordQuery; + +/** + * Sample entry point demonstrating the search capabilites of + * {@link SailingServiceAsync#search(String, com.sap.sse.common.search.KeywordQuery, AsyncCallback)} and + * {@link SailingServiceAsync#getSearchServerNames(AsyncCallback)}. + * + * @author Axel Uhl (D043530) + * + */ +public class SearchEntryPoint extends AbstractEntryPoint { + private final SailingServiceAsync sailingService = GWT.create(SailingService.class); + + @Override + protected void doOnModuleLoad() { + super.doOnModuleLoad(); + registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + VerticalPanel searchAndResults = new VerticalPanel(); + RootPanel.get().add(searchAndResults); + HorizontalPanel searchPanel = new HorizontalPanel(); + searchAndResults.add(searchPanel); + searchPanel.add(new Label("Search")); + final TextBox searchBox = new TextBox(); + searchPanel.add(searchBox); + Button searchButton = new Button("Search"); + searchPanel.add(searchButton); + final TextArea resultsLabel = new TextArea(); + resultsLabel.setEnabled(false); + searchAndResults.add(resultsLabel); + searchButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + final KeywordQuery query = new KeywordQuery(searchBox.getText().split(" ")); + resultsLabel.setText(""); + sailingService.getSearchServerNames(new AsyncCallback>() { + @Override + public void onSuccess(Iterable serverNames) { + search(resultsLabel, query, /* null meaning the "local" server */ null); + for (String serverName : serverNames) { + search(resultsLabel, query, serverName); + } + } + + @Override + public void onFailure(Throwable caught) { + Window.alert("Error: "+caught.getMessage()); + } + }); + } + }); + } + + private void search(final TextArea resultsLabel, final KeywordQuery query, final String serverName) { + sailingService.search(serverName, query, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + Window.alert("Error: "+caught.getMessage()); + } + + @Override + public void onSuccess(Iterable result) { + for (LeaderboardSearchResultDTO r : result) { + resultsLabel.setText(resultsLabel.getText()+"\n"+r); + } + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 52a2f84eaf7..cc84e749d99 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -65,6 +65,8 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.RaceColumn; @@ -270,6 +272,7 @@ import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.GateDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupBaseDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardSearchResultDTO; import com.sap.sailing.gwt.ui.shared.LegInfoDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; @@ -378,6 +381,8 @@ import com.sap.sailing.server.replication.impl.ReplicaDescriptor; import com.sap.sailing.util.BuildVersion; import com.sap.sailing.xrr.resultimport.schema.RegattaResults; import com.sap.sse.common.Util; +import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.common.search.Result; /** * The server side implementation of the RPC service. @@ -3112,16 +3117,26 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } private EventBaseDTO convertToEventDTO(EventBase event) { - List lgDTOs = new ArrayList<>(); - if (event.getLeaderboardGroups() != null) { - for (LeaderboardGroupBase lgBase : event.getLeaderboardGroups()) { - lgDTOs.add(new LeaderboardGroupBaseDTO(lgBase.getId(), lgBase.getName(), lgBase.getDescription(), lgBase.hasOverallLeaderboard())); + final EventBaseDTO eventDTO; + if (event == null) { + eventDTO = null; + } else { + List lgDTOs = new ArrayList<>(); + if (event.getLeaderboardGroups() != null) { + for (LeaderboardGroupBase lgBase : event.getLeaderboardGroups()) { + lgDTOs.add(convertToLeaderboardGroupBaseDTO(lgBase)); + } } + eventDTO = new EventBaseDTO(event.getName(), lgDTOs); + copyEventBaseFieldToDTO(event, eventDTO); } - EventBaseDTO eventDTO = new EventBaseDTO(event.getName(), lgDTOs); - copyEventBaseFieldToDTO(event, eventDTO); return eventDTO; } + + private LeaderboardGroupBaseDTO convertToLeaderboardGroupBaseDTO(LeaderboardGroupBase leaderboardGroupBase) { + return new LeaderboardGroupBaseDTO(leaderboardGroupBase.getId(), leaderboardGroupBase.getName(), + leaderboardGroupBase.getDescription(), leaderboardGroupBase.hasOverallLeaderboard()); + } private void copyEventBaseFieldToDTO(EventBase event, EventBaseDTO eventDTO) { eventDTO.venue = new VenueDTO(); @@ -4625,4 +4640,42 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return result; } + + @Override + public Iterable getSearchServerNames() { + List result = new ArrayList<>(); + for (RemoteSailingServerReference remoteServerRef : getService().getLiveRemoteServerReferences()) { + result.add(remoteServerRef.getName()); + } + return result; + } + + @Override + public Iterable search(String serverNameOrNullForMain, KeywordQuery query) throws MalformedURLException { + final List result = new ArrayList<>(); + if (serverNameOrNullForMain == null) { + Result searchResult = getService().search(query); + for (LeaderboardSearchResult hit : searchResult.getHits()) { + result.add(createLeaderboardSearchResultDTO(hit, getRequestBaseURL())); + } + } else { + RemoteSailingServerReference remoteRef = getService().getRemoteServerReferenceByName(serverNameOrNullForMain); + for (LeaderboardSearchResultBase hit : getService().searchRemotely(serverNameOrNullForMain, query).getHits()) { + result.add(createLeaderboardSearchResultDTO(hit, remoteRef.getURL())); + } + } + return result; + } + + private LeaderboardSearchResultDTO createLeaderboardSearchResultDTO(LeaderboardSearchResultBase leaderboardSearchResult, URL baseURL) { + ArrayList leaderboardGroups = new ArrayList<>(); + for (LeaderboardGroupBase lgb : leaderboardSearchResult.getLeaderboardGroups()) { + LeaderboardGroupBaseDTO leaderboardGroupDTO = convertToLeaderboardGroupBaseDTO(lgb); + leaderboardGroups.add(leaderboardGroupDTO); + } + return new LeaderboardSearchResultDTO(baseURL.toString(), leaderboardSearchResult.getLeaderboard().getName(), + leaderboardSearchResult.getLeaderboard().getDisplayName(), leaderboardSearchResult.getRegattaName(), + leaderboardSearchResult.getBoatClassName(), convertToEventDTO(leaderboardSearchResult.getEvent()), + leaderboardGroups); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml new file mode 100755 index 00000000000..177244e8f24 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Search.gwt.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index 62d95784be6..a24a0a86440 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -22,6 +22,7 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; @@ -556,5 +557,25 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch */ Util.Pair, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref); + /** + * Searches the content of this server, not that of any remote servers referenced by any {@link RemoteSailingServerReference}s. + */ Result search(KeywordQuery query); + + /** + * Searches a specific remote server whose reference has the {@link RemoteSailingServerReference#getName() name} + * remoteServerReferenceName. If a remote server reference with that name is not known, + * null is returned. Otherwise, a non-null and possibly empty search result set is + * returned. + */ + Result searchRemotely(String remoteServerReferenceName, KeywordQuery query); + + /** + * References to remote servers may be dead or alive. This is internally determined by regularly polling those + * servers for their events list. If the events list cannot be successfully retrieved, the server is considered "dead." + * This method returns the "live" server references. + */ + Iterable getLiveRemoteServerReferences(); + + RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName); } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaSearchResultRanker.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/LeaderboardSearchResultBaseRanker.java similarity index 66% rename from java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaSearchResultRanker.java rename to java/com.sap.sailing.server/src/com/sap/sailing/server/impl/LeaderboardSearchResultBaseRanker.java index 1b0ea9adaad..a942f6d21a2 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaSearchResultRanker.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/LeaderboardSearchResultBaseRanker.java @@ -5,28 +5,18 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.server.RacingEventService; import com.sap.sse.common.Util; -public class RegattaSearchResultRanker implements Comparator { - private final TrackedRegattaRegistry trackedRegattaRegistry; - - protected RegattaSearchResultRanker(RacingEventService racingEventService) { - this.trackedRegattaRegistry = racingEventService; - } - +public class LeaderboardSearchResultBaseRanker implements Comparator { @Override - public int compare(LeaderboardSearchResult o1, LeaderboardSearchResult o2) { - Regatta r1 = o1.getRegatta(); - Regatta r2 = o2.getRegatta(); - TrackedRegatta trackedR1 = r1 == null ? null : trackedRegattaRegistry.getTrackedRegatta(r1); - TrackedRegatta trackedR2 = r2 == null ? null : trackedRegattaRegistry.getTrackedRegatta(r2); + public int compare(T o1, T o2) { + TrackedRegatta trackedR1 = getTrackedRegatta(o1); + TrackedRegatta trackedR2 = getTrackedRegatta(o2); final int result; if (trackedR1 == null) { if (trackedR2 == null) { @@ -44,16 +34,8 @@ public class RegattaSearchResultRanker implements Comparator startOfTrackingTimes = new ArrayList<>(); for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) { if (trackedRace.getStartOfTracking() != null) { @@ -68,4 +50,20 @@ public class RegattaSearchResultRanker implements Comparator { + private final TrackedRegattaRegistry trackedRegattaRegistry; + + protected LeaderboardSearchResultRanker(RacingEventService racingEventService) { + this.trackedRegattaRegistry = racingEventService; + } + + protected TrackedRegatta getTrackedRegatta(LeaderboardSearchResult o1) { + Regatta r1 = o1.getRegatta(); + TrackedRegatta trackedR1 = r1 == null ? null : trackedRegattaRegistry.getTrackedRegatta(r1); + return trackedR1; + } +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index c695970aa69..99b138d8de8 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1,12 +1,16 @@ package com.sap.sailing.server.impl; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.MalformedURLException; import java.net.SocketException; import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -28,6 +32,10 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; @@ -40,6 +48,7 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; @@ -135,6 +144,11 @@ import com.sap.sailing.server.OperationExecutionListener; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sailing.server.Replicator; +import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; import com.sap.sailing.server.masterdata.DataImportLockWithProgress; import com.sap.sailing.server.operationaltransformation.AddCourseArea; import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta; @@ -174,6 +188,7 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock; import com.sap.sse.common.Util; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; +import com.sap.sse.common.search.ResultImpl; public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport, RegattaListener, LeaderboardRegistry, Replicator { private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName()); @@ -881,6 +896,16 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport mongoObjectFactory.storeSailingServer(result); return result; } + + @Override + public Iterable getLiveRemoteServerReferences() { + return remoteSailingServerSet.getLiveRemoteServerReferences(); + } + + @Override + public RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName) { + return remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName); + } @Override public com.sap.sse.common.Util.Pair, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref) { @@ -2594,6 +2619,53 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport @Override public Result search(KeywordQuery query) { - return new RegattaByKeywordSearchService().search(this, query); + long start = System.currentTimeMillis(); + logger.info("Searching local server for "+query); + Result result = new RegattaByKeywordSearchService().search(this, query); + logger.fine("Search for "+query+" took "+(System.currentTimeMillis()-start)+"ms"); + return result; + } + + @Override + public Result searchRemotely(String remoteServerReferenceName, KeywordQuery query) { + long start = System.currentTimeMillis(); + ResultImpl result = null; + RemoteSailingServerReference remoteRef = remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName); + if (remoteRef == null) { + result = null; + } else { + BufferedReader bufferedReader = null; + try { + try { + final URL eventsURL = new URL(remoteRef.getURL(), "sailingserver/api/v1/search?q="+URLEncoder.encode(query.toString(), "UTF-8")); + logger.info("Searching remote server "+remoteRef+" for "+query); + URLConnection urlConnection = eventsURL.openConnection(); + urlConnection.connect(); + bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + JSONParser parser = new JSONParser(); + Object eventsAsObject = parser.parse(bufferedReader); + final LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); + LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( + new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer( + DomainFactory.INSTANCE)), leaderboardGroupBaseJsonDeserializer), + leaderboardGroupBaseJsonDeserializer); + result = new ResultImpl(query, new LeaderboardSearchResultBaseRanker()); + JSONArray hitsAsJsonArray = (JSONArray) eventsAsObject; + for (Object hitAsObject : hitsAsJsonArray) { + JSONObject hitAsJson = (JSONObject) hitAsObject; + LeaderboardSearchResultBase hit = deserializer.deserialize(hitAsJson); + result.addHit(hit); + } + } finally { + if (bufferedReader != null) { + bufferedReader.close(); + } + } + } catch (IOException | ParseException e) { + logger.log(Level.INFO, "Exception trying to fetch events from remote server "+remoteRef+": "+e.getMessage(), e); + } + } + logger.fine("Remote search on "+remoteRef+" for "+query+" took "+(System.currentTimeMillis()-start)+"ms"); + return result; } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java index a2fdd73974f..72aa4fce917 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java @@ -33,7 +33,7 @@ import com.sap.sse.common.search.ResultImpl; */ public class RegattaByKeywordSearchService { Result search(final RacingEventService racingEventService, KeywordQuery query) { - ResultImpl result = new ResultImpl<>(query, new RegattaSearchResultRanker(racingEventService)); + ResultImpl result = new ResultImpl<>(query, new LeaderboardSearchResultRanker(racingEventService)); final Map eventForLeaderboard = new HashMap<>(); final Map> leaderboardGroupsForLeaderboard = new HashMap<>(); AbstractListFilter leaderboardFilter = new AbstractListFilter() { diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java index cae67699a95..1d23a8ceaca 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java @@ -6,8 +6,10 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -22,7 +24,6 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.RemoteSailingServerReference; import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; @@ -30,6 +31,7 @@ import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserial import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; /** * A set of {@link RemoteSailingServerReference}s including a cache of their {@link EventBase events} that is @@ -44,7 +46,8 @@ public class RemoteSailingServerSet { private static final Logger logger = Logger.getLogger(RemoteSailingServerSet.class.getName()); /** - * Holds the {@link Event} objects for the events of all registered sailing server instances. + * Holds the remote server references managed by this set. Keys are the + * {@link RemoteSailingServerReference#getName() names} of the server references. */ private final ConcurrentHashMap remoteSailingServers; @@ -76,6 +79,14 @@ public class RemoteSailingServerSet { triggerAsynchronousEventCacheUpdate(ref); } } + + /** + * If this set has a {@link RemoteSailingServerReference} whose {@link RemoteSailingServerReference#getName() name} equals + * name, it is returned. Otherwise, null is returned. + */ + public RemoteSailingServerReference getServerReferenceByName(String name) { + return remoteSailingServers.get(name); + } private void triggerAsynchronousEventCacheUpdate(final RemoteSailingServerReference ref) { new Thread("Event Cache Updater for remote server "+ref) { @@ -150,4 +161,15 @@ public class RemoteSailingServerSet { } return updateRemoteServerEventCacheSynchronously(ref); } + + public Iterable getLiveRemoteServerReferences() { + List result = new ArrayList<>(); + for (Map.Entry, Exception>> e : cachedEventsForRemoteSailingServers.entrySet()) { + if (e.getValue().getB() == null) { + // no exception; reference considered live + result.add(e.getKey()); + } + } + return result; + } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/search/KeywordQuery.java b/java/com.sap.sse.common/src/com/sap/sse/common/search/KeywordQuery.java index ef1e064979c..dc6a5a49d3c 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/search/KeywordQuery.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/search/KeywordQuery.java @@ -1,14 +1,19 @@ package com.sap.sse.common.search; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.sap.sse.common.Util; -public class KeywordQuery implements Query { - private final Iterable keywords; +public class KeywordQuery implements Query, Serializable { + private static final long serialVersionUID = -4214703751947494064L; + + private Iterable keywords; + KeywordQuery() {} // for GWT RPC serialization + public KeywordQuery(String... keywords) { this.keywords = trim(Arrays.asList(keywords)); } @@ -32,6 +37,16 @@ public class KeywordQuery implements Query { @Override public String toString() { - return ""+keywords; + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String keyword : keywords) { + if (first) { + first = false; + } else { + sb.append(' '); + } + sb.append(keyword); + } + return sb.toString(); } }