Merge remote-tracking branch 'origin/master' into windestimation

Conflicts:
	java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8890,
auto-replicate from replica at 8889).launch
This commit is contained in:
Vladislav Chumak committed 2019-03-13 11:04:51 +01:00
commit f3cee8ee87
39 files changed
+551 -58

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath "com.android.tools.build:gradle:3.3.1"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.3"
classpath "org.ajoberstar:grgit:2.3.0"
}
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
@@ -1,8 +1,18 @@
package com.sap.sailing.declination.test;
import org.junit.Before;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.text.ParseException;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.impl.ColoradoImporter;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class ColoradoDeclinationServiceTest extends DeclinationServiceTest<ColoradoImporter> {
@Override
@@ -11,4 +21,13 @@ public class ColoradoDeclinationServiceTest extends DeclinationServiceTest<Color
importer = new ColoradoImporter();
super.setUp();
}
@Test
public void testDeclinationQueryNotMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2020-02-03").getTime()),
new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 5000);
assertNotNull(result);
assertEquals(-1.80464, result.getBearing().getDegrees(), 0.001);
assertEquals(0.15244, result.getAnnualChange().getDegrees(), 0.001);
}
}
@@ -22,8 +22,8 @@ public abstract class DeclinationImportTest<I extends DeclinationImporter> exten
public void importSimpleDeclination() throws IOException, ParseException, ParserConfigurationException, SAXException {
Declination record = importer.importRecord(new DegreePosition(53, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("2016-05-27").getTime()));
assertEquals(0.26307, record.getBearing().getDegrees(), 0.001);
assertEquals(0.14795, record.getAnnualChange().getDegrees(), 0.001);
assertEquals(0.26307, record.getBearing().getDegrees(), 0.05);
assertEquals(0.14795, record.getAnnualChange().getDegrees(), 0.05);
}
@Test
@@ -32,8 +32,8 @@ public abstract class DeclinationImportTest<I extends DeclinationImporter> exten
Declination record = importer.importRecord(new DegreePosition(-10, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("2017-05-27").getTime()));
System.out.println("took "+(System.currentTimeMillis()-start)+"ms");
assertEquals(-8.44581, record.getBearing().getDegrees(), 0.001);
assertEquals(0.17712, record.getAnnualChange().getDegrees(), 0.001);
assertEquals(-8.44581, record.getBearing().getDegrees(), 0.2);
assertEquals(0.17712, record.getAnnualChange().getDegrees(), 0.2);
}
@Test
@@ -8,6 +8,7 @@ import java.io.IOException;
import java.text.ParseException;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.declination.Declination;
@@ -19,7 +20,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public abstract class DeclinationServiceTest<I extends DeclinationImporter> extends AbstractDeclinationTest<I> {
private DeclinationService service;
protected DeclinationService service;
@Before
public void setUp() {
@@ -34,6 +35,7 @@ public abstract class DeclinationServiceTest<I extends DeclinationImporter> exte
assertEquals(0.+09./60., result.getAnnualChange().getDegrees(), 0.0000001);
}
@Ignore("Currently NOAA service ends Dec 2019; this test for now is only available for the Colorado importer")
@Test
public void testDeclinationQueryNotMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2020-02-03").getTime()),
@@ -1,11 +1,9 @@
package com.sap.sailing.declination.test;
import org.junit.Before;
import org.junit.Ignore;
import com.sap.sailing.declination.impl.NOAAImporterForTesting;
@Ignore("US Government Shutdown around 2018-01-22")
public class NOAADeclinationImportTest extends DeclinationImportTest<NOAAImporterForTesting> {
@Before
public void setUp() {
@@ -1,11 +1,9 @@
package com.sap.sailing.declination.test;
import org.junit.Before;
import org.junit.Ignore;
import com.sap.sailing.declination.impl.NOAAImporter;
@Ignore("US Government Shutdown around 2018-01-22")
public class NOAADeclinationServiceTest extends DeclinationServiceTest<NOAAImporter> {
@Override
@Before
@@ -1,11 +1,9 @@
package com.sap.sailing.declination.test;
import org.junit.Before;
import org.junit.Ignore;
import com.sap.sailing.declination.impl.NOAAImporter;
@Ignore("US Government Shutdown around 2018-01-22")
public class NOAADeclinationStoreTest extends DeclinationStoreTest<NOAAImporter> {
@Override
@Before
@@ -1,11 +1,9 @@
package com.sap.sailing.declination.test;
import org.junit.Before;
import org.junit.Ignore;
import com.sap.sailing.declination.impl.NOAAImporter;
@Ignore("US Government Shutdown around 2018-01-22")
public class NOAASimpleDeclinationTest extends SimpleDeclinationTest<NOAAImporter> {
@Before
public void setUp() {
@@ -23,21 +23,21 @@ public abstract class SimpleDeclinationTest<I extends DeclinationImporter> exten
public void testSimpleCorrection() throws IOException, ParseException, ParserConfigurationException, SAXException {
final MillisecondsTimePoint timePoint = new MillisecondsTimePoint(simpleDateFormat.parse("2019-05-27").getTime());
Declination record = importer.importRecord(new DegreePosition(53, 3), timePoint);
assertEquals(0.70594, record.getBearing().getDegrees(), 0.001);
assertEquals(0.14745, record.getAnnualChange().getDegrees(), 0.001);
assertEquals(0.70594, record.getBearing().getDegrees(), 0.2);
assertEquals(0.14745, record.getAnnualChange().getDegrees(), 0.2);
TimePoint oneYearLater = timePoint.plus(Duration.ONE_YEAR);
Bearing bearing = record.getBearingCorrectedTo(oneYearLater);
assertEquals(0.70594+0.14745, bearing.getDegrees(), 0.01);
assertEquals(0.70594+0.14745, bearing.getDegrees(), 0.2);
}
@Test
public void testBackwardCorrection() throws IOException, ParseException, ParserConfigurationException, SAXException {
final MillisecondsTimePoint timePoint = new MillisecondsTimePoint(simpleDateFormat.parse("2019-05-27").getTime());
Declination record = importer.importRecord(new DegreePosition(53, 3), timePoint);
assertEquals(0.70594, record.getBearing().getDegrees(), 0.001);
assertEquals(0.14745, record.getAnnualChange().getDegrees(), 0.001);
assertEquals(0.70594, record.getBearing().getDegrees(), 0.2);
assertEquals(0.14745, record.getAnnualChange().getDegrees(), 0.2);
TimePoint oneYearEarlier = timePoint.minus(Duration.ONE_YEAR);
Bearing bearing = record.getBearingCorrectedTo(oneYearEarlier);
assertEquals(0.70594-0.14745, bearing.getDegrees(), 0.01);
assertEquals(0.70594-0.14745, bearing.getDegrees(), 0.2);
}
}
@@ -14,8 +14,8 @@ import com.sap.sse.common.TimePoint;
public interface DeclinationService {
/**
* A default implementation with a spatial default precision of 1 {@link Mile#METERS_PER_GEOGRAPHICAL_MILE
* geographical mile} which equals the length of an arc with one degree on a meridian.
* A default implementation with a spatial default precision of 60 {@link Mile#METERS_PER_GEOGRAPHICAL_MILE
* nautical miles} which equals the length of an arc with one degree on a meridian.
*/
DeclinationService INSTANCE = new DeclinationServiceImpl(new CentralAngleDistance(1./180.*Math.PI), new NOAAImporter());
@@ -45,6 +45,9 @@ public abstract class DeclinationImporter {
}
} catch (IOException | ParserConfigurationException | SAXException e) {
logger.log(Level.FINE, "Exception while trying to load magnetic declination online", e);
synchronized (result) {
result.notifyAll(); // wake up waiter; no result will show up anymore
}
}
}
};
@@ -357,7 +357,7 @@ public abstract class AbstractLeaderboardDTO implements Serializable {
} else {
List<String> raceNames = new ArrayList<String>();
for (RaceColumnDTO race : races) {
raceNames.add(race.getName());
raceNames.add(race != null ? race.getName() : null);
}
result = prime * result + raceNames.hashCode();
}
@@ -403,11 +403,11 @@ public abstract class AbstractLeaderboardDTO implements Serializable {
List<String> raceColumnNames = new ArrayList<String>(races.size());
List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
for (RaceColumnDTO race : races) {
raceColumnNames.add(race.getName());
raceColumnNames.add(race != null ? race.getName() : null);
}
if (other.races != null) {
for (RaceColumnDTO otherRace : other.races) {
otherRaceColumnNames.add(otherRace.getName());
otherRaceColumnNames.add(otherRace != null ? otherRace.getName() : null);
}
}
if (!raceColumnNames.equals(otherRaceColumnNames))
@@ -3,6 +3,8 @@ package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Date;
import com.sap.sse.common.CacheableRPCResult;
/**
* Allows for incremental serialization of a {@link LeaderboardDTO} by providing an implementation that refers to a previous
* version and provides only updates, or alternatively sending the full leaderboard.
@@ -10,7 +12,7 @@ import java.util.Date;
* @author Axel Uhl (d043530)
*
*/
public interface IncrementalOrFullLeaderboardDTO extends Serializable {
public interface IncrementalOrFullLeaderboardDTO extends Serializable, CacheableRPCResult {
/**
* Produces the leaderboard DTO by providing the previous version relative to which a delta was requested.
* If <code>null</code> was used to identify the previous version, <code>null</code> may be passed for
@@ -35,7 +35,7 @@ public class JSONServiceImpl implements JSONService {
private RaceRecord createRaceRecord(URL jsonURL, boolean loadLiveAndStoredURI, JSONObject jsonRaceEntry)
throws URISyntaxException, IOException {
RaceRecord raceRecord = new RaceRecord(jsonURL, regattaName,
(String) jsonRaceEntry.get("name"), (String) jsonRaceEntry.get("url"),
(String) jsonRaceEntry.get("name"), (String) jsonRaceEntry.get("url_html"),
(String) jsonRaceEntry.get("params_url"),
(String) jsonRaceEntry.get("id"),
(String) jsonRaceEntry.get("tracking_starttime"),
@@ -6,7 +6,7 @@ import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.gwt.ui.client.ServerConfigurationService;
import com.sap.sailing.gwt.ui.server.Activator;
import com.sap.sailing.server.interfaces.RacingEventService;
import com.sap.sse.gwt.dispatch.servlets.ProxiedRemoteServiceServlet;
import com.sap.sse.gwt.server.ProxiedRemoteServiceServlet;
import com.sap.sse.util.ServiceTrackerFactory;
/**
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=La carrera empieza con ceñida
events=Eventos
pairingList=Lista de emparejamientos
pairingLists=Listas de emparejamientos
pairingListCreationInfo=Inicialmente se calculará una plantilla que contenga solo los números de los competidores y que pueda aplicarse a otras clasificaciones. Si el recuento de competidores no es divisible por recuento de embarcaciones sin remanente, se introducirán marcadores de posición en blanco en la lista de emparejamientos. \ Además, si el recuento de competidores difiere del recuento de competidores registrado en la clasificación real, la lista de emparejamientos no podrá aplicarse a los registros de la carrera. \ El recuento de flights, que se utiliza para generar una lista de emparejamientos, está definido por el número de flights de la clasificación que no son carreras de medalla. \ Cuando se utilizan repeticiones de flight, se pueden repetir asignaciones de flights (por ejemplo, 15 flights con 3 repeticiones de flight: 5 asignaciones de flight únicas que se repetirán 3 veces cada una de ellas). \ Un factor de cambio de embarcación equivalente a 0 da como resultado una asignación bien distribuida de competidores y embarcaciones, considerando que el máximo (número de flights) provoca menos cambios de embarcación, dado que los competidores navegan en la misma embarcación para dos flotas, uno detrás de otro, en diferentes flights.
pairingListCreationInfo=Inicialmente se calculará una plantilla que contenga solo los números de los competidores y que pueda aplicarse a otras clasificaciones. Si el recuento de competidores no es divisible por recuento de embarcaciones sin remanente, se introducirán marcadores de posición en blanco en la lista de emparejamientos. \ Además, si el recuento de competidores difiere del recuento de competidores registrado en la clasificación real, la lista de emparejamientos no podrá aplicarse a los registros de la carrera. \ El recuento de flights, que se utiliza para generar una lista de emparejamientos, está definido por el número de flights de la clasificación que no son carreras de medalla. \ Cuando se utilizan repeticiones de flight, se pueden repetir asignaciones de flights (por ejemplo, 15 flights con 3 repeticiones de flight: 5 asignaciones de flight únicas que se repetirán 3 veces cada una de ellas). \ Un factor de cambio de embarcación equivalente a 0 da como resultado una asignación bien distribuida de competidores y embarcaciones, considerando que el máximo (número de flights) provoca menos cambios de embarcación aumentando las posibilidades de que un competidor navegue con la misma embarcación en dos carreras contiguas a través de un límite de flights.
setCompetitors=Fije el recuento de competidores:
setBoatChangeFactor=Fije el factor de cambio de embarcación:
seriesHint=Seleccione una o más series que deberían estar en una lista de emparejamientos:
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=La course a commencé avec une portion de parcours au v
events=Manifestations
pairingList=Liste d''appariement
pairingLists=Listes d''appariement
pairingListCreationInfo=Tout d''abord, un modèle contenant uniquement le nombre de concurrents sera calculé. Il pourra être appliqué à d''autres palmarès. Si la division du nombre de concurrents par le nombre de bateaux ne permet pas d''obtenir un nombre entier, des espaces réservés vides seront ajoutés à la liste d''appariement. \ En outre, si le nombre de concurrents ne correspond pas au nombre de concurrents inscrits au palmarès actuel, la liste d''appariement ne pourra pas être appliquée aux journaux de course. \ Le nombre de flights (matchs) utilisé pour générer une liste d''appariement est défini par tous les flights du palmarès, hors courses médaillées. \ En cas de répétition des flights, les affectations de flights peuvent être répétées (par exemple, 15 flights avec 3 répétitions : 5 affectations de flights uniques, et chacun sera répété 3 fois). \ Un facteur de changement de bateau équivalent à 0 permet une répartition correcte des concurrents sur les bateaux, alors que le nombre de flights maximum implique moins de changements de bateaux car les concurrents naviguent sur un même bateau pour deux flottes l''une après l''autre sur des flights différents.
pairingListCreationInfo=Tout d''abord, un modèle contenant uniquement le nombre de concurrents sera calculé. Il pourra être appliqué à d''autres palmarès. Si la division du nombre de concurrents par le nombre de bateaux ne permet pas d''obtenir un nombre entier, des espaces réservés vides seront ajoutés à la liste d''appariement. \ En outre, si le nombre de concurrents ne correspond pas au nombre de concurrents inscrits au palmarès actuel, la liste d''appariement ne pourra pas être appliquée aux journaux de course. \ Le nombre de flights (matchs) utilisé pour générer une liste d''appariement est défini par tous les flights du palmarès, hors courses médaillées. \ En cas de répétition des flights, les affectations de flights peuvent être répétées (par exemple, 15 flights avec 3 répétitions : 5 affectations de flights uniques, et chacun sera répété 3 fois). \ Un facteur de changement de bateau équivalent à 0 permet une répartition correcte des concurrents sur les bateaux, alors que le nombre de flights maximum implique moins de changements de bateaux en augmentant les chances qu''un concurrent navigue sur un même bateau pour deux courses consécutives trans-flight.
setCompetitors=Veuillez définir le nombre de concurrents :
setBoatChangeFactor=Veuillez définir le facteur de changement de bateau :
seriesHint=Veuillez sélectionner une ou plusieurs séries à ajouter à une liste de paires :
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=アップウィンドレグでレーススタート
events=イベント
pairingList=対戦表
pairingLists=対戦表
pairingListCreationInfo=まず最初に、競技者番号のみが含まれていて、他のリーダーボードに適用できるテンプレートがすべて計算されます。競技者カウントが艇カウントで割り切れない場合、空のプレースホルダーが対戦表に入力されます。 \ また、競技者カウントが実際のリーダーボードにある登録済の競技者カウントと等しくない場合、対戦表をレースログに適用することはできません。 \ 対戦表を生成するために使用されるフライトのカウントは、リーダーボードにあるメダルレースでないフライトの回数によって設定されます。 \ フライト繰返の使用時に、フライト割当を繰り返すことができます (例: 3 つのフライト繰返がある 15 回のフライトでは、別々の 5 つのフライト割当でそれぞれが 3 回繰り返されます)。 \ 艇変更係数が 0 に等しければ、艇への競技者の割当がうまく配分されることにつながり、一方、最大 (フライト回数) になると、別のフライトだが互いに前後している 2 つのフリートで競技者が同一艇で帆走するため艇変更が少なくなることにつながります。
pairingListCreationInfo=まず最初に、競技者番号のみが含まれていて、他のリーダーボードに適用できるテンプレートがすべて計算されます。競技者カウントが艇カウントで割り切れない場合、空のプレースホルダーが対戦表に入力されます。 \ また、競技者カウントが実際のリーダーボードにある登録済の競技者カウントと等しくない場合、対戦表をレースログに適用することはできません。 \ 対戦表を生成するために使用されるフライトのカウントは、リーダーボードにあるメダルレースでないフライトの回数によって設定されます。 \ フライト繰返の使用時に、フライト割当を繰り返すことができます (例: 3 つのフライト繰返がある 15 回のフライトでは、別々の 5 つのフライト割当でそれぞれが 3 回繰り返されます)。 \ 艇変更係数が 0 に等しければ、艇への競技者の割当がうまく配分されることにつながり、一方、最大 (フライト回数) になると、フライトの境界を超えて隣接している 2 つのレースで競技者が同一艇で帆走する可能性が高くなることで艇変更がより少なくなります。
setCompetitors=競技者カウントを設定してください:
setBoatChangeFactor=艇変更係数を設定してください:
seriesHint=対戦表にあることが必要なシリーズを 1 つ以上選択してください:
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=A corrida começa com perna de contravento
events=Eventos
pairingList=Lista de pares
pairingLists=Listas de pares
pairingListCreationInfo=Primeiro será calculado um modelo que contém somente os números dos competidores e pode ser aplicado a outros painéis de classificação. Se contagem de competidores não for divisível pela contagem de barcos sem restante, os caracteres de preenchimento em branco serão inseridos na lista de pares. \ Além disso, se a contagem de competidores for diferente da contagem de competidores registrado no painel de classificação real, a lista de pares não pode ser aplicada aos registros das corridas. \ A contagem de voos, que é utilizado para gerar uma lista de pares, é definida pelo número de voos no painel de classificação que não sejam corridas para medalha. \ Ao utilizar repetições de voo, as atribuições de voo podem ser repetidas (por exemplo, 15 voos com 3 repetições de voo: 5 atribuições de voo únicas e cada uma será repetida 3 vezes). \ Um fator de mudança de barco igual a 0 leva a uma atribuição bem distribuída de competidores a barcos, sendo que o (número de voos) máximo leva a menos mudanças de barco devido a competidores que navegam no mesmo barco para duas flotilhas atrás uma da outra em diferentes voos.
pairingListCreationInfo=Primeiro será calculado um modelo que contém somente os números dos competidores e pode ser aplicado a outros painéis de classificação. Se contagem de competidores não for divisível pela contagem de barcos sem restante, os caracteres de preenchimento em branco serão inseridos na lista de pares. \ Além disso, se a contagem de competidores for diferente da contagem de competidores registrados no painel de classificação real, a lista de pares não pode ser aplicada aos registros das corridas. \ A contagem de voos, que é utilizada para gerar uma lista de pares, é definida pelo número de voos no painel de classificação que não sejam corridas para medalha. \ Ao utilizar repetições de voo, as atribuições de voo podem ser repetidas (por exemplo, 15 voos com 3 repetições de voo: 5 atribuições de voo únicas e cada uma será repetida 3 vezes). \ Um fator de mudança de barco igual a 0 leva a uma atribuição bem distribuída de competidores a barcos, sendo que o (número de voos) máximo leva a menos mudanças de barco aumentando as hipóteses de um competidor navegar no mesmo barco em duas corridas contínuas além do limite do voo.
setCompetitors=Defina a contagem dos competidores:
setBoatChangeFactor=Defina o fator de mudança de barco:
seriesHint=Selecione uma ou mais séries que devam estar em uma lista de pares:
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=Гонка начинается против ветра
events=События
pairingList=Список пар
pairingLists=Списки пар
pairingListCreationInfo=Первым будет рассчитан шаблон, который содержит только номера участников и может быть применен к другим таблицам лидеров. Если число участников не делится на число лодок без остатка, в списке пар будут пустые места. \ Если число участников не равно числу зарегистрированных участников в фактической таблице лидеров, список пар не может быть применен к журналам гонки. \ Для генерирования списка пар используется число всех флайтов таблицы лидеров, не являющихся гонками на медали. \ Если используются повторы флайтов, то присвоения флайтов могут повторяться (например, 15 флайтов с 3 повторами: 5 уникальных присвоений флайтов, каждый из которых повторяется 3 раза). \ Результатом нулевого коэффициента смены лодки является хорошее распределение присвоений участников лодкам, то есть максимум (число флайтов) ведет к меньшему числу смен лодок, поскольку участники плывут на одной и той же лодке двух флотов друг за другом в разных флайтах.
pairingListCreationInfo=Первым будет рассчитан шаблон, который содержит только номера участников и может быть применен к другим таблицам лидеров. Если число участников не делится на число лодок без остатка, в списке пар будут пустые места. \ Если число участников не равно числу зарегистрированных участников в фактической таблице лидеров, список пар не может быть применен к журналам гонки. \ Для генерирования списка пар используется число всех флайтов таблицы лидеров, не являющихся гонками на медали. \ Если используются повторы флайтов, то присвоения флайтов могут повторяться (например, 15 флайтов с 3 повторами: 5 уникальных присвоений флайтов, каждый из которых повторяется 3 раза). \ Результатом нулевого коэффициента смены лодки является хорошее распределение участников по лодкам, то есть максимум (число флайтов) ведет к меньшему числу смен лодок, и это увеличивает шансы, что участник будет плыть на одной и той же лодке в двух смежных гонках до окончания флайта.
setCompetitors=Укажите число участников:
setBoatChangeFactor=Укажите коэффициент смены лодки:
seriesHint=Выберите не менее одной серии для списка пар:
@@ -236,7 +236,7 @@ raceIsKnownToStartUpwind=比赛轮次从迎风航段开始
events=活动
pairingList=配对清单
pairingLists=配对清单
pairingListCreationInfo=首先,将计算仅包含参赛队数并且可以应用到其他积分榜的模板。如果参赛队数量无法被船只数量除尽,则在配对清单中输入空占位符。\ 此外,如果参赛队数量不等于实际积分榜中注册的参赛队数量,配对清单无法应用到比赛轮次日志。\ 用于生成配对清单的航程数由积分榜中不是奖牌轮的航程数定义。\ 使用航程重复时,可以重复航程分配(例如,有 3 个航程重复的 15 个航程:5 个独特的航程分配,每个重复 3 次)。\ 船只更换系数等于 0 导致向船只平均分配参赛队,而最大(航程数)导致船只更换次数减少,因为不同航程中背后两个船队的参赛队在同一船只上航行。
pairingListCreationInfo=首先,将计算仅包含参赛队数并且可以应用到其他积分榜的模板。如果参赛队数量无法被船只数量除尽,则在配对清单中输入空占位符。\ 此外,如果参赛队数量不等于实际积分榜中注册的参赛队数量,配对清单无法应用到比赛轮次日志。\ 用于生成配对清单的航程数由积分榜中不是奖牌轮的航程数定义。\ 使用航程重复时,可以重复航程分配(例如,有 3 个航程重复的 15 个航程:5 个独特的航程分配,每个重复 3 次)。\ 船只更换系数等于 0 导致向船只平均分配参赛队,而最大值(航程数)通过提高参赛队在两个连续的比赛轮次驾驶同一船只驶过航程边界的机会来减少换船次数。
setCompetitors=请设置参赛队数量:
setBoatChangeFactor=请设置船只更换系数:
seriesHint=请选择一场或多场应位于配对清单的系列赛:
@@ -1175,7 +1175,6 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
@Override
protected void ensureExpansionDataIsLoaded(final Runnable callWhenExpansionDataIsLoaded) {
if (getLegCount(getLeaderboard(), getRaceColumnName()) != -1) {
callWhenExpansionDataIsLoaded.run();
} else {
@@ -50,7 +50,7 @@ import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO;
import com.sap.sse.datamining.shared.impl.dto.ReducedDimensionsDTO;
import com.sap.sse.datamining.shared.impl.dto.StoredDataMiningQueryDTOImpl;
import com.sap.sse.datamining.ui.client.DataMiningService;
import com.sap.sse.gwt.dispatch.servlets.ProxiedRemoteServiceServlet;
import com.sap.sse.gwt.server.ProxiedRemoteServiceServlet;
import com.sap.sse.i18n.ResourceBundleStringMessages;
import com.sap.sse.security.SecurityService;
import com.sap.sse.util.ServiceTrackerFactory;
@@ -564,7 +564,7 @@ import com.sap.sse.gwt.client.ServerInfoDTO;
import com.sap.sse.gwt.client.media.ImageDTO;
import com.sap.sse.gwt.client.media.ImageResizingTaskDTO;
import com.sap.sse.gwt.client.media.VideoDTO;
import com.sap.sse.gwt.dispatch.servlets.ProxiedRemoteServiceServlet;
import com.sap.sse.gwt.server.ResultCachingProxiedRemoteServiceServlet;
import com.sap.sse.gwt.server.filestorage.FileStorageServiceDTOUtils;
import com.sap.sse.gwt.shared.filestorage.FileStorageServiceDTO;
import com.sap.sse.gwt.shared.filestorage.FileStorageServicePropertyErrorsDTO;
@@ -599,7 +599,7 @@ import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON;
/**
* The server side implementation of the RPC service.
*/
public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements SailingService, RaceFetcher, RegattaFetcher {
public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet implements SailingService, RaceFetcher, RegattaFetcher {
private static final Logger logger = Logger.getLogger(SailingServiceImpl.class.getName());
private static final String STRING_MESSAGES_BASE_NAME = "stringmessages/StringMessages";
@@ -23,10 +23,10 @@
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<booleanAttribute key="show_selected_only" value="false"/>
<stringAttribute key="target_bundles" value="routeconverter@default:default,org.apache.commons.codec@default:default,org.apache.poi@default:default,org.apache.poi.ooxml@default:default,org.apache.poi.ooxml.schemas@default:default,org.dom4j@default:default,org.apache.xmlbeans@default:default,org.apache.commons.math@default:default,org.apache.httpcomponents.httpclient@default:default,org.apache.httpcomponents.httpcore@default:default,org.objectweb.asm@default:default,org.hyperic.sigar@default:default,com.sun.jersey.contribs.jersey-multipart@default:default,javax.validation@default:default,org.apache.commons.fileupload@default:default,org.jdom@default:default,org.jvnet.mimepull@default:default,org.mongodb.mongo-java-driver@default:default,org.eclipse.jetty.osgi.boot@3:true,slf4j.jdk14@default:default,org.apache.felix.shell@0:true,org.apache.felix.shell.remote@0:true,lz4-java@default:default,javax.servlet@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.eclipse.jetty.deploy@4:true,org.eclipse.jetty.http@4:true,org.eclipse.jetty.io@4:true,org.eclipse.jetty.jmx@4:true,org.eclipse.jetty.security@4:true,org.eclipse.jetty.server@4:true,org.eclipse.jetty.servlet@4:true,org.eclipse.jetty.util@4:true,org.eclipse.jetty.webapp@4:true,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.xml@4:true,slf4j.api@default:default,org.apache.servicemix.bundles.zxing@default:default,com.fasterxml.jackson.core.jackson-annotations@default:default,com.fasterxml.jackson.core.jackson-core@default:default,com.fasterxml.jackson.core.jackson-databind@default:default,org.apache.commons.io@default:default,jcl.over.slf4j@default:default,org.apache.servicemix.bundles.aws-java-sdk@default:default,com.sun.mail.javax.mail@default:default,com.rabbitmq.client@default:default,org.apache.commons.lang@default:default,jackson-jaxrs@default:default,com.sun.jersey@default:default,javax.ws.rs@default:default,org.apache.commons.beanutils@default:default,org.apache.servicemix.bundles.ehcache@default:default,org.apache.servicemix.bundles.scribe@default:default,org.apache.shiro.core@default:default,org.apache.shiro.ehcache@default:default,org.apache.shiro.web@default:default,jackson-core-asl@default:default,jackson-mapper-asl@default:default,org.apache.commons.collections@default:default,org.eclipse.jetty.client@default:default,javax.xml@default:default,com.sun.activation.javax.activation@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.equinox.launcher@default:default,org.eclipse.equinox.simpleconfigurator@2:true,org.eclipse.osgi@default:default,org.eclipse.osgi.services@default:default,org.eclipse.equinox.cm@default:default,com.sun.istack.commons-runtime@default:default,jaxb-api@default:default,com.sun.xml.bind.jaxb-impl@default:default,javax.xml.stream@default:default,javax.xml.ws@default:default,org.eclipse.osgi.util@default:default"/>
<stringAttribute key="target_bundles" value="com.fasterxml.jackson.core.jackson-annotations@default:default,com.fasterxml.jackson.core.jackson-core@default:default,com.fasterxml.jackson.core.jackson-databind@default:default,com.rabbitmq.client@default:default,com.sun.activation.javax.activation@default:default,com.sun.istack.commons-runtime@default:default,com.sun.jersey.contribs.jersey-multipart@default:default,com.sun.jersey@default:default,com.sun.mail.javax.mail@default:default,com.sun.xml.bind.jaxb-impl@default:default,jackson-core-asl@default:default,jackson-jaxrs@default:default,jackson-mapper-asl@default:default,javax.servlet@default:default,javax.validation@default:default,javax.ws.rs@default:default,javax.xml.stream@default:default,javax.xml.ws@default:default,javax.xml@default:default,jaxb-api@default:default,jcl.over.slf4j@default:default,lz4-java@default:default,org.apache.commons.beanutils@default:default,org.apache.commons.codec@default:default,org.apache.commons.collections@default:default,org.apache.commons.fileupload@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.math@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.apache.felix.shell.remote@0:true,org.apache.felix.shell@0:true,org.apache.httpcomponents.httpclient@default:default,org.apache.httpcomponents.httpcore@default:default,org.apache.poi.ooxml.schemas@default:default,org.apache.poi.ooxml@default:default,org.apache.poi@default:default,org.apache.servicemix.bundles.aws-java-sdk@default:default,org.apache.servicemix.bundles.ehcache@default:default,org.apache.servicemix.bundles.scribe@default:default,org.apache.servicemix.bundles.zxing@default:default,org.apache.shiro.core@default:default,org.apache.shiro.ehcache@default:default,org.apache.shiro.web@default:default,org.apache.xmlbeans@default:default,org.dom4j@default:default,org.eclipse.equinox.cm@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.equinox.launcher@default:default,org.eclipse.equinox.simpleconfigurator@2:true,org.eclipse.jetty.client@default:default,org.eclipse.jetty.deploy@4:true,org.eclipse.jetty.http@4:true,org.eclipse.jetty.io@4:true,org.eclipse.jetty.jmx@4:true,org.eclipse.jetty.osgi.boot@3:true,org.eclipse.jetty.security@4:true,org.eclipse.jetty.server@4:true,org.eclipse.jetty.servlet@4:true,org.eclipse.jetty.util@4:true,org.eclipse.jetty.webapp@4:true,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.xml@4:true,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hyperic.sigar@default:default,org.jdom@default:default,org.jvnet.mimepull@default:default,org.mongodb.mongo-java-driver@default:default,org.objectweb.asm@default:default,routeconverter@default:default,slf4j.api@default:default,slf4j.jdk14@default:false"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfigArea" value="false"/>
<stringAttribute key="workspace_bundles" value="com.sap.sailing.geocoding@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain@default:default,com.sap.sailing.news@4:true,com.sap.sailing.domain.tractracadapter@5:true,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.domain.windfinderadapter@4:true,com.sap.sailing.server@5:true,com.sap.sailing.server.gateway@5:true,com.tractrac.clientmodule@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@5:true,com.sap.sailing.domain.swisstimingadapter.persistence@4:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@4:true,com.sap.sailing.gwt.ui@6:true,com.sap.sailing.udpconnector@default:default,com.sap.sailing.xmlexport@5:true,com.sap.sailing.simulator@default:default,com.sap.sailing.www@5:true,com.sap.sailing.resultimport@4:true,com.sap.sailing.kiworesultimport@4:true,com.sap.sailing.ess40.resultimport@4:true,com.sap.sailing.freg.resultimport@4:true,com.sap.sailing.barbados.resultimport@4:true,com.sap.sailing.sailwave.resultimport@4:true,com.sap.sailing.manage2sail.resultimport@4:true,com.sap.sailing.yachtscoring.resultimport@4:true,com.sap.sailing.velum.resultimport@4:true,com.sap.sailing.monitoring@7:true,com.sap.sailing.xrr.resultimport@4:true,com.sap.sailing.domain.igtimiadapter@4:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@4:true,com.sap.sailing.domain.deckmanadapter@5:true,com.sap.sailing.xrr.structureimport@default:default,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.dashboards.gwt@6:true,com.sap.sailing.dashboards.gwt@6:true,com.sap.sailing.datamining@5:true,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.polars@5:true,com.sap.sailing.windestimation@5:true,com.sap.sailing.polars.datamining@5:true,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.manage2sail@default:default,com.sap.sailing.polars.datamining.shared@default:default,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.competitorimport@default:default,com.sap.sailing.datamining.provider@default:default,com.sap.sailing.grib@default:default,com.sap.sailing.nmeaconnector@default:default,com.sap.sailing.domain.expeditionadapter@5:true,com.sap.sailing.expeditionconnector.persistence@4:true,com.sap.sailing.expeditionconnector.common@default:default,com.sap.sailing.domain.bravoadapter@5:true,net.sf.marineapi@default:default,com.sap.sailing.routeconverterjava11extension@default:default,com.sap.sailing.server.interface@default:default,com.sap.sse.datamining.ui@default:default,com.tractrac.clientmodule@default:default,com.sap.sse.gwt@default:default,com.google.gwt.servlet@default:default,com.sap.sse.security@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse@default:default,com.sap.sse.common@default:default,com.sap.sse.datamining@default:default,com.sap.sse.datamining.annotations@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication@6:true,com.sap.sse.filestorage@4:true,com.sap.sse.shared.android@default:default,com.sap.sse.mail@5:true,com.sap.sse.threadmanager@default:default,com.sap.sse.security.common@default:default,org.json.simple@default:default,com.sap.sse.jersey.jaxbdependencyfragment@default:default,org.moxieapps.gwt.highcharts@default:default,com.googlecode.java-diff-utils@default:default,org.mp4parser.isoparser@default:default,com.sap.sse.replication.interfaces@default:default,com.sap.sse.security.persistence@default:default,com.sap.sse.jettyextensions@default:default"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.servlet@default:default,com.googlecode.java-diff-utils@default:default,com.sap.sailing.barbados.resultimport@4:true,com.sap.sailing.competitorimport@default:default,com.sap.sailing.dashboards.gwt@6:true,com.sap.sailing.datamining.provider@default:default,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.datamining@5:true,com.sap.sailing.declination@default:default,com.sap.sailing.domain.bravoadapter@5:true,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.deckmanadapter@5:true,com.sap.sailing.domain.expeditionadapter@5:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.igtimiadapter@4:true,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@4:true,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@4:true,com.sap.sailing.domain.swisstimingadapter@5:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@4:true,com.sap.sailing.domain.tractracadapter@5:true,com.sap.sailing.domain.windfinderadapter@4:true,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@4:true,com.sap.sailing.expeditionconnector.common@default:default,com.sap.sailing.expeditionconnector.persistence@4:true,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@4:true,com.sap.sailing.geocoding@default:default,com.sap.sailing.grib@default:default,com.sap.sailing.gwt.ui@6:true,com.sap.sailing.kiworesultimport@4:true,com.sap.sailing.manage2sail.resultimport@4:true,com.sap.sailing.manage2sail@default:default,com.sap.sailing.monitoring@7:true,com.sap.sailing.news@4:true,com.sap.sailing.nmeaconnector@default:default,com.sap.sailing.polars.datamining.shared@default:default,com.sap.sailing.polars.datamining@5:true,com.sap.sailing.polars@5:true,com.sap.sailing.windestimation@5:true,com.sap.sailing.resultimport@4:true,com.sap.sailing.routeconverterjava11extension@default:false,com.sap.sailing.sailwave.resultimport@4:true,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@5:true,com.sap.sailing.server.interface@default:default,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.server@5:true,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@4:true,com.sap.sailing.www@5:true,com.sap.sailing.xmlexport@5:true,com.sap.sailing.xrr.resultimport@4:true,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.xrr.structureimport@default:default,com.sap.sailing.yachtscoring.resultimport@4:true,com.sap.sse.common@default:default,com.sap.sse.datamining.annotations@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.datamining.ui@default:default,com.sap.sse.datamining@default:default,com.sap.sse.filestorage@4:true,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.gwt@default:default,com.sap.sse.jersey.jaxbdependencyfragment@default:false,com.sap.sse.jettyextensions@default:false,com.sap.sse.mail@5:true,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication.interfaces@default:default,com.sap.sse.replication@6:true,com.sap.sse.security.common@default:default,com.sap.sse.security.persistence@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse.security@default:default,com.sap.sse.shared.android@default:default,com.sap.sse.threadmanager@default:default,com.sap.sse@default:default,com.tractrac.clientmodule@default:default,net.sf.marineapi@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default,org.mp4parser.isoparser@default:default"/>
<stringAttribute key="yk-options" value="&#13;&#10;additional-options2=onexit\=snapshot&#13;&#10;"/>
</launchConfiguration>
@@ -21,6 +21,18 @@
<div class="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">March 2019</h2>
<ul class="bulletList">
<li>By caching serialized results of leaderboard requests the server now requires
significantly less CPU power during live situations. Instead of computing the
serialized representation of a leaderboard again and again, the serialized version
is maintained in the cache as long as the original object from which the serialized
version was produced is referenced. This way, the cache cleans itself. It can be
observed through JMX, e.g., using JConsole, by looking at the JMX bean
<tt>com.sap.sse:type=GWTRPCSerializedResultCache_*</tt>.
</li>
</ul>
<h2 class="articleSubheadline">February 2019</h2>
<ul class="bulletList">
<li>The pairing list construction supports a new parameter that can be used
@@ -0,0 +1,9 @@
package com.sap.sse.common;
/**
* Marker interface for GWT RPC result objects whose serialized representation may be cached. It will
* be judged by object identity, regardless of whether the class implementing this interface offers
* a dedicated {@link #equals(Object)} and {@link #hashCode()} implementation.
*/
public interface CacheableRPCResult {
}
@@ -0,0 +1,72 @@
package com.sap.sse.gwt.server;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import com.sap.sse.common.CacheableRPCResult;
import com.sap.sse.gwt.server.CacheKey;
/**
* The {@link CacheKey} class uses weak references that may be cleared. This test is to ensure that handling
* {@link CacheKey} objects in hashed structures such as {@link HashMap}s works properly across clearing the reference
* held by the key.
*
* @author Axel Uhl (D043530)
*
*/
public class TestCacheKeyInMap {
private HashMap<CacheKey, String> map;
private ReferenceQueue<CacheableRPCResult> queue;
@Before
public void setUp() {
map = new HashMap<>();
queue = new ReferenceQueue<>();
}
@Test
public void testNonClearedReference() {
final CacheableRPCResult cachedObject = new CacheableRPCResult() {};
final CacheKey key = new CacheKey(/* serialization policy */ null, cachedObject, queue);
final String value = "abc";
map.put(key, value);
assertSame(value, map.get(new CacheKey(/* serialization policy */ null, cachedObject, /* queue */ null)));
}
@Test
public void testClearingReferenceAfterCaching() {
final CacheableRPCResult cachedObject = new CacheableRPCResult() {};
final CacheKey key = new CacheKey(/* serialization policy */ null, cachedObject, queue);
final String value = "abc";
map.put(key, value);
key.clear();
assertSame(value, map.get(key));
}
@Test
public void testClearingReferenceBeforeCaching() {
final CacheableRPCResult cachedObject = new CacheableRPCResult() {};
final CacheKey key = new CacheKey(/* serialization policy */ null, cachedObject, queue);
key.clear();
final String value = "abc";
map.put(key, value);
assertSame(value, map.get(key));
}
@Test
public void testRemoveAfterCaching() {
final CacheableRPCResult cachedObject = new CacheableRPCResult() {};
final CacheKey key = new CacheKey(/* serialization policy */ null, cachedObject, queue);
final String value = "abc";
map.put(key, value);
key.clear();
assertSame(value, map.remove(key));
assertTrue(map.isEmpty());
}
}
@@ -11,6 +11,7 @@ import com.sap.sse.gwt.dispatch.shared.commands.Action;
import com.sap.sse.gwt.dispatch.shared.commands.Result;
import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException;
import com.sap.sse.gwt.dispatch.shared.exceptions.ServerDispatchException;
import com.sap.sse.gwt.server.ResultCachingProxiedRemoteServiceServlet;
/**
* Abstract server-side part of the dispatch functionality. Needs to be subclassed in modules that want to use dispatch
@@ -18,7 +19,7 @@ import com.sap.sse.gwt.dispatch.shared.exceptions.ServerDispatchException;
*
* @param <CTX> the concrete {@link DispatchContext} used by dispatch.
*/
public abstract class AbstractDispatchServlet<CTX extends DispatchContext> extends ProxiedRemoteServiceServlet
public abstract class AbstractDispatchServlet<CTX extends DispatchContext> extends ResultCachingProxiedRemoteServiceServlet
implements DispatchRPC<CTX> {
private static final long serialVersionUID = -4279307111608131857L;
@@ -0,0 +1,70 @@
package com.sap.sse.gwt.server;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.sap.sse.common.CacheableRPCResult;
/**
* A weak reference to a {@link CacheableRPCResult} object, augmented by a reference to the
* {@link SerializationPolicy} that is to be used to serialize the result as a response of
* a GWT RPC call.<p>
*
* The object computes its hash code at construction and remembers it. This way, even if the
* weak reference is {@link #clear() cleared}, the hash code remains constant. Equality is
* decided based on the equality of the {@link SerializationPolicy} and the <em>identity</em> of
* the {@link CacheableRPCResult} object.<p>
*
* With this approach, an object of this type will always be {@link #equals(Object) equal} to itself,
* and its hash code will remain constant, thus guaranteeing that removing it from a hashed data
* structure such as a {@link HashMap} will always work, even if the weak reference was cleared
* since insertion.<p>
*
* Note: the case of a cleared weak reference that is compared to a new {@link CacheKey} constructed
* with the <em>same</em> original {@link CacheableRPCResult} does not need to be considered because
* clearing the weak reference implies the unreachability of the original {@link CacheableRPCResult}.
*/
public class CacheKey extends WeakReference<CacheableRPCResult> {
private final SerializationPolicy serializationPolicy;
private final int hashCode;
public CacheKey(SerializationPolicy serializationPolicy, CacheableRPCResult cacheableResult, ReferenceQueue<CacheableRPCResult> dereferencedObjectsQueue) {
super(cacheableResult, dereferencedObjectsQueue);
this.serializationPolicy = serializationPolicy;
this.hashCode = internalHashCode();
}
private int internalHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((get() == null) ? 0 : get().hashCode());
result = prime * result + ((serializationPolicy == null) ? 0 : serializationPolicy.hashCode());
return result;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CacheKey other = (CacheKey) obj;
if (get() != other.get()) // compare identity, not equality
return false;
if (serializationPolicy == null) {
if (other.serializationPolicy != null)
return false;
} else if (!serializationPolicy.equals(other.serializationPolicy))
return false;
return true;
}
}
@@ -0,0 +1,162 @@
package com.sap.sse.gwt.server;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.sap.sse.common.TimePoint;
public abstract class DelegatingProxiedRemoteServiceServlet extends ProxiedRemoteServiceServlet {
private static final long serialVersionUID = -5543378343472849437L;
/**
* Overwritten version of {@link RemoteServiceServlet#processCall(RPCRequest)} that calls
* {@link #invokeAndEncodeResponse(Object, Method, Object[], SerializationPolicy, int)} instead of
* {@link RPC#invokeAndEncodeResponse(Object, Method, Object[], SerializationPolicy, int)}.
* {@link #invokeAndEncodeResponse(Object, Method, Object[], SerializationPolicy, int)} in turn calls
* {@link #encodeResponseForSuccess(Method, SerializationPolicy, int, Object)} which subclasses can override
* in order to customize result serialization, e.g., by picking a cached serialized version.
*/
@Override
public String processCall(RPCRequest rpcRequest) throws SerializationException {
final TimePoint startOfProcessCall = beforeProcessCall();
try {
final Object delegate = this;
try {
onAfterRequestDeserialized(rpcRequest);
return invokeAndEncodeResponse(delegate, rpcRequest.getMethod(), rpcRequest.getParameters(),
rpcRequest.getSerializationPolicy(), rpcRequest.getFlags());
} catch (IncompatibleRemoteServiceException ex) {
log("An IncompatibleRemoteServiceException was thrown while processing this call.", ex);
return RPC.encodeResponseForFailedRequest(rpcRequest, ex);
} catch (RpcTokenException tokenException) {
log("An RpcTokenException was thrown while processing this call.", tokenException);
return RPC.encodeResponseForFailedRequest(rpcRequest, tokenException);
}
} finally {
afterProcessCall(rpcRequest, startOfProcessCall);
}
}
/**
* Similar to {@link RPC#invokeAndEncodeResponse(Object, Method, Object[], SerializationPolicy, int)}, but
* calls {@link #encodeResponseForSuccess(Method, SerializationPolicy, int, Object)}, giving subclasses
* a way to override result serialization.
*/
private String invokeAndEncodeResponse(Object target, Method serviceMethod, Object[] args,
SerializationPolicy serializationPolicy, int flags) throws SerializationException {
if (serviceMethod == null) {
throw new NullPointerException("serviceMethod");
}
if (serializationPolicy == null) {
throw new NullPointerException("serializationPolicy");
}
String responsePayload;
try {
Object result = serviceMethod.invoke(target, args);
responsePayload = encodeResponseForSuccess(serviceMethod, serializationPolicy, flags, result);
} catch (IllegalAccessException e) {
SecurityException securityException = new SecurityException(formatIllegalAccessErrorMessage(target, serviceMethod));
securityException.initCause(e);
throw securityException;
} catch (IllegalArgumentException e) {
SecurityException securityException = new SecurityException(formatIllegalArgumentErrorMessage(target, serviceMethod, args));
securityException.initCause(e);
throw securityException;
} catch (InvocationTargetException e) {
// Try to encode the caught exception
Throwable cause = e.getCause();
responsePayload = RPC.encodeResponseForFailure(serviceMethod, cause, serializationPolicy, flags);
}
return responsePayload;
}
/**
* To be overwritten by subclasses to customize the serialization of the result object.
*/
protected String encodeResponseForSuccess(Method serviceMethod, SerializationPolicy serializationPolicy, int flags,
Object result) throws SerializationException {
final String responsePayload;
responsePayload = RPC.encodeResponseForSuccess(serviceMethod, result, serializationPolicy, flags);
return responsePayload;
}
/**
* Taken from {@link RPC}.
*/
private String formatIllegalAccessErrorMessage(Object target, Method serviceMethod) {
StringBuffer sb = new StringBuffer();
sb.append("Blocked attempt to access inaccessible method '");
sb.append(getSourceRepresentation(serviceMethod));
sb.append("'");
if (target != null) {
sb.append(" on target '");
sb.append(printTypeName(target.getClass()));
sb.append("'");
}
sb.append("; this is either misconfiguration or a hack attempt");
return sb.toString();
}
/**
* Taken from {@link RPC}.
*/
private String formatIllegalArgumentErrorMessage(Object target, Method serviceMethod, Object[] args) {
StringBuffer sb = new StringBuffer();
sb.append("Blocked attempt to invoke method '");
sb.append(getSourceRepresentation(serviceMethod));
sb.append("'");
if (target != null) {
sb.append(" on target '");
sb.append(printTypeName(target.getClass()));
sb.append("'");
}
sb.append(" with invalid arguments");
if (args != null && args.length > 0) {
sb.append(Arrays.asList(args));
}
return sb.toString();
}
private String getSourceRepresentation(Method method) {
return method.toString().replace('$', '.');
}
/**
* Taken from {@link RPC}.
*/
private String printTypeName(Class<?> type) {
// Primitives
if (type.equals(Integer.TYPE)) {
return "int";
} else if (type.equals(Long.TYPE)) {
return "long";
} else if (type.equals(Short.TYPE)) {
return "short";
} else if (type.equals(Byte.TYPE)) {
return "byte";
} else if (type.equals(Character.TYPE)) {
return "char";
} else if (type.equals(Boolean.TYPE)) {
return "boolean";
} else if (type.equals(Float.TYPE)) {
return "float";
} else if (type.equals(Double.TYPE)) {
return "double";
}
// Arrays
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
return printTypeName(componentType) + "[]";
}
// Everything else
return type.getName().replace('$', '.');
}
}
@@ -1,4 +1,4 @@
package com.sap.sse.gwt.dispatch.servlets;
package com.sap.sse.gwt.server;
import java.io.IOException;
import java.util.Arrays;
@@ -115,35 +115,53 @@ public abstract class ProxiedRemoteServiceServlet extends RemoteServiceServlet {
return Locale.ENGLISH;
}
/**
* Subclasses overriding this method must either delegate to this method or, if they don't, call
* {@link #beforeProcessCall()} at the beginning and {@link #afterProcessCall(RPCRequest, TimePoint)} at the end of
* the method, ideally in a {@code finally} clause, passing the result of the {@link #beforeProcessCall()} call as
* the {@code startOfRequestProcessing} parameter.
*/
@Override
public String processCall(RPCRequest rpcRequest) throws SerializationException {
final TimePoint startOfRequestProcessing = beforeProcessCall();
try {
final String result = super.processCall(rpcRequest);
return result;
} finally {
afterProcessCall(rpcRequest, startOfRequestProcessing);
}
}
protected TimePoint beforeProcessCall() {
final TimePoint startOfRequestProcessing = MillisecondsTimePoint.now();
final String result = super.processCall(rpcRequest);
return startOfRequestProcessing;
}
protected void afterProcessCall(RPCRequest rpcRequest, final TimePoint startOfRequestProcessing) {
processingStartAndFinishTime.set(new Triple<>(rpcRequest, startOfRequestProcessing, MillisecondsTimePoint.now()));
return result;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
final TimePoint afterResultSerialization = MillisecondsTimePoint.now();
final TimePoint afterSendingResultToResponse = MillisecondsTimePoint.now();
final Triple<RPCRequest, TimePoint, TimePoint> startAndEndOfProcessing = processingStartAndFinishTime.get();
if (startAndEndOfProcessing == null) {
logger.warning("A non-POST request with method "+req.getMethod()+" from address "+req.getRemoteAddr()
+" was processed. No timing information available.");
} else {
final Duration totalTime = startAndEndOfProcessing.getB().until(afterResultSerialization);
final Duration totalTime = startAndEndOfProcessing.getB().until(afterSendingResultToResponse);
if (totalTime.compareTo(LOG_REQUESTS_TAKING_LONGER_THAN) > 0) {
logRequest(startAndEndOfProcessing.getA(), startAndEndOfProcessing.getB(), startAndEndOfProcessing.getC(), afterResultSerialization);
logRequest(startAndEndOfProcessing.getA(), startAndEndOfProcessing.getB(), startAndEndOfProcessing.getC(), afterSendingResultToResponse);
}
processingStartAndFinishTime.set(null);
}
}
private void logRequest(RPCRequest request, TimePoint startOfProcessing, TimePoint endOfProcessing, TimePoint afterResultSerialization) {
private void logRequest(RPCRequest request, TimePoint startOfProcessing, TimePoint endOfProcessing, TimePoint afterSendingResultToResponse) {
final Duration processingDuration = startOfProcessing.until(endOfProcessing);
final Duration serializationDuration = endOfProcessing.until(afterResultSerialization);
final Duration totalDuration = startOfProcessing.until(afterResultSerialization);
final Duration sendingToResponseDuration = endOfProcessing.until(afterSendingResultToResponse);
final Duration totalDuration = startOfProcessing.until(afterSendingResultToResponse);
final String username;
final Subject subject = SecurityUtils.getSubject();
if (subject != null) {
@@ -154,7 +172,7 @@ public abstract class ProxiedRemoteServiceServlet extends RemoteServiceServlet {
logger.log(Level.WARNING, "GWT RPC Request "+request.getMethod()+
" by user "+username+
" with parameters "+Arrays.toString(request.getParameters())+" on "+this+
" took "+processingDuration+" to process, "+serializationDuration+" to serialize the response, so "+
" took "+processingDuration+" to process, "+sendingToResponseDuration+" to send result into response, so "+
totalDuration+" in total.");
}
}
@@ -0,0 +1,8 @@
package com.sap.sse.gwt.server;
public interface RPCSerializedResultCacheMXBean {
long getRecalcCount();
long getCallCount();
long getNumberOfCachedResults();
long getTotalCacheSize();
}
@@ -0,0 +1,126 @@
package com.sap.sse.gwt.server;
import java.lang.management.ManagementFactory;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.sap.sse.common.CacheableRPCResult;
/**
* {@link ProxiedRemoteServiceServlet} that automatically caches serialized RPC results for result objects that
* implement {@link CacheableRPCResult}.
*/
public class ResultCachingProxiedRemoteServiceServlet extends DelegatingProxiedRemoteServiceServlet {
private static final Logger logger = Logger.getLogger(ResultCachingProxiedRemoteServiceServlet.class.getName());
private static final long serialVersionUID = -4245484615349695611L;
private Map<CacheKey, String> resultCache = new ConcurrentHashMap<>();
/**
* Holds the weak references to {@link CacheableRPCResult} objects that have been garbage-collected
* and hence dereferenced. This will set the {@link WeakReference}'s {@link WeakReference#get() referent}
* to {@code null} but leave the reference in the cache. Upon cache access we will purge cache
* entries whose {@link CacheableRPCResult} has appeared in this queue.
*/
final ReferenceQueue<CacheableRPCResult> dereferencedObjectsQueue;
private final AtomicLong callCount;
private final AtomicLong recalcCount;
public class RPCSerializedResultCacheMXBeanImpl implements RPCSerializedResultCacheMXBean {
@Override
public long getCallCount() {
return callCount.get();
}
@Override
public long getRecalcCount() {
return recalcCount.get();
}
@Override
public long getNumberOfCachedResults() {
return resultCache.size();
}
@Override
public long getTotalCacheSize() {
long result = 0;
for (final String cachedString : resultCache.values()) {
result += cachedString.length();
}
return result;
}
}
public ResultCachingProxiedRemoteServiceServlet() {
callCount = new AtomicLong();
recalcCount = new AtomicLong();
dereferencedObjectsQueue = new ReferenceQueue<>();
// Add an MBean for the service to the JMX bean server:
final RPCSerializedResultCacheMXBean mbean = new RPCSerializedResultCacheMXBeanImpl();
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
final ObjectName mBeanName = new ObjectName("com.sap.sse:type=GWTRPCSerializedResultCache_"+
getClass().getSimpleName()+"_"+(toString().substring(toString().indexOf('@')+1)));
mbs.registerMBean(mbean, mBeanName);
} catch (Exception e) {
logger.log(Level.WARNING, "Couldn't register MXBean for result-caching proxy "+this, e);
}
}
@Override
protected String encodeResponseForSuccess(Method serviceMethod, SerializationPolicy serializationPolicy, int flags,
Object result) throws SerializationException {
purgeCache();
if (result instanceof CacheableRPCResult) {
final CacheableRPCResult cacheableResult = (CacheableRPCResult) result;
try {
callCount.incrementAndGet();
return resultCache.computeIfAbsent(new CacheKey(serializationPolicy, cacheableResult, dereferencedObjectsQueue),
key -> {
recalcCount.incrementAndGet();
try {
return super.encodeResponseForSuccess(serviceMethod, serializationPolicy, flags, result);
} catch (SerializationException serializationException) {
throw new TemporaryWrapperException(serializationException);
}
});
} catch (TemporaryWrapperException e) {
throw e.serializationException;
}
}
return super.encodeResponseForSuccess(serviceMethod, serializationPolicy, flags, result);
}
/**
* For all references enqueued in {@link #dereferencedObjectsQueue}, removes their key from the {@link #resultCache}.
*/
private void purgeCache() {
Reference<? extends CacheableRPCResult> clearedReference;
while ((clearedReference = dereferencedObjectsQueue.poll()) != null) {
resultCache.remove(clearedReference);
}
}
private class TemporaryWrapperException extends RuntimeException {
private static final long serialVersionUID = 4720608117776979002L;
private final SerializationException serializationException;
public TemporaryWrapperException(SerializationException serializationException) {
this.serializationException = serializationException;
}
}
}
-2
View File
@@ -169,9 +169,7 @@
<module>com.sap.sailing.domain.test</module>
<module>com.sap.sse.replication.testsupport</module>
<module>com.sap.sailing.server.testsupport</module>
<!-- The following test fragment doesn't yet do what it should in the Maven context... -->
<module>com.sap.sse.gwt.test</module>
<!-- -->
<module>com.sap.sse.datamining.test</module>
<module>com.sap.sse.security.replication.test</module>
<module>com.sap.sse.security.test</module>
@@ -35,7 +35,7 @@ android {
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
versionCode appVersionCode.toInteger()
versionName "$appVersionCode - Altitude"
versionName appVersionName
}
sourceSets {
@@ -35,7 +35,7 @@ android {
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
versionCode appVersionCode.toInteger()
versionName "$appVersionCode - Altitude"
versionName appVersionName
}
sourceSets {
@@ -35,7 +35,7 @@ android {
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
versionCode appVersionCode.toInteger()
versionName "$appVersionCode - Laser"
versionName appVersionName
}
sourceSets {