mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-04 18:20:14 +00:00
Merge branch 'master' into tracapi
This commit is contained in:
commit
d4c3b5309d
50 files changed
+1612
-159
No files matched your search
@@ -0,0 +1,16 @@
|
||||
DEPLOY_TO=server
|
||||
SERVER_NAME=LIVE
|
||||
MEMORY=4096m
|
||||
REPLICATION_HOST=172.31.25.253
|
||||
REPLICATION_CHANNEL=sapsailinganalytics-live
|
||||
TELNET_PORT=14888
|
||||
SERVER_PORT=8888
|
||||
MONGODB_HOST=172.31.25.253
|
||||
MONGODB_PORT=10201
|
||||
EXPEDITION_PORT=2010
|
||||
REPLICATE_ON_START=False
|
||||
REPLICATE_MASTER_SERVLET_HOST=
|
||||
REPLICATE_MASTER_SERVLET_PORT=
|
||||
REPLICATE_MASTER_QUEUE_HOST=
|
||||
REPLICATE_MASTER_QUEUE_PORT=
|
||||
ADDITIONAL_JAVA_ARGS="-XX:+UseMembar -XX:+UseParallelGC -XX:+UseParallelOldGC -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dorg.eclipse.jetty.LEVEL=OFF -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog"
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public enum ScoringSchemeType {
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE,
|
||||
HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
|
||||
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
public interface BoatChangeListener {
|
||||
void sailIdChanged(String oldSailId, String newSailId);
|
||||
}
|
||||
+13
@@ -10,4 +10,17 @@ public interface Competitor extends Named, WithID, IsManagedBySharedDomainFactor
|
||||
Boat getBoat();
|
||||
|
||||
Color getColor();
|
||||
|
||||
/**
|
||||
* Adds a listener to this competitor. The listener is also added to the boat and the team for changes.
|
||||
* Adding a listener that is already part of this competitor's listeners set remains without effect.
|
||||
*/
|
||||
void addCompetitorChangeListener(CompetitorChangeListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener from this competitor. The listener is also removed from the boat and the team.
|
||||
* Trying to remove a listener that is not currently in the set of this competitor's listener remains
|
||||
* without effect.
|
||||
*/
|
||||
void removeCompetitorChangeListener(CompetitorChangeListener listener);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
|
||||
public interface CompetitorChangeListener extends BoatChangeListener, NationalityChangeListener {
|
||||
void colorChanged(Color oldColor, Color newColor);
|
||||
|
||||
void nameChanged(String oldName, String newName);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface NationalityChangeListener extends Serializable {
|
||||
void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality);
|
||||
}
|
||||
+41
-1
@@ -1,17 +1,31 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatChangeListener;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
|
||||
public class BoatImpl extends NamedImpl implements DynamicBoat {
|
||||
private static final long serialVersionUID = 3489730487528955788L;
|
||||
private final BoatClass boatClass;
|
||||
private String sailID;
|
||||
private transient Set<BoatChangeListener> listeners;
|
||||
|
||||
public BoatImpl(String name, BoatClass boatClass, String sailID) {
|
||||
super(name);
|
||||
this.boatClass = boatClass;
|
||||
this.sailID = sailID;
|
||||
this.listeners = new HashSet<BoatChangeListener>();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
ois.defaultReadObject();
|
||||
listeners = new HashSet<BoatChangeListener>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,6 +40,32 @@ public class BoatImpl extends NamedImpl implements DynamicBoat {
|
||||
|
||||
@Override
|
||||
public void setSailId(String newSailId) {
|
||||
this.sailID = newSailId;
|
||||
final String oldSailId = this.sailID;
|
||||
if (!Util.equalsWithNull(oldSailId, newSailId)) {
|
||||
this.sailID = newSailId;
|
||||
for (BoatChangeListener listener : getListeners()) {
|
||||
listener.sailIdChanged(oldSailId, newSailId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBoatChangeListener(BoatChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorChangeListener(BoatChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<BoatChangeListener> getListeners() {
|
||||
synchronized (listeners) {
|
||||
return new HashSet<BoatChangeListener>(listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
-3
@@ -1,10 +1,16 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorChangeListener;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
|
||||
public class CompetitorImpl implements DynamicCompetitor {
|
||||
private static final long serialVersionUID = 294603681016643157L;
|
||||
@@ -12,8 +18,8 @@ public class CompetitorImpl implements DynamicCompetitor {
|
||||
private final DynamicBoat boat;
|
||||
private final Serializable id;
|
||||
private String name;
|
||||
|
||||
private Color color;
|
||||
private transient Set<CompetitorChangeListener> listeners;
|
||||
|
||||
public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) {
|
||||
this.id = id;
|
||||
@@ -21,6 +27,12 @@ public class CompetitorImpl implements DynamicCompetitor {
|
||||
this.team = team;
|
||||
this.boat = boat;
|
||||
this.color = color;
|
||||
this.listeners = new HashSet<CompetitorChangeListener>();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
ois.defaultReadObject();
|
||||
listeners = new HashSet<CompetitorChangeListener>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,7 +47,13 @@ public class CompetitorImpl implements DynamicCompetitor {
|
||||
|
||||
@Override
|
||||
public void setName(String newName) {
|
||||
this.name = newName;
|
||||
final String oldName = this.name;
|
||||
if (!Util.equalsWithNull(oldName, newName)) {
|
||||
this.name = newName;
|
||||
for (CompetitorChangeListener listener : getListeners()) {
|
||||
listener.nameChanged(oldName, newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,6 +82,36 @@ public class CompetitorImpl implements DynamicCompetitor {
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
final Color oldColor = this.color;
|
||||
if (!Util.equalsWithNull(oldColor, color)) {
|
||||
this.color = color;
|
||||
for (CompetitorChangeListener listener : getListeners()) {
|
||||
listener.colorChanged(oldColor, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompetitorChangeListener(CompetitorChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
getBoat().addBoatChangeListener(listener);
|
||||
getTeam().addNationalityChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorChangeListener(CompetitorChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
getBoat().removeCompetitorChangeListener(listener);
|
||||
getTeam().removeNationalityChangeListener(listener);
|
||||
}
|
||||
|
||||
private Iterable<CompetitorChangeListener> getListeners() {
|
||||
synchronized (listeners) {
|
||||
return new HashSet<CompetitorChangeListener>(listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -1,7 +1,13 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Boat;
|
||||
import com.sap.sailing.domain.base.BoatChangeListener;
|
||||
|
||||
public interface DynamicBoat extends Boat {
|
||||
void setSailId(String newSailId);
|
||||
|
||||
void addBoatChangeListener(BoatChangeListener listener);
|
||||
|
||||
void removeCompetitorChangeListener(BoatChangeListener listener);
|
||||
|
||||
}
|
||||
+5
@@ -1,8 +1,13 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.NationalityChangeListener;
|
||||
import com.sap.sailing.domain.base.Person;
|
||||
|
||||
public interface DynamicPerson extends Person {
|
||||
void setNationality(Nationality newNationality);
|
||||
|
||||
void addNationalityChangeListener(NationalityChangeListener listener);
|
||||
|
||||
void removeNationalityChangeListener(NationalityChangeListener listener);
|
||||
}
|
||||
+5
@@ -1,8 +1,13 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.NationalityChangeListener;
|
||||
import com.sap.sailing.domain.base.Team;
|
||||
|
||||
public interface DynamicTeam extends Team {
|
||||
void setNationality(Nationality newNationality);
|
||||
|
||||
void addNationalityChangeListener(NationalityChangeListener listener);
|
||||
|
||||
void removeNationalityChangeListener(NationalityChangeListener listener);
|
||||
}
|
||||
+39
-1
@@ -1,24 +1,37 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.NationalityChangeListener;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
|
||||
public class PersonImpl extends NamedImpl implements DynamicPerson {
|
||||
private static final long serialVersionUID = -2104903799224233508L;
|
||||
private Nationality nationality;
|
||||
private final Date dateOfBirth;
|
||||
private final String description;
|
||||
private transient Set<NationalityChangeListener> listeners;
|
||||
|
||||
public PersonImpl(String name, Nationality nationality, Date dateOfBirth, String description) {
|
||||
super(name);
|
||||
this.nationality = nationality;
|
||||
this.dateOfBirth = dateOfBirth;
|
||||
this.description = description;
|
||||
this.listeners = new HashSet<NationalityChangeListener>();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
ois.defaultReadObject();
|
||||
listeners = new HashSet<NationalityChangeListener>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getImage() {
|
||||
throw new UnsupportedOperationException("Image for person not yet implemented");
|
||||
@@ -31,7 +44,13 @@ public class PersonImpl extends NamedImpl implements DynamicPerson {
|
||||
|
||||
@Override
|
||||
public void setNationality(Nationality newNationality) {
|
||||
this.nationality = newNationality;
|
||||
final Nationality oldNationality = this.nationality;
|
||||
if (!Util.equalsWithNull(oldNationality, newNationality)) {
|
||||
this.nationality = newNationality;
|
||||
for (NationalityChangeListener listener : getListeners()) {
|
||||
listener.nationalityChanged(this, oldNationality, newNationality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,4 +63,23 @@ public class PersonImpl extends NamedImpl implements DynamicPerson {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNationalityChangeListener(NationalityChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNationalityChangeListener(NationalityChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<NationalityChangeListener> getListeners() {
|
||||
synchronized (listeners) {
|
||||
return new HashSet<NationalityChangeListener>(listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
-8
@@ -1,18 +1,80 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.NationalityChangeListener;
|
||||
import com.sap.sailing.domain.base.Person;
|
||||
import com.sap.sailing.domain.base.WithNationality;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
|
||||
public class TeamImpl extends NamedImpl implements DynamicTeam {
|
||||
private static final long serialVersionUID = 4646922280429210183L;
|
||||
private final Iterable<? extends DynamicPerson> sailors;
|
||||
private final Person coach;
|
||||
private final DynamicPerson coach;
|
||||
private final NationalityChangeListener personNationalityChangeForwarder;
|
||||
private transient Set<NationalityChangeListener> listeners;
|
||||
|
||||
public TeamImpl(String name, Iterable<? extends DynamicPerson> sailors, Person coach) {
|
||||
public TeamImpl(String name, Iterable<? extends DynamicPerson> sailors, DynamicPerson coach) {
|
||||
super(name);
|
||||
listeners = new HashSet<NationalityChangeListener>();
|
||||
this.sailors = sailors;
|
||||
this.coach = coach;
|
||||
this.personNationalityChangeForwarder = new NationalityChangeListener() {
|
||||
private static final long serialVersionUID = -6024941913105410444L;
|
||||
@Override
|
||||
public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) {
|
||||
if (what == getNationalityDonor()) {
|
||||
for (NationalityChangeListener listener : getNationalityChangeListeners()) {
|
||||
listener.nationalityChanged(TeamImpl.this, oldNationality, newNationality);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
setTransitiveListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* The listeners on {@link DynamicPerson} are transient. Our {@link #personNationalityChangeForwarder} needs to
|
||||
* be restored after deserialization.
|
||||
*/
|
||||
private void setTransitiveListeners() {
|
||||
if (this.sailors != null) {
|
||||
for (DynamicPerson sailor : this.sailors) {
|
||||
sailor.addNationalityChangeListener(personNationalityChangeForwarder);
|
||||
}
|
||||
}
|
||||
if (this.coach != null) {
|
||||
this.coach.addNationalityChangeListener(personNationalityChangeForwarder);
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
ois.defaultReadObject();
|
||||
listeners = new HashSet<NationalityChangeListener>();
|
||||
setTransitiveListeners();
|
||||
}
|
||||
|
||||
public void addNationalityChangeListener(NationalityChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNationalityChangeListener(NationalityChangeListener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<NationalityChangeListener> getNationalityChangeListeners() {
|
||||
synchronized (listeners) {
|
||||
return new HashSet<NationalityChangeListener>(listeners);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -21,28 +83,38 @@ public class TeamImpl extends NamedImpl implements DynamicTeam {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Person getCoach() {
|
||||
public DynamicPerson getCoach() {
|
||||
return coach;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Nationality getNationality() {
|
||||
return getNationalityDonor().getNationality();
|
||||
}
|
||||
|
||||
private WithNationality getNationalityDonor() {
|
||||
for (Person sailor : getSailors()) {
|
||||
if (sailor.getNationality() != null) {
|
||||
return sailor.getNationality();
|
||||
return sailor;
|
||||
}
|
||||
}
|
||||
if (getCoach() != null) {
|
||||
return getCoach().getNationality();
|
||||
return getCoach();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNationality(Nationality newNationality) {
|
||||
for (Person sailor : getSailors()) {
|
||||
((DynamicPerson) sailor).setNationality(newNationality);
|
||||
Nationality oldNationality = getNationality();
|
||||
if (!Util.equalsWithNull(oldNationality, newNationality)) {
|
||||
for (Person sailor : getSailors()) {
|
||||
((DynamicPerson) sailor).setNationality(newNationality);
|
||||
}
|
||||
for (NationalityChangeListener listener : getNationalityChangeListeners()) {
|
||||
listener.nationalityChanged(this, oldNationality, newNationality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+8
-1
@@ -299,12 +299,19 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
|
||||
protected boolean add(FixType fix) {
|
||||
lockForWrite();
|
||||
try {
|
||||
return getInternalRawFixes().add(fix);
|
||||
return addWithoutLocking(fix);
|
||||
} finally {
|
||||
unlockAfterWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The caller must ensure to hold the write lock for this track when calling this methos
|
||||
*/
|
||||
protected boolean addWithoutLocking(FixType fix) {
|
||||
return getInternalRawFixes().add(fix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getAverageIntervalBetweenFixes() {
|
||||
lockForRead();
|
||||
|
||||
Executable
+243
@@ -0,0 +1,243 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
|
||||
/**
|
||||
* A map that is concurrency-safe as provided by the {@link ConcurrentHashMap} implementation, and referencing
|
||||
* keys only weakly. Like {@link ConcurrentHashMap}, this map does not support <code>null</code> keys.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*/
|
||||
public class ConcurrentWeakHashMap<K, V> implements Map<K, V> {
|
||||
private final ConcurrentHashMap<WeakReferenceThatIsEqualToItselfAndItsReferent, V> map;
|
||||
|
||||
private final ReferenceQueue<K> queue;
|
||||
|
||||
/**
|
||||
* A reference whose hash code is based on the hash code of the original referent, also after the
|
||||
* reference has been enqueued. Equality is defined such that this reference is equal to itself which is
|
||||
* helpful to remove the entries from the {@link ConcurrentHashMap}, as well as equal to any other reference
|
||||
* of this type if their referents are equal.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private class WeakReferenceThatIsEqualToItselfAndItsReferent extends WeakReference<K> {
|
||||
private final int hashCode;
|
||||
|
||||
public WeakReferenceThatIsEqualToItselfAndItsReferent(K referent, ReferenceQueue<? super K> q) {
|
||||
super(referent, q);
|
||||
hashCode = referent.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>other</code> is this reference, it it considered equal. Otherwise, <code>other</code> is considered
|
||||
* equal to this reference if the referent is still set and the referent equals <code>other</code>.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
final boolean result;
|
||||
if (other == this) {
|
||||
result = true;
|
||||
} else {
|
||||
@SuppressWarnings("unchecked") // don't care about the generics here; important is that we can get at the referent
|
||||
WeakReferenceThatIsEqualToItselfAndItsReferent otherAsRef = (WeakReferenceThatIsEqualToItselfAndItsReferent) other;
|
||||
K referent = get();
|
||||
K otherReferent = otherAsRef.get();
|
||||
result = Util.equalsWithNull(referent, otherReferent);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class KeySet extends AbstractSet<K> {
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return map.containsKey(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return map.remove(o) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
purgeStaleEntries();
|
||||
final Iterator<Map.Entry<WeakReferenceThatIsEqualToItselfAndItsReferent, V>> iterator = map.entrySet().iterator();
|
||||
return new Iterator<K>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next() {
|
||||
return iterator.next().getKey().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
purgeStaleEntries();
|
||||
final Iterator<Map.Entry<WeakReferenceThatIsEqualToItselfAndItsReferent, V>> iterator = map.entrySet().iterator();
|
||||
return new Iterator<Map.Entry<K, V>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, V> next() {
|
||||
final Map.Entry<WeakReferenceThatIsEqualToItselfAndItsReferent, V> e = iterator.next();
|
||||
return new Map.Entry<K, V>() {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return e.getKey().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return e.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
return e.setValue(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap() {
|
||||
map = new ConcurrentHashMap<WeakReferenceThatIsEqualToItselfAndItsReferent, V>();
|
||||
queue = new ReferenceQueue<K>();
|
||||
}
|
||||
|
||||
private void purgeStaleEntries() {
|
||||
Reference<? extends K> ref;
|
||||
while ((ref=queue.poll()) != null) {
|
||||
map.remove(ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
purgeStaleEntries();
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
purgeStaleEntries();
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so false will result which is ok
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
purgeStaleEntries();
|
||||
return map.containsKey(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so null will result which is ok
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
purgeStaleEntries();
|
||||
return map.get(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
return map.put(new WeakReferenceThatIsEqualToItselfAndItsReferent(key, queue), value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // if the cast doesn't work then neither will the equals call, so null will result which is ok
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
return map.remove(new WeakReferenceThatIsEqualToItselfAndItsReferent((K) key, /* queue */ null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
|
||||
put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return new KeySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<java.util.Map.Entry<K, V>> entrySet() {
|
||||
return new EntrySet();
|
||||
}
|
||||
|
||||
}
|
||||
+27
-21
@@ -1,9 +1,10 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@@ -51,7 +52,7 @@ public class LockUtil {
|
||||
|
||||
private static final int NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK = 5;
|
||||
private static final Logger logger = Logger.getLogger(Util.class.getName());
|
||||
private static final Map<NamedReentrantReadWriteLock, TimePoint> lastTimeWriteLockWasObtained = new WeakHashMap<NamedReentrantReadWriteLock, TimePoint>();
|
||||
private static final Map<NamedReentrantReadWriteLock, TimePoint> lastTimeWriteLockWasObtained = new ConcurrentWeakHashMap<NamedReentrantReadWriteLock, TimePoint>();
|
||||
|
||||
/**
|
||||
* Tells how many other threads propagated which held lock to the key thread. During propagation, a lock is
|
||||
@@ -63,20 +64,20 @@ public class LockUtil {
|
||||
* The thread-specific value maps are used as monitor objects whenever decisions about thread-specific lock counts
|
||||
* need to be made.
|
||||
*/
|
||||
private static final Map<Thread, Map<Lock, Integer>> propagationCounts = new ConcurrentHashMap<Thread, Map<Lock,Integer>>();
|
||||
private static final Map<Thread, Map<Lock, Integer>> propagationCounts = new ConcurrentWeakHashMap<Thread, Map<Lock,Integer>>();
|
||||
|
||||
/**
|
||||
* Counts the "virtual" locks. A "virtual" lock is obtained if and only if at the time of calling
|
||||
* {@link #lockForRead(NamedReentrantReadWriteLock)} or {@link #lockForWrite(NamedReentrantReadWriteLock)} the
|
||||
* respective lock has a positive {@link #propagationCounts propagation count} for the current thread.
|
||||
*/
|
||||
private static final Map<Thread, Map<Lock, Integer>> virtualLockCounts = new ConcurrentHashMap<Thread, Map<Lock, Integer>>();
|
||||
private static final Map<Thread, Map<Lock, Integer>> virtualLockCounts = new ConcurrentWeakHashMap<Thread, Map<Lock, Integer>>();
|
||||
|
||||
/**
|
||||
* Redundant but easily accessible hold count per thread and lock. These are the actual lock hold counts as they are
|
||||
* recorded in the actual {@link NamedReentrantReadWriteLock} locks.
|
||||
*/
|
||||
private static final Map<Thread, Map<Lock, Integer>> lockCounts = new ConcurrentHashMap<Thread, Map<Lock, Integer>>();
|
||||
private static final Map<Thread, Map<Lock, Integer>> lockCounts = new ConcurrentWeakHashMap<Thread, Map<Lock, Integer>>();
|
||||
|
||||
public static void lockForRead(NamedReentrantReadWriteLock lock) {
|
||||
acquireLockVirtuallyOrActually(lock, lock.readLock(), ReadOrWrite.READ);
|
||||
@@ -84,9 +85,7 @@ public class LockUtil {
|
||||
|
||||
public static void lockForWrite(NamedReentrantReadWriteLock lock) {
|
||||
acquireLockVirtuallyOrActually(lock, lock.writeLock(), ReadOrWrite.WRITE);
|
||||
synchronized (lastTimeWriteLockWasObtained) {
|
||||
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
||||
}
|
||||
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
||||
}
|
||||
|
||||
private static void acquireLockVirtuallyOrActually(NamedReentrantReadWriteLock lock, final Lock readOrWriteLock, final ReadOrWrite readOrWrite) {
|
||||
@@ -147,9 +146,7 @@ public class LockUtil {
|
||||
unlockVirtuallyOrActually(lock, lock.writeLock());
|
||||
}
|
||||
final TimePoint timePointWriteLockWasObtained;
|
||||
synchronized (lastTimeWriteLockWasObtained) {
|
||||
timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock);
|
||||
}
|
||||
timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock);
|
||||
if (timePointWriteLockWasObtained == null) {
|
||||
logger.info("Internal error: write lock " + lock.getName()
|
||||
+ " to be unlocked but no time recorded for when it was last obtained.\n"
|
||||
@@ -355,9 +352,9 @@ public class LockUtil {
|
||||
}
|
||||
|
||||
|
||||
private static String getStackTrace(Thread thread) {
|
||||
private static String formatStackTrace(StackTraceElement[] stackTrace) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement sf : thread.getStackTrace()) {
|
||||
for (StackTraceElement sf : stackTrace) {
|
||||
sb.append(sf.toString());
|
||||
sb.append('\n');
|
||||
}
|
||||
@@ -365,7 +362,7 @@ public class LockUtil {
|
||||
}
|
||||
|
||||
private static String getCurrentStackTrace() {
|
||||
return getStackTrace(Thread.currentThread());
|
||||
return formatStackTrace(Thread.currentThread().getStackTrace());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +378,14 @@ public class LockUtil {
|
||||
try {
|
||||
locked = lock.tryLock(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK, TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
Thread writer = lockParent.getWriter();
|
||||
// capture the stack traces as quickly as possible to try to reflect the situation as it was when the lock couuldn't be obtained
|
||||
StackTraceElement[] writerStackTrace = writer != null ? writer.getStackTrace() : null;
|
||||
Map<Thread, StackTraceElement[]> readerStackTraces = new HashMap<Thread, StackTraceElement[]>();
|
||||
final List<Thread> readers = lockParent.getReaders();
|
||||
for (Thread reader : readers) {
|
||||
readerStackTraces.put(reader, reader.getStackTrace());
|
||||
}
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("Couldn't acquire lock ");
|
||||
message.append(lockDescriptionForTimeoutLogMessage);
|
||||
@@ -388,14 +393,15 @@ public class LockUtil {
|
||||
message.append(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK);
|
||||
message.append("s in thread " + Thread.currentThread().getName() + " at ");
|
||||
message.append(getCurrentStackTrace());
|
||||
Thread writer = lockParent.getWriter();
|
||||
if (writer != null) {
|
||||
message.append("\nThe current writer is:\n");
|
||||
appendThreadData(message, writer);
|
||||
appendThreadData(message, writer, writerStackTrace);
|
||||
}
|
||||
message.append("\nThe current readers are:\n");
|
||||
for (Thread reader : lockParent.getReaders()) {
|
||||
appendThreadData(message, reader);
|
||||
if (readers != null && !readers.isEmpty()) {
|
||||
message.append("\nThe current readers are:\n");
|
||||
for (Thread reader : readers) {
|
||||
appendThreadData(message, reader, readerStackTraces.get(reader));
|
||||
}
|
||||
}
|
||||
message.append("Trying again...");
|
||||
logger.info(message.toString());
|
||||
@@ -406,10 +412,10 @@ public class LockUtil {
|
||||
return locked;
|
||||
}
|
||||
|
||||
private static void appendThreadData(StringBuilder message, Thread writer) {
|
||||
private static void appendThreadData(StringBuilder message, Thread writer, StackTraceElement[] stackTrace) {
|
||||
message.append(writer);
|
||||
message.append('\n');
|
||||
message.append(getStackTrace(writer));
|
||||
message.append(formatStackTrace(stackTrace));
|
||||
message.append('\n');
|
||||
}
|
||||
|
||||
|
||||
Executable
+197
@@ -0,0 +1,197 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.CompetitorChangeListener;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.Team;
|
||||
import com.sap.sailing.domain.base.WithNationality;
|
||||
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
|
||||
import com.sap.sailing.domain.base.impl.DynamicCompetitor;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.impl.RGBColor;
|
||||
|
||||
/**
|
||||
* Tests the {@link CompetitorChangeListener} stuff including the serialization of the transitive listener
|
||||
* pattern on the {@link Team} structure.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class CompetitorListenerTest extends AbstractSerializationTest {
|
||||
private CompetitorChangeListener listener;
|
||||
private boolean nationalityChanged;
|
||||
private Object whatChangedNationality;
|
||||
private Object oldNationality;
|
||||
private Object newNationality;
|
||||
private boolean sailIdChanged;
|
||||
private String oldSailId;
|
||||
private String newSailId;
|
||||
private boolean nameChanged;
|
||||
private String oldName;
|
||||
private String newName;
|
||||
private boolean colorChanged;
|
||||
private Color oldColor;
|
||||
private Color newColor;
|
||||
private DynamicCompetitor competitor;
|
||||
private DomainFactoryImpl baseDomainFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
baseDomainFactory = new DomainFactoryImpl();
|
||||
competitor = TrackBasedTest.createCompetitor("Hasso");
|
||||
nationalityChanged = false;
|
||||
whatChangedNationality = null;
|
||||
oldNationality = null;
|
||||
newNationality = null;
|
||||
sailIdChanged = false;
|
||||
oldSailId = null;
|
||||
newSailId = null;
|
||||
nameChanged = false;
|
||||
oldName = null;
|
||||
newName = null;
|
||||
colorChanged = false;
|
||||
oldColor = null;
|
||||
newColor = null;
|
||||
listener = new CompetitorChangeListener() {
|
||||
private static final long serialVersionUID = 4581029778988240209L;
|
||||
|
||||
@Override
|
||||
public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) {
|
||||
nationalityChanged = true;
|
||||
whatChangedNationality = what;
|
||||
CompetitorListenerTest.this.oldNationality = oldNationality;
|
||||
CompetitorListenerTest.this.newNationality = newNationality;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sailIdChanged(String oldSailId, String newSailId) {
|
||||
sailIdChanged = true;
|
||||
CompetitorListenerTest.this.oldSailId = oldSailId;
|
||||
CompetitorListenerTest.this.newSailId = newSailId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nameChanged(String oldName, String newName) {
|
||||
nameChanged = true;
|
||||
CompetitorListenerTest.this.oldName = oldName;
|
||||
CompetitorListenerTest.this.newName = newName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void colorChanged(Color oldColor, Color newColor) {
|
||||
colorChanged = true;
|
||||
CompetitorListenerTest.this.oldColor = oldColor;
|
||||
CompetitorListenerTest.this.newColor = newColor;
|
||||
}
|
||||
};
|
||||
competitor.addCompetitorChangeListener(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompetitorListenerPatternForName() {
|
||||
competitor.setName("Dr. Hasso Plattner");
|
||||
assertTrue(nameChanged);
|
||||
assertEquals("Hasso", oldName);
|
||||
assertEquals("Dr. Hasso Plattner", newName);
|
||||
assertFalse(sailIdChanged);
|
||||
assertFalse(colorChanged);
|
||||
assertFalse(nationalityChanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompetitorListenerPatternForSailId() {
|
||||
final String myOldSailId = competitor.getBoat().getSailID();
|
||||
competitor.getBoat().setSailId("POR 40");
|
||||
assertFalse(nameChanged);
|
||||
assertTrue(sailIdChanged);
|
||||
assertEquals(myOldSailId, oldSailId);
|
||||
assertEquals("POR 40", newSailId);
|
||||
assertFalse(colorChanged);
|
||||
assertFalse(nationalityChanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompetitorListenerPatternForColor() {
|
||||
final Color myOldColor = competitor.getColor();
|
||||
final RGBColor myNewColor = new RGBColor(123, 12, 234);
|
||||
competitor.setColor(myNewColor);
|
||||
assertFalse(nameChanged);
|
||||
assertFalse(sailIdChanged);
|
||||
assertTrue(colorChanged);
|
||||
assertEquals(myOldColor, oldColor);
|
||||
assertEquals(myNewColor, newColor);
|
||||
assertFalse(nationalityChanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompetitorListenerPatternForNationality() {
|
||||
final Nationality myOldNationality = competitor.getTeam().getNationality();
|
||||
final Nationality myNewNationality = DomainFactory.INSTANCE.getOrCreateNationality("POR");
|
||||
competitor.getTeam().setNationality(myNewNationality);
|
||||
assertFalse(nameChanged);
|
||||
assertFalse(sailIdChanged);
|
||||
assertFalse(colorChanged);
|
||||
assertTrue(nationalityChanged);
|
||||
assertEquals(myOldNationality, oldNationality);
|
||||
assertEquals(myNewNationality, newNationality);
|
||||
assertSame(competitor.getTeam(), whatChangedNationality);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompetitorSerializationLosesAllExternalListeners() throws ClassNotFoundException, IOException {
|
||||
DomainFactory baseDomainFactory = new DomainFactoryImpl();
|
||||
DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory);
|
||||
clonedCompetitor.setName("Dr. Hasso Plattner");
|
||||
clonedCompetitor.getBoat().setSailId("POR 40");
|
||||
final RGBColor myNewColor = new RGBColor(123, 12, 234);
|
||||
clonedCompetitor.setColor(myNewColor);
|
||||
final Nationality myNewNationality = baseDomainFactory.getOrCreateNationality("POR");
|
||||
clonedCompetitor.getTeam().setNationality(myNewNationality);
|
||||
assertFalse(nameChanged);
|
||||
assertFalse(sailIdChanged);
|
||||
assertFalse(colorChanged);
|
||||
assertFalse(nationalityChanged);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompetitorSerializationListenerPatternForNameOnSerializedCompetitor() throws ClassNotFoundException, IOException {
|
||||
DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory);
|
||||
competitor = clonedCompetitor;
|
||||
competitor.addCompetitorChangeListener(listener);
|
||||
testSimpleCompetitorListenerPatternForName();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompetitorSerializationListenerPatternForColorOnSerializedCompetitor() throws ClassNotFoundException, IOException {
|
||||
DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory);
|
||||
competitor = clonedCompetitor;
|
||||
competitor.addCompetitorChangeListener(listener);
|
||||
testSimpleCompetitorListenerPatternForColor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompetitorSerializationListenerPatternForSailIdOnSerializedCompetitor() throws ClassNotFoundException, IOException {
|
||||
DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory);
|
||||
competitor = clonedCompetitor;
|
||||
competitor.addCompetitorChangeListener(listener);
|
||||
testSimpleCompetitorListenerPatternForSailId();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompetitorSerializationListenerPatternForNationalityOnSerializedCompetitor() throws ClassNotFoundException, IOException {
|
||||
DynamicCompetitor clonedCompetitor = cloneBySerialization(competitor, baseDomainFactory);
|
||||
competitor = clonedCompetitor;
|
||||
competitor.addCompetitorChangeListener(listener);
|
||||
testSimpleCompetitorListenerPatternForNationality();
|
||||
}
|
||||
}
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.util.impl.ConcurrentWeakHashMap;
|
||||
|
||||
/**
|
||||
* Tests the class {@link ConcurrentWeakHashMap}.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class ConcurrentWeakHashMapTest {
|
||||
@Test
|
||||
public void testPutAndGet() {
|
||||
ConcurrentWeakHashMap<String, String> m = new ConcurrentWeakHashMap<>();
|
||||
m.put("a", "b");
|
||||
assertEquals("b", m.get("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAndIterate() {
|
||||
ConcurrentWeakHashMap<String, String> m = new ConcurrentWeakHashMap<>();
|
||||
m.put("a", "b");
|
||||
assertEquals("b", m.values().iterator().next());
|
||||
assertEquals("a", m.keySet().iterator().next());
|
||||
assertEquals("a", m.entrySet().iterator().next().getKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAndPutAgain() {
|
||||
ConcurrentWeakHashMap<Integer, String> m = new ConcurrentWeakHashMap<>();
|
||||
Integer one1 = new Integer(1);
|
||||
Integer one2 = new Integer(1);
|
||||
assertNotSame(one1, one2);
|
||||
m.put(one1, "a");
|
||||
String replaced = m.put(one2, "b");
|
||||
assertEquals("a", replaced);
|
||||
assertEquals(1, m.size());
|
||||
assertEquals(m.keySet().iterator().next(), one2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWithSame() {
|
||||
ConcurrentWeakHashMap<Integer, String> m = new ConcurrentWeakHashMap<>();
|
||||
Integer one = new Integer(1);
|
||||
m.put(one, "a");
|
||||
m.remove(one);
|
||||
assertTrue(m.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveImpactOnKeySetAndEntrySet() {
|
||||
ConcurrentWeakHashMap<Integer, String> m = new ConcurrentWeakHashMap<>();
|
||||
Integer one = new Integer(1);
|
||||
m.put(one, "a");
|
||||
Set<Integer> keySet = m.keySet();
|
||||
Set<Entry<Integer, String>> entrySet = m.entrySet();
|
||||
assertEquals(1, keySet.size());
|
||||
assertEquals(1, entrySet.size());
|
||||
m.remove(one);
|
||||
assertTrue(m.isEmpty());
|
||||
assertTrue(keySet.isEmpty());
|
||||
assertTrue(entrySet.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWithEqual() {
|
||||
ConcurrentWeakHashMap<Integer, String> m = new ConcurrentWeakHashMap<>();
|
||||
Integer one1 = new Integer(1);
|
||||
Integer one2 = new Integer(1);
|
||||
assertNotSame(one1, one2);
|
||||
m.put(one1, "a");
|
||||
m.remove(one2);
|
||||
assertTrue(m.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeThrougKeySet() {
|
||||
ConcurrentWeakHashMap<Integer, String> m = new ConcurrentWeakHashMap<>();
|
||||
Integer one1 = new Integer(1);
|
||||
m.put(one1, "a");
|
||||
final Set<Integer> keySet = m.keySet();
|
||||
Iterator<Integer> keySetIter = keySet.iterator();
|
||||
keySetIter.next();
|
||||
keySetIter.remove();
|
||||
assertTrue(m.isEmpty());
|
||||
assertTrue(keySet.isEmpty());
|
||||
assertTrue(m.entrySet().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAndGetWithSubsequentGC() {
|
||||
Object key = createKeyObject();
|
||||
ConcurrentWeakHashMap<Object, String> m = new ConcurrentWeakHashMap<>();
|
||||
m.put(key, "b");
|
||||
assertEquals("b", m.get(key));
|
||||
key = null;
|
||||
forceEnqueuableWeakReferencesToBeEnqueued();
|
||||
assertTrue(m.isEmpty());
|
||||
}
|
||||
|
||||
private void forceEnqueuableWeakReferencesToBeEnqueued() {
|
||||
System.gc();
|
||||
try {
|
||||
long[] hugeArrayThatWontGetAllocated = new long[Integer.MAX_VALUE];
|
||||
assertTrue(hugeArrayThatWontGetAllocated.length > 0);
|
||||
} catch (OutOfMemoryError e) {
|
||||
// Ignore OME
|
||||
}
|
||||
try {
|
||||
Thread.sleep(50); // give GC some time to finish enqueuing and finalizing all weakly-referenced objects
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a separate method for this, assuming that the compiler won't put the object on the using method's stack
|
||||
* @return
|
||||
*/
|
||||
private Object createKeyObject() {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sailing.domain.tracking.WindWithConfidence;
|
||||
import com.sap.sailing.domain.tracking.impl.TrackBasedEstimationWindTrackImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
|
||||
|
||||
/**
|
||||
* See the issues documented in bug 1923 (http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=1923).
|
||||
* This test mocks {@link TrackedRace} to the degree necessary and fires more or less random "measured"
|
||||
* wind fixes against the {@link TrackBasedEstimationWindTrackImpl} while concurrently reading massively
|
||||
* from it. With this, the test is supposed to reproduce conditions as observed during events, leading
|
||||
* up to bug 1923. This can then be the basis for a fix.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class WindEstimationLockingUnderLoadTest {
|
||||
private static final Logger logger = Logger.getLogger(WindEstimationLockingUnderLoadTest.class.getName());
|
||||
|
||||
private static final int MIN_WIND_SPEED_IN_KNOTS = 5;
|
||||
|
||||
private static final int MAX_WIND_SPEED_IN_KNOTS = 20;
|
||||
|
||||
private TrackedRace mockedTrackedRace;
|
||||
|
||||
private WindSource realWindSource;
|
||||
|
||||
private TrackBasedEstimationWindTrackImpl estimationTrack;
|
||||
|
||||
private WindTrackImpl measuredTrack;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
realWindSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, "1");
|
||||
measuredTrack = new WindTrackImpl(WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* useSpeed */ true, /* nameForReadWriteLock */ "Test wind track in "+getClass().getName());
|
||||
mockedTrackedRace = mockTrackedRace();
|
||||
estimationTrack = new TrackBasedEstimationWindTrackImpl(mockedTrackedRace, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, 0.5);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// clean up all Mockito stubbing leaks, particularly the InvocationImpl objects attached to any ThreadLocal;
|
||||
// see also bug 1923, comment #9.
|
||||
Mockito.reset();
|
||||
}
|
||||
|
||||
private TrackedRace mockTrackedRace() {
|
||||
TrackedRace result = mock(TrackedRace.class);
|
||||
RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class);
|
||||
when(result.getRace()).thenReturn(mockedRaceDefinition);
|
||||
when(mockedRaceDefinition.getName()).thenReturn("Test Race");
|
||||
when(result.getEstimatedWindDirectionWithConfidence((Position) any(), (TimePoint) any())).thenAnswer(new Answer<WindWithConfidence<TimePoint>>() {
|
||||
public WindWithConfidence<TimePoint> answer(InvocationOnMock invocation) {
|
||||
return randomWindOrNull();
|
||||
}
|
||||
});
|
||||
when(result.getOrCreateWindTrack(realWindSource)).thenReturn(measuredTrack);
|
||||
when(result.getStatus()).thenReturn(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, /* loadingProgress */ 1.0));
|
||||
when(result.getMillisecondsOverWhichToAverageWind()).thenReturn(30000l);
|
||||
return result;
|
||||
}
|
||||
|
||||
private WindWithConfidence<TimePoint> randomWindOrNull() {
|
||||
final WindWithConfidence<TimePoint> result;
|
||||
double speedInKnots = MAX_WIND_SPEED_IN_KNOTS*Math.random();
|
||||
if (speedInKnots < MIN_WIND_SPEED_IN_KNOTS) {
|
||||
result = null;
|
||||
} else {
|
||||
double directionInDegrees = 360*Math.random();
|
||||
double confidence = Math.random();
|
||||
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
|
||||
result = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(49, 8), now, new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(directionInDegrees))),
|
||||
confidence, now, /* useSpeed */ true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleWindEstimation() {
|
||||
// We can expect MIN_WIND_SPEED_IN_KNOTS/MAX_WIND_SPEED_IN_KNOTS results to be null, but as there is a significant
|
||||
// difference between the two, with a sufficient amount of random samples there has to be at least one non-null
|
||||
// result
|
||||
int notNullCount = 0;
|
||||
for (int i=0; i<1000; i++) {
|
||||
Wind estimatedWind = estimationTrack.getAveragedWind(/* position */ null, MillisecondsTimePoint.now().minus(10000*i));
|
||||
if (estimatedWind != null) {
|
||||
notNullCount++;
|
||||
}
|
||||
}
|
||||
assertTrue(notNullCount > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendingNewWindFixToEstimationTrack() {
|
||||
final WindImpl wind = new WindImpl(new DegreePosition(49, 8), MillisecondsTimePoint.now(),
|
||||
new KnotSpeedWithBearingImpl(/* speedInKnots */12., new DegreeBearingImpl(47)));
|
||||
addMeasuredWindFix(wind);
|
||||
// no verdict, really; just expecting that there is no exception raised
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingManyWindFixesWhileReadingLikeCrazy() throws InterruptedException {
|
||||
Thread inserter = new Thread("Inserter thread in "+getClass().getName()+".testAddingManyWindFixesWhileReadingLikeCrazy") {
|
||||
@Override
|
||||
public void run() {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
final WindImpl wind = new WindImpl(new DegreePosition(49, 8), now, new KnotSpeedWithBearingImpl(
|
||||
/* speedInKnots */12., new DegreeBearingImpl(47)));
|
||||
addMeasuredWindFix(wind);
|
||||
now = now.plus(10);
|
||||
}
|
||||
logger.info("Inserter thread done");
|
||||
}
|
||||
};
|
||||
Runnable readerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
for (int i=0; i<100000; i++) {
|
||||
estimationTrack.getAveragedWind(/* position */ null, now);
|
||||
now = now.plus(10); // the cache quantizes to a full second; so not all time points will automatically fetch uncached values
|
||||
}
|
||||
logger.info(Thread.currentThread().getName()+" done");
|
||||
}
|
||||
};
|
||||
inserter.start();
|
||||
List<Thread> readers = new ArrayList<Thread>();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Thread reader = new Thread(readerRunnable, "Reader thread "+i+" in " + getClass().getName()
|
||||
+ ".testAddingManyWindFixesWhileReadingLikeCrazy");
|
||||
reader.start();
|
||||
readers.add(reader);
|
||||
}
|
||||
inserter.join();
|
||||
for (Thread reader : readers) {
|
||||
reader.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the wind fix to the {@link #measuredTrack} and explicitly notifies {@link #estimationTrack} which is
|
||||
* not a listener on the mocked tracked race or the wind track and therefore otherwise wouldn't receive the fix
|
||||
*/
|
||||
private void addMeasuredWindFix(final WindImpl wind) {
|
||||
measuredTrack.add(wind);
|
||||
estimationTrack.windDataReceived(wind, realWindSource);
|
||||
}
|
||||
}
|
||||
+3
@@ -35,6 +35,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOver
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
@@ -86,6 +87,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
|
||||
return new HighPointFirstGets10LastBreaksTie();
|
||||
case LOW_POINT_WINNER_GETS_ZERO:
|
||||
return new LowPointWinnerGetsZero();
|
||||
case HIGH_POINT_WINNER_GETS_SIX:
|
||||
return new HighPointWinnerGetsSix();
|
||||
}
|
||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||
}
|
||||
|
||||
+86
-11
@@ -7,11 +7,15 @@ import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorChangeListener;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.WithNationality;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
@@ -39,7 +43,8 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
*/
|
||||
public class LeaderboardCacheManager {
|
||||
private final WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener> scoreCorrectionListeners;
|
||||
private final NamedReentrantReadWriteLock scoreCorrectionListenersLock;
|
||||
private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock;
|
||||
private final WeakHashMap<Leaderboard, CacheInvalidationUponCompetitorChangeListener> competitorChangeListeners;
|
||||
private final WeakHashMap<Leaderboard, Map<TrackedRace, Set<CacheInvalidationListener>>> invalidationListenersPerLeaderboard;
|
||||
private final WeakHashMap<Leaderboard, RaceColumnListener> raceColumnListeners;
|
||||
|
||||
@@ -113,6 +118,64 @@ public class LeaderboardCacheManager {
|
||||
}
|
||||
}
|
||||
|
||||
private class CacheInvalidationUponCompetitorChangeListener implements CompetitorChangeListener {
|
||||
private static final long serialVersionUID = 8308312509904366143L;
|
||||
private final Leaderboard leaderboard;
|
||||
private final Set<Competitor> observedCompetitors;
|
||||
|
||||
/**
|
||||
* Registers this listener as listener for all of the <code>leaderboard</code>'s current
|
||||
* {@link Leaderboard#getCompetitors() competitors}
|
||||
*/
|
||||
public CacheInvalidationUponCompetitorChangeListener(Leaderboard leaderboard) {
|
||||
this.leaderboard = leaderboard;
|
||||
observedCompetitors = new HashSet<>();
|
||||
updateCompetitorListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sailIdChanged(String oldSailId, String newSailId) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void colorChanged(Color oldColor, Color newColor) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nameChanged(String oldName, String newName) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
public synchronized void updateCompetitorListeners() {
|
||||
Set<Competitor> competitorsToStopObserving = new HashSet<>(observedCompetitors);
|
||||
for (Competitor competitor : leaderboard.getCompetitors()) {
|
||||
if (!observedCompetitors.contains(competitor)) {
|
||||
competitor.addCompetitorChangeListener(this); // start observing competitor
|
||||
} else {
|
||||
competitorsToStopObserving.remove(competitor); // keep observing competitor
|
||||
}
|
||||
}
|
||||
for (Competitor competitorToStopObserving : competitorsToStopObserving) {
|
||||
competitorToStopObserving.removeCompetitorChangeListener(this);
|
||||
observedCompetitors.remove(competitorToStopObserving);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removeFromAllCompetitors() {
|
||||
for (Competitor competitor : observedCompetitors) {
|
||||
competitor.removeCompetitorChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CacheInvalidationUponScoreCorrectionListener implements ScoreCorrectionListener {
|
||||
private final Leaderboard leaderboard;
|
||||
|
||||
@@ -146,10 +209,12 @@ public class LeaderboardCacheManager {
|
||||
|
||||
public LeaderboardCacheManager(LeaderboardCache leaderboardCache) {
|
||||
this.leaderboardCache = leaderboardCache;
|
||||
this.invalidationListenersPerLeaderboard = new WeakHashMap<Leaderboard, Map<TrackedRace, Set<CacheInvalidationListener>>>();
|
||||
this.invalidationListenersPerLeaderboard = new WeakHashMap<>();
|
||||
this.raceColumnListeners = new WeakHashMap<Leaderboard, RaceColumnListener>();
|
||||
this.scoreCorrectionListeners = new WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener>();
|
||||
this.scoreCorrectionListenersLock = new NamedReentrantReadWriteLock("Score correction listeners", /* fair */ false);
|
||||
this.scoreCorrectionListeners = new WeakHashMap<>();
|
||||
this.scoreCorrectionAndCompetitorChangeListenersLock = new NamedReentrantReadWriteLock(
|
||||
"Score correction and competitor change listeners", /* fair */false);
|
||||
this.competitorChangeListeners = new WeakHashMap<>();
|
||||
}
|
||||
|
||||
private void removeFromCache(Leaderboard leaderboard) {
|
||||
@@ -167,14 +232,21 @@ public class LeaderboardCacheManager {
|
||||
synchronized (raceColumnListeners) {
|
||||
leaderboard.removeRaceColumnListener(raceColumnListeners.remove(leaderboard));
|
||||
}
|
||||
LockUtil.lockForWrite(scoreCorrectionListenersLock);
|
||||
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
final CacheInvalidationUponScoreCorrectionListener removedScoreCorrectionListener;
|
||||
final CacheInvalidationUponCompetitorChangeListener removedCompetitorChangeListener;
|
||||
try {
|
||||
removedScoreCorrectionListener = scoreCorrectionListeners.remove(leaderboard);
|
||||
removedCompetitorChangeListener = competitorChangeListeners.remove(leaderboard);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(scoreCorrectionListenersLock);
|
||||
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
}
|
||||
if (removedScoreCorrectionListener != null) {
|
||||
leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener);
|
||||
}
|
||||
if (removedCompetitorChangeListener != null) {
|
||||
removedCompetitorChangeListener.removeFromAllCompetitors();
|
||||
}
|
||||
leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener);
|
||||
// invalidate after the listeners have been removed; this is important because invalidation may trigger a
|
||||
// re-calculation which then in turn may call add(leaderboard) asynchronously again which may be executed
|
||||
// before the listener removal happens here. This could lead to a race condition where listeners are
|
||||
@@ -195,22 +267,25 @@ public class LeaderboardCacheManager {
|
||||
private void registerAsListener(final Leaderboard leaderboard) {
|
||||
// only add as listener again if not yet added
|
||||
final boolean containsKey;
|
||||
LockUtil.lockForRead(scoreCorrectionListenersLock);
|
||||
LockUtil.lockForRead(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
try {
|
||||
containsKey = scoreCorrectionListeners.containsKey(leaderboard);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(scoreCorrectionListenersLock);
|
||||
LockUtil.unlockAfterRead(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
}
|
||||
if (!containsKey) {
|
||||
LockUtil.lockForWrite(scoreCorrectionListenersLock);
|
||||
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
try {
|
||||
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
|
||||
registerListener(leaderboard, trackedRace);
|
||||
}
|
||||
final CacheInvalidationUponScoreCorrectionListener scoreCorrectionListener = new CacheInvalidationUponScoreCorrectionListener(
|
||||
leaderboard);
|
||||
final CacheInvalidationUponCompetitorChangeListener competitorChangeListener = new CacheInvalidationUponCompetitorChangeListener(
|
||||
leaderboard);
|
||||
leaderboard.getScoreCorrection().addScoreCorrectionListener(scoreCorrectionListener);
|
||||
scoreCorrectionListeners.put(leaderboard, scoreCorrectionListener);
|
||||
competitorChangeListeners.put(leaderboard, competitorChangeListener);
|
||||
final RaceColumnListener raceColumnListener = new RaceColumnListener() {
|
||||
private static final long serialVersionUID = 8165124797028386317L;
|
||||
|
||||
@@ -312,7 +387,7 @@ public class LeaderboardCacheManager {
|
||||
raceColumnListeners.put(leaderboard, raceColumnListener);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(scoreCorrectionListenersLock);
|
||||
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-7
@@ -24,11 +24,25 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint {
|
||||
public HighPointFirstGetsFixedScore(double scoreForRaceWinner) {
|
||||
this.scoreForRaceWinner = scoreForRaceWinner;
|
||||
}
|
||||
|
||||
protected double getScoreForRaceWinner() {
|
||||
return scoreForRaceWinner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
|
||||
final int effectiveRank;
|
||||
final int effectiveRank = getEffectiveRank(raceColumn, competitor, rank);
|
||||
final Double result;
|
||||
if (effectiveRank == 0) {
|
||||
result = null;
|
||||
} else {
|
||||
result = Math.max(1.0, (double) (getScoreForRaceWinner() - effectiveRank + 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected int getEffectiveRank(RaceColumn raceColumn, Competitor competitor, int rank) {
|
||||
final int effectiveRank;
|
||||
int competitorFleetOrdering;
|
||||
if (rank == 0) {
|
||||
effectiveRank = 0;
|
||||
@@ -41,11 +55,6 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint {
|
||||
effectiveRank = rank;
|
||||
}
|
||||
}
|
||||
if (effectiveRank == 0) {
|
||||
result = null;
|
||||
} else {
|
||||
result = Math.max(1.0, (double) (scoreForRaceWinner - effectiveRank + 1));
|
||||
}
|
||||
return result;
|
||||
return effectiveRank;
|
||||
}
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
|
||||
public class HighPointWinnerGetsSix extends HighPointFirstGetsFixedScore {
|
||||
private static final long serialVersionUID = -4116295702464748719L;
|
||||
|
||||
public HighPointWinnerGetsSix() {
|
||||
super(/* scoreForRaceWinner */ 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.HIGH_POINT_WINNER_GETS_SIX;
|
||||
}
|
||||
}
|
||||
+9
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Boat;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorChangeListener;
|
||||
import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.Team;
|
||||
@@ -63,6 +64,14 @@ public class DummyMarkPassingWithTimePointOnly implements MarkPassing {
|
||||
public Color getColor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompetitorChangeListener(CompetitorChangeListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorChangeListener(CompetitorChangeListener listener) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
@@ -131,6 +131,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
public void recordFix(Competitor competitor, GPSFixMoving fix) {
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
|
||||
if (track != null) {
|
||||
if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest(""+competitor.getName() + ": " + fix);
|
||||
}
|
||||
track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1009,7 +1009,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
|
||||
final boolean result;
|
||||
lockForWrite();
|
||||
try {
|
||||
result = super.add(fix);
|
||||
result = addWithoutLocking(fix);
|
||||
invalidateValidityAndDistanceCaches(fix);
|
||||
} finally {
|
||||
unlockAfterWrite();
|
||||
|
||||
+161
-52
@@ -82,13 +82,21 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
|
||||
private final NavigableSet<TimePoint> timePointsWithCachedNullResult;
|
||||
|
||||
/**
|
||||
* Access to this cache is guarded by {@link #cacheLock}.
|
||||
*/
|
||||
private final NavigableSet<WindWithConfidence<TimePoint>> cache;
|
||||
|
||||
/**
|
||||
* Access to {@link #cache} and {@link #timePointsWithCachedNullResult} and
|
||||
* {@link #timePointsWithCachedNullResultFastContains} is guarded by this lock.
|
||||
*/
|
||||
private final NamedReentrantReadWriteLock cacheLock;
|
||||
|
||||
/**
|
||||
* Access to {@link #scheduledRefreshInterval} is guarded by this lock. In particular, when a client
|
||||
* holds this lock the client can be sure that {@link InvalidationInterval#isSet()} implies that a
|
||||
* cache update is scheduled or already running.
|
||||
*/
|
||||
private final NamedReentrantReadWriteLock scheduledRefreshIntervalLock;
|
||||
|
||||
private final Weigher<TimePoint> weigher;
|
||||
|
||||
/**
|
||||
@@ -106,7 +114,13 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
|
||||
private static class InvalidationInterval implements Serializable {
|
||||
private static final long serialVersionUID = -6406690520919193690L;
|
||||
/**
|
||||
* Either both {@link #start} and {@link #end} are <code>null</code>, or both are non-<code>null</code>
|
||||
*/
|
||||
private WindWithConfidence<TimePoint> start;
|
||||
/**
|
||||
* Either both {@link #start} and {@link #end} are <code>null</code>, or both are non-<code>null</code>
|
||||
*/
|
||||
private TimePoint end;
|
||||
public InvalidationInterval() {
|
||||
super();
|
||||
@@ -117,18 +131,38 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
public TimePoint getEnd() {
|
||||
return end;
|
||||
}
|
||||
public synchronized void clear() {
|
||||
public void clear() {
|
||||
start = null;
|
||||
end = null;
|
||||
}
|
||||
public synchronized boolean isSet() {
|
||||
public boolean isSet() {
|
||||
return start != null && end != null;
|
||||
}
|
||||
public synchronized void set(WindWithConfidence<TimePoint> startOfInvalidation, TimePoint endOfInvalidation) {
|
||||
|
||||
/**
|
||||
* Checks whether the interval starting at {@link #start} and ending at {@link #end} is fully contained in this
|
||||
* interval. Neither of the two arguments is allowed to be <code>null</code>. The interval is contained in this
|
||||
* interval if this interval {@link #isSet() is set} and this interval's {@link #getStart() start} is at or
|
||||
* before <code>start</code> and this interval's {@link #getEnd() end} is at or after <code>end</code>.
|
||||
*/
|
||||
public boolean contains(TimePoint start, TimePoint end) {
|
||||
return isSet() && !getStart().getObject().getTimePoint().after(start) && !end.after(getEnd());
|
||||
}
|
||||
/**
|
||||
* Neither of the two arguments is allowed to be <code>null</code>.
|
||||
*/
|
||||
public void set(WindWithConfidence<TimePoint> startOfInvalidation, TimePoint endOfInvalidation) {
|
||||
assert startOfInvalidation != null;
|
||||
assert endOfInvalidation != null;
|
||||
this.start = startOfInvalidation;
|
||||
this.end = endOfInvalidation;
|
||||
}
|
||||
public synchronized void extend(WindWithConfidence<TimePoint> startOfInvalidation, TimePoint endOfInvalidation) {
|
||||
/**
|
||||
* Neither of the two arguments is allowed to be <code>null</code>.
|
||||
*/
|
||||
public void extend(WindWithConfidence<TimePoint> startOfInvalidation, TimePoint endOfInvalidation) {
|
||||
assert startOfInvalidation != null;
|
||||
assert endOfInvalidation != null;
|
||||
if (startOfInvalidation.getObject().getTimePoint().compareTo(start.getObject().getTimePoint()) < 0) {
|
||||
this.start = startOfInvalidation;
|
||||
}
|
||||
@@ -136,11 +170,54 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
end = endOfInvalidation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves this object unchanged. Returns a new {@link InvalidationInterval} object. If the interval defined by
|
||||
* <code>start</code> and <code>end</code> does not exceed this interval, the resulting interval will have
|
||||
* {@link #isSet()}<code>==false</code>. Otherwise, the resulting interval will contain all time ranges from
|
||||
* <code>start</code> to <code>end</code> (inclusive) that are not in this interval. In particular, if
|
||||
* <code>start</code>..<code>end</code> exceeds this interval on both ends, the resulting interval will start at
|
||||
* <code>start</code> and end at <code>end</code>. If this interval has {@link #isSet()}<code>==false</code>,
|
||||
* the resulting interval will range from <code>start</code> to <code>end</code>.
|
||||
*
|
||||
* @param start must not be <code>null</code>
|
||||
* @param end must not be <code>null</code>
|
||||
*/
|
||||
public InvalidationInterval subtract(WindWithConfidence<TimePoint> start, TimePoint end) {
|
||||
assert start != null;
|
||||
assert end != null;
|
||||
final InvalidationInterval result = new InvalidationInterval();
|
||||
if (!isSet()) {
|
||||
result.set(start, end);
|
||||
} else {
|
||||
final WindWithConfidence<TimePoint> newStart;
|
||||
final TimePoint startTimePoint = getStart().getObject().getTimePoint();
|
||||
if (start.getObject().getTimePoint().before(startTimePoint)) {
|
||||
newStart = start;
|
||||
} else {
|
||||
// don't go beyond the end of time, avoiding overflow
|
||||
newStart = getDummyFixWithConfidence(getEnd().asMillis()==Long.MAX_VALUE?getEnd():getEnd().plus(1));
|
||||
}
|
||||
final TimePoint newEnd;
|
||||
if (end.after(getEnd())) {
|
||||
newEnd = end;
|
||||
} else {
|
||||
// avoid underflow
|
||||
newEnd = startTimePoint.asMillis()==0?startTimePoint:startTimePoint.minus(1);
|
||||
}
|
||||
if (!newStart.getObject().getTimePoint().after(newEnd)) {
|
||||
result.set(newStart, newEnd);
|
||||
} // else, the resulting interval remains unset
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #scheduleCacheRefresh(WindWithConfidence, TimePoint)} synchronizes on this object before changing it
|
||||
* and when actually invalidating the cache.
|
||||
* {@link #scheduleCacheRefresh(WindWithConfidence, TimePoint)} synchronizes on this object before changing it and
|
||||
* when actually invalidating the cache. When a querying thread holds at least the read lock of {@link #cacheLock}
|
||||
* and the refresh interval it {@link InvalidationInterval#isSet() is set}, an invalidation is currently running or
|
||||
* has been scheduled.
|
||||
*/
|
||||
private final InvalidationInterval scheduledRefreshInterval;
|
||||
|
||||
@@ -171,8 +248,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
return o1.getObject().getTimePoint().compareTo(o2.getObject().getTimePoint());
|
||||
}
|
||||
});
|
||||
cacheLock = new NamedReentrantReadWriteLock(TrackBasedEstimationWindTrackImpl.class.getSimpleName()+" for race "+
|
||||
trackedRace.getRace().getName(), /* fair */ false);
|
||||
cacheLock = new NamedReentrantReadWriteLock(TrackBasedEstimationWindTrackImpl.class.getSimpleName()
|
||||
+ " cacheLock for race " + trackedRace.getRace().getName(), /* fair */false);
|
||||
scheduledRefreshIntervalLock = new NamedReentrantReadWriteLock(
|
||||
TrackBasedEstimationWindTrackImpl.class.getSimpleName() + " scheduledRefreshIntervalLock for race "
|
||||
+ trackedRace.getRace().getName(), /* fair */false);
|
||||
virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace);
|
||||
weigher = ConfidenceFactory.INSTANCE
|
||||
.createHyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageWind());
|
||||
@@ -183,9 +263,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes serialization on this object to avoid the cache being updated while being written.
|
||||
* Uses the {@link #cacheLock} and {@link #scheduledRefreshIntervalLock} to make serialization on this object thread
|
||||
* safe by avoiding the cache being updated while being written.
|
||||
*/
|
||||
private synchronized void writeObject(ObjectOutputStream s) throws IOException {
|
||||
private void writeObject(ObjectOutputStream s) throws IOException {
|
||||
LockUtil.lockForRead(scheduledRefreshIntervalLock);
|
||||
LockUtil.lockForRead(cacheLock);
|
||||
lockForRead();
|
||||
try {
|
||||
@@ -193,6 +275,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
} finally {
|
||||
unlockAfterRead();
|
||||
LockUtil.unlockAfterRead(cacheLock);
|
||||
LockUtil.unlockAfterRead(scheduledRefreshIntervalLock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,29 +310,22 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* The caller must <em>not</em> hold the read lock of {@link #cacheLock} when calling this method (unless the caller
|
||||
* also holds the corresponding write lock) because this method will acquire the corresponding write lock, and
|
||||
* upgrading from a read lock to a write lock is not possible.
|
||||
*/
|
||||
protected void cache(TimePoint timePoint, WindWithConfidence<TimePoint> fix) {
|
||||
// can't use lockForWrite() here because caching can happen while holding the read lock, and the lock can't be
|
||||
// upgraded. But lockForRead() and synchronization will do the job because all invalidations lock the write lock,
|
||||
// and all contains() checks and get() calls use synchronization too.
|
||||
LockUtil.lockForWrite(cacheLock);
|
||||
try {
|
||||
// synchronization necessary to protect writeObject from ConcurrentModificationException
|
||||
synchronized (this) {
|
||||
if (fix == null) {
|
||||
synchronized (timePointsWithCachedNullResult) {
|
||||
timePointsWithCachedNullResult.add(timePoint);
|
||||
}
|
||||
synchronized (timePointsWithCachedNullResultFastContains) {
|
||||
timePointsWithCachedNullResultFastContains.add(timePoint);
|
||||
}
|
||||
} else {
|
||||
LockUtil.lockForWrite(cacheLock);
|
||||
try {
|
||||
cache.add(fix);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(cacheLock);
|
||||
}
|
||||
}
|
||||
if (fix == null) {
|
||||
timePointsWithCachedNullResult.add(timePoint);
|
||||
timePointsWithCachedNullResultFastContains.add(timePoint);
|
||||
} else {
|
||||
cache.add(fix);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(cacheLock);
|
||||
@@ -263,19 +339,29 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
* will not be massively delayed by having to re-calculate the estimation over and over again.
|
||||
*/
|
||||
private void scheduleCacheRefresh(WindWithConfidence<TimePoint> startOfInvalidation, TimePoint endOfInvalidation) {
|
||||
LockUtil.lockForWrite(cacheLock);
|
||||
final boolean alreadyContained;
|
||||
LockUtil.lockForRead(scheduledRefreshIntervalLock);
|
||||
try {
|
||||
if (!scheduledRefreshInterval.isSet()) {
|
||||
// according to the invariant this implies [1]==null
|
||||
scheduledRefreshInterval.set(startOfInvalidation, endOfInvalidation);
|
||||
startSchedulerForCacheRefresh();
|
||||
} else {
|
||||
// this means that an invalidation is already scheduled; as long as we're synchronized on scheduledInvalidationInterval
|
||||
// we can safely extend the interval; the invalidation won't start before we release the lock
|
||||
scheduledRefreshInterval.extend(startOfInvalidation, endOfInvalidation);
|
||||
}
|
||||
alreadyContained = scheduledRefreshInterval.contains(startOfInvalidation.getObject().getTimePoint(), endOfInvalidation);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(cacheLock);
|
||||
LockUtil.unlockAfterRead(scheduledRefreshIntervalLock);
|
||||
}
|
||||
if (!alreadyContained) {
|
||||
LockUtil.lockForWrite(scheduledRefreshIntervalLock);
|
||||
try {
|
||||
if (!scheduledRefreshInterval.isSet()) {
|
||||
// according to the invariant this implies [1]==null
|
||||
scheduledRefreshInterval.set(startOfInvalidation, endOfInvalidation);
|
||||
startSchedulerForCacheRefresh();
|
||||
} else {
|
||||
// this means that an invalidation or incremental refresh is already scheduled; as long as we're
|
||||
// synchronized on scheduledInvalidationInterval we can safely extend the interval; the invalidation
|
||||
// won't start before we release the lock
|
||||
scheduledRefreshInterval.extend(startOfInvalidation, endOfInvalidation);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +371,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
* running.
|
||||
*/
|
||||
private void invalidateCache() {
|
||||
LockUtil.lockForWrite(scheduledRefreshIntervalLock);
|
||||
LockUtil.lockForWrite(cacheLock);
|
||||
try {
|
||||
Iterator<WindWithConfidence<TimePoint>> iter = (scheduledRefreshInterval.getStart() == null ? getCachedFixes()
|
||||
@@ -312,6 +399,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
scheduledRefreshInterval.clear();
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(cacheLock);
|
||||
LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,27 +409,33 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
private void refreshCacheIncrementally() {
|
||||
Set<WindWithConfidence<TimePoint>> windFixesToRecalculate = new HashSet<WindWithConfidence<TimePoint>>();
|
||||
Set<TimePoint> cachedNullResultsToRecalculate = new HashSet<TimePoint>();
|
||||
final WindWithConfidence<TimePoint> refreshIntervalStart;
|
||||
final TimePoint refreshIntervalEnd;
|
||||
LockUtil.lockForRead(scheduledRefreshIntervalLock);
|
||||
LockUtil.lockForRead(cacheLock);
|
||||
try {
|
||||
Iterator<WindWithConfidence<TimePoint>> iter = (scheduledRefreshInterval.getStart() == null ? getCachedFixes()
|
||||
: getCachedFixes().tailSet(scheduledRefreshInterval.getStart(), /* inclusive */true)).iterator();
|
||||
Iterator<TimePoint> nullIter = (scheduledRefreshInterval.getStart() == null ? timePointsWithCachedNullResult
|
||||
: timePointsWithCachedNullResult.tailSet(scheduledRefreshInterval.getStart().getObject().getTimePoint(), /* inclusive */
|
||||
refreshIntervalStart = scheduledRefreshInterval.getStart();
|
||||
Iterator<WindWithConfidence<TimePoint>> iter = (refreshIntervalStart == null ? getCachedFixes()
|
||||
: getCachedFixes().tailSet(refreshIntervalStart, /* inclusive */true)).iterator();
|
||||
Iterator<TimePoint> nullIter = (refreshIntervalStart == null ? timePointsWithCachedNullResult
|
||||
: timePointsWithCachedNullResult.tailSet(refreshIntervalStart.getObject().getTimePoint(), /* inclusive */
|
||||
true)).iterator();
|
||||
WindWithConfidence<TimePoint> nextFixToRecalculate = null;
|
||||
refreshIntervalEnd = scheduledRefreshInterval.getEnd();
|
||||
while (iter.hasNext() &&
|
||||
((nextFixToRecalculate = iter.next()).getObject().getTimePoint().compareTo(scheduledRefreshInterval.getEnd()) < 0) ||
|
||||
scheduledRefreshInterval.getEnd() == null) {
|
||||
((nextFixToRecalculate = iter.next()).getObject().getTimePoint().compareTo(refreshIntervalEnd) < 0) ||
|
||||
refreshIntervalEnd == null) {
|
||||
windFixesToRecalculate.add(nextFixToRecalculate);
|
||||
}
|
||||
TimePoint nextNullResultToRecalculate = null;
|
||||
while (nullIter.hasNext() &&
|
||||
((nextNullResultToRecalculate = nullIter.next()).compareTo(scheduledRefreshInterval.getEnd()) < 0) ||
|
||||
scheduledRefreshInterval.getEnd() == null) {
|
||||
((nextNullResultToRecalculate = nullIter.next()).compareTo(refreshIntervalEnd) < 0) ||
|
||||
refreshIntervalEnd == null) {
|
||||
cachedNullResultsToRecalculate.add(nextNullResultToRecalculate);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(cacheLock);
|
||||
LockUtil.unlockAfterRead(scheduledRefreshIntervalLock);
|
||||
}
|
||||
Set<TimePoint> nullRemovals = new HashSet<TimePoint>();
|
||||
Set<TimePoint> nullInsertions = new HashSet<TimePoint>();
|
||||
@@ -381,10 +475,22 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
for (Map.Entry<TimePoint, WindWithConfidence<TimePoint>> cacheInsertion : cacheInsertions.entrySet()) {
|
||||
cache(cacheInsertion.getKey(), cacheInsertion.getValue());
|
||||
}
|
||||
scheduledRefreshInterval.clear();
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(cacheLock);
|
||||
}
|
||||
LockUtil.lockForWrite(scheduledRefreshIntervalLock);
|
||||
try {
|
||||
// now remove the interval for which the cache was refreshed; note that in between the interval may have been extended;
|
||||
// for the extended part of the interval that hasn't been refreshed during this run, a new refresh needs to be
|
||||
// requested
|
||||
InvalidationInterval remainingRefreshInterval = scheduledRefreshInterval.subtract(refreshIntervalStart, refreshIntervalEnd);
|
||||
scheduledRefreshInterval.clear();
|
||||
if (remainingRefreshInterval.isSet()) {
|
||||
scheduleCacheRefresh(remainingRefreshInterval.getStart(), remainingRefreshInterval.getEnd());
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(scheduledRefreshIntervalLock);
|
||||
}
|
||||
}
|
||||
|
||||
private void startSchedulerForCacheRefresh() {
|
||||
@@ -452,15 +558,18 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
return result;
|
||||
}
|
||||
|
||||
private WindWithConfidence<TimePoint> getDummyFixWithConfidence(TimePoint timePoint) {
|
||||
private static WindWithConfidence<TimePoint> getDummyFixWithConfidence(TimePoint timePoint) {
|
||||
return new WindWithConfidenceImpl<TimePoint>(new WindImpl(null, timePoint, defaultSpeedWithBearing), 0,
|
||||
timePoint, /* useSpeed */false);
|
||||
}
|
||||
|
||||
private boolean nullResultCacheContains(TimePoint timePoint) {
|
||||
assertReadLock();
|
||||
synchronized (timePointsWithCachedNullResultFastContains) {
|
||||
LockUtil.lockForRead(cacheLock);
|
||||
try {
|
||||
return timePointsWithCachedNullResultFastContains.contains(timePoint);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(cacheLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -52,10 +52,6 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
|
||||
*/
|
||||
abstract protected Wind getWind(Position p, TimePoint timePoint);
|
||||
|
||||
public long getResolutionInMilliseconds() {
|
||||
return resolutionInMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from expected to be an integer multiple of {@link #resolutionInMilliseconds} or <code>null</code>
|
||||
* @param to expected to be an integer multiple of {@link #resolutionInMilliseconds} or <code>null</code>
|
||||
@@ -71,6 +67,10 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
|
||||
this.resolutionInMilliseconds = resolutionInMilliseconds;
|
||||
}
|
||||
|
||||
public long getResolutionInMilliseconds() {
|
||||
return resolutionInMilliseconds;
|
||||
}
|
||||
|
||||
protected WindTrack getTrack() {
|
||||
return track;
|
||||
}
|
||||
|
||||
+8
-4
@@ -132,10 +132,14 @@ public class CompetitorEditDialog extends DataEntryDialog<CompetitorDTO> {
|
||||
@Override
|
||||
protected CompetitorDTO getResult() {
|
||||
Color color;
|
||||
try {
|
||||
color = new RGBColor(displayColorTextBox.getText());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
color = new InvalidColor(iae);
|
||||
if (displayColorTextBox.getText() == null || displayColorTextBox.getText().isEmpty()) {
|
||||
color = null;
|
||||
} else {
|
||||
try {
|
||||
color = new RGBColor(displayColorTextBox.getText());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
color = new InvalidColor(iae);
|
||||
}
|
||||
}
|
||||
CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color,
|
||||
/* twoLetterIsoCountryCode */ null,
|
||||
|
||||
+1
@@ -373,6 +373,7 @@ public interface StringMessages extends Messages {
|
||||
String scoringSchemeLowPointSystem();
|
||||
String scoringSchemeHighPointSystem();
|
||||
String scoringSchemeLowPointWinnerGetsZero();
|
||||
String scoringSchemeWinnerGetsSix();
|
||||
String scoringSystem();
|
||||
String createFlexibleLeaderboard();
|
||||
String createRegattaLeaderboard();
|
||||
|
||||
+1
@@ -402,6 +402,7 @@ scoringSchemeHighPointSystem=High Point System
|
||||
scoringSchemeHighPointEssOverall=High Point System ESS Overall
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
|
||||
scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points
|
||||
scoringSystem=Scoring system
|
||||
createFlexibleLeaderboard=Create flexible leaderboard
|
||||
createRegattaLeaderboard=Create regatta leaderboard
|
||||
|
||||
+1
@@ -403,6 +403,7 @@ scoringSchemeHighPointSystem=High Point System
|
||||
scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte
|
||||
scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte
|
||||
scoringSystem=Bepunktungssystem
|
||||
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
||||
createRegattaLeaderboard=Rangliste für Regatta anlegen
|
||||
|
||||
+2
@@ -20,6 +20,8 @@ public class ScoringSchemeTypeFormatter {
|
||||
return stringMessages.scoringSchemeHighPointFirstGetsTen();
|
||||
case LOW_POINT_WINNER_GETS_ZERO:
|
||||
return stringMessages.scoringSchemeLowPointWinnerGetsZero();
|
||||
case HIGH_POINT_WINNER_GETS_SIX:
|
||||
return stringMessages.scoringSchemeWinnerGetsSix();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+22
-4
@@ -1216,6 +1216,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
(newestEvent != null && timePoint.before(newestEvent))); i++) {
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(null, timePoint);
|
||||
if (averagedWindWithConfidence != null) {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Found averaged wind: " + averagedWindWithConfidence);
|
||||
}
|
||||
double confidence = averagedWindWithConfidence.getConfidence();
|
||||
WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint);
|
||||
windDTO.confidence = confidence;
|
||||
@@ -1226,6 +1229,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
if (confidence > maxWindConfidence) {
|
||||
maxWindConfidence = confidence;
|
||||
}
|
||||
} else {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName());
|
||||
}
|
||||
}
|
||||
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth);
|
||||
}
|
||||
@@ -1353,6 +1360,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
while (fixIter.hasNext()) {
|
||||
GPSFixMoving fix = fixIter.next();
|
||||
if (fix.getTimePoint().compareTo(toTimePointExcluding) < 0) {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest(""+competitor.getName()+": " + fix);
|
||||
}
|
||||
fixes.add(fix);
|
||||
} else {
|
||||
break;
|
||||
@@ -1367,7 +1377,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
Position estimatedPosition = track.getEstimatedPosition(middle, extrapolate);
|
||||
SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(middle);
|
||||
if (estimatedPosition != null && estimatedSpeed != null) {
|
||||
fixes.add(new GPSFixMovingImpl(estimatedPosition, middle, estimatedSpeed));
|
||||
GPSFixMoving estimatedFix = new GPSFixMovingImpl(estimatedPosition, middle, estimatedSpeed);
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest(""+competitor.getName()+": " + estimatedFix+" (estimated)");
|
||||
}
|
||||
fixes.add(estimatedFix);
|
||||
}
|
||||
}
|
||||
Iterator<GPSFixMoving> fixIter = fixes.iterator();
|
||||
@@ -2853,13 +2867,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
@Override
|
||||
public void updateEvent(UUID eventId, String eventName, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, List<String> regattaNames) {
|
||||
getService().apply(new UpdateEvent(eventId, eventName, new MillisecondsTimePoint(startDate), new MillisecondsTimePoint(endDate), venue.getName(), isPublic, regattaNames));
|
||||
TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null;
|
||||
TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null;
|
||||
getService().apply(new UpdateEvent(eventId, eventName, startTimePoint, endTimePoint, venue.getName(), isPublic, regattaNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventDTO createEvent(String eventName, Date startDate, Date endDate, String venue, boolean isPublic, List<String> courseAreaNames) {
|
||||
UUID eventUuid = UUID.randomUUID();
|
||||
getService().apply(new CreateEvent(eventName, new MillisecondsTimePoint(startDate), new MillisecondsTimePoint(endDate), venue, isPublic, eventUuid));
|
||||
TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null;
|
||||
TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null;
|
||||
getService().apply(new CreateEvent(eventName, startTimePoint, endTimePoint, venue, isPublic, eventUuid));
|
||||
for (String courseAreaName : courseAreaNames) {
|
||||
createCourseArea(eventUuid, courseAreaName);
|
||||
}
|
||||
@@ -3957,4 +3975,4 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -6,7 +6,6 @@ import java.util.Set;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.base.Person;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.impl.DynamicPerson;
|
||||
import com.sap.sailing.domain.base.impl.DynamicTeam;
|
||||
@@ -32,7 +31,7 @@ public class TeamJsonDeserializer implements JsonDeserializer<DynamicTeam> {
|
||||
public DynamicTeam deserialize(JSONObject object)
|
||||
throws JsonDeserializationException {
|
||||
String name = (String) object.get(TeamJsonSerializer.FIELD_NAME);
|
||||
Person coach = personDeserializer.deserialize((JSONObject) object
|
||||
DynamicPerson coach = personDeserializer.deserialize((JSONObject) object
|
||||
.get(TeamJsonSerializer.FIELD_COACH));
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ public class LeaderboardJsonGetServlet extends AbstractJsonHttpServlet implement
|
||||
} catch (ParseHttpParameterException e1) {
|
||||
}
|
||||
boolean useCache = true; // default
|
||||
if(useCacheParam != null && "false".equalsIgnoreCase(useCacheParam)) {
|
||||
if (useCacheParam != null && "false".equalsIgnoreCase(useCacheParam)) {
|
||||
useCache = false;
|
||||
}
|
||||
if (leaderboardName == null) {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.sap.sailing.server.gateway.jaxrs;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import com.sun.jersey.spi.container.ContainerRequest;
|
||||
import com.sun.jersey.spi.container.ContainerResponse;
|
||||
import com.sun.jersey.spi.container.ContainerResponseFilter;
|
||||
|
||||
@Provider
|
||||
public class AccessControlResponseFilter implements ContainerResponseFilter {
|
||||
|
||||
@Override
|
||||
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
|
||||
MultivaluedMap<String, Object> httpHeaders = response.getHttpHeaders();
|
||||
httpHeaders.add("Access-Control-Allow-Origin", "*");
|
||||
httpHeaders.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type");
|
||||
httpHeaders.add("Access-Control-Expose-Headers", "Location, Content-Disposition");
|
||||
httpHeaders.add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, HEAD, OPTIONS");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+30
-3
@@ -1,14 +1,19 @@
|
||||
package com.sap.sailing.server.gateway.jaxrs.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.EventBase;
|
||||
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
|
||||
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
|
||||
@@ -18,7 +23,11 @@ import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
|
||||
|
||||
@Path("/v1/events")
|
||||
public class EventsResource extends AbstractSailingServerResource {
|
||||
|
||||
|
||||
private Response getBadEventErrorResponse(String eventId) {
|
||||
return Response.status(Status.NOT_FOUND).entity("Could not find an event with id '" + eventId + "'.").type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
public Response getEvents() {
|
||||
@@ -35,9 +44,27 @@ public class EventsResource extends AbstractSailingServerResource {
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
@Path("{eventId}")
|
||||
public Response getEvent(@PathParam("eventId") String eventId) {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
Response response;
|
||||
UUID eventUuid;
|
||||
try {
|
||||
eventUuid = UUID.fromString(eventId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return getBadEventErrorResponse(eventId);
|
||||
}
|
||||
|
||||
Event event = getService().getEvent(eventUuid);
|
||||
if (event == null) {
|
||||
response = getBadEventErrorResponse(eventId);
|
||||
} else {
|
||||
JsonSerializer<EventBase> eventSerializer = new EventJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()));
|
||||
JSONObject eventJson = eventSerializer.serialize(event);
|
||||
|
||||
String json = eventJson.toJSONString();
|
||||
response = Response.ok(json, MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
@Path("{eventId}/racestates")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../fontface.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0 Beta</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0 Beta</h1>
|
||||
<h2>URL: /api/v1/events/{eventId}</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>The service gets the details of an sailing event</p>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Request method:</td>
|
||||
<td>GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/events/41de25fd-0f22-4200-bdac-5574e7d5225d">http://www.sapsailing.com/sailingserver/api/v1/events/41de25fd-0f22-4200-bdac-5574e7d5225d</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,6 +36,10 @@ name to make sure you get to the event you're interested in.
|
||||
<td><a href="eventsGetDoc.html">/api/v1/events</a></td>
|
||||
<td>Gets all events</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="eventGetDoc.html">/api/v1/events/{name}</a></td>
|
||||
<td>Gets the details of an event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="regattasGetDoc.html">/api/v1/regattas</a></td>
|
||||
<td>Gets all regattas</td>
|
||||
@@ -53,7 +57,7 @@ name to make sure you get to the event you're interested in.
|
||||
<td>Gets the bearing of the first leg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="raceMarkPassingsGetDoc.html">/api/v1/regattas/{regattaname}/races/{racename}/markpassings/a></td>
|
||||
<td><a href="raceMarkPassingsGetDoc.html">/api/v1/regattas/{regattaname}/races/{racename}/markpassings</a></td>
|
||||
<td>Gets all mark passings of a race, by competitor and by waypoint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/regattas/Kieler Woche (5o5)">http://www.sapsailing.com/sailingserver/api/v1/regatta/Kieler Woche (5o5)</a></td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/regattas/Kieler Woche (5o5)">http://www.sapsailing.com/sailingserver/api/v1/regattas/Kieler Woche (5o5)</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
|
||||
+17
-18
@@ -37,7 +37,6 @@ import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Person;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
@@ -193,7 +192,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -204,7 +203,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
@@ -402,7 +401,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -412,7 +411,7 @@ public class MasterDataImportTest {
|
||||
UUID competitor2UUID = UUID.randomUUID();
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Test Mustermann", new NationalityImpl("GER"), new Date(645487200000L), "desc"));
|
||||
Person coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc");
|
||||
DynamicPerson coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc");
|
||||
DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133");
|
||||
CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, team2, boat2);
|
||||
@@ -678,7 +677,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -689,7 +688,7 @@ public class MasterDataImportTest {
|
||||
UUID competitor2UUID = UUID.randomUUID();
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Test Mustermann", new NationalityImpl("GER"), new Date(645487200000L), "desc"));
|
||||
Person coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc");
|
||||
DynamicPerson coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc");
|
||||
DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133");
|
||||
Competitor competitor2 = sourceDomainFactory.getCompetitorStore().getOrCreateCompetitor(competitor2UUID,
|
||||
@@ -833,7 +832,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -844,7 +843,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
@@ -1029,7 +1028,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -1040,7 +1039,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
@@ -1127,7 +1126,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailorsToOverride = new HashSet<DynamicPerson>();
|
||||
sailorsToOverride.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(
|
||||
645487200000L), "Oberhoschy"));
|
||||
Person coachToOverride = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(
|
||||
DynamicPerson coachToOverride = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(
|
||||
645487200000L), "Der Lennart halt");
|
||||
DynamicTeam teamToOverride = new TeamImpl("Pros", sailorsToOverride, coachToOverride);
|
||||
BoatClass boatClassToOverride = new BoatClassImpl("H16", true);
|
||||
@@ -1298,7 +1297,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -1309,7 +1308,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
@@ -1431,7 +1430,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -1442,7 +1441,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
@@ -1638,7 +1637,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
Person coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
@@ -1649,7 +1648,7 @@ public class MasterDataImportTest {
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
Person coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
|
||||
+3
-1
@@ -29,7 +29,9 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperation<Compet
|
||||
|
||||
@Override
|
||||
public Competitor internalApplyTo(RacingEventService toState) throws Exception {
|
||||
return toState.getBaseDomainFactory().getCompetitorStore().updateCompetitor(idAsString, newName, newDisplayColor, newSailId, newNationality);
|
||||
Competitor result = toState.getBaseDomainFactory().getCompetitorStore()
|
||||
.updateCompetitor(idAsString, newName, newDisplayColor, newSailId, newNationality);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -69,6 +69,16 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Act 3</b></td>
|
||||
<td><b>Qingdao</b></td>
|
||||
<td><b>30th April - 4th May</b></td>
|
||||
<td>
|
||||
<a target='blank' class='btn-cta' href='http://ess40-2014.sapsailing.com/gwt/Leaderboard.html?name=ESS%202014%20Qingdao%20%28Extreme40%29&showRaceDetails=true&displayName=Muscat&leaderboardGroupName=Extreme%20Sailing%20Series%202014&root=leaderboardGroupPanel'>
|
||||
<span>Leaderboard</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,16 +20,18 @@
|
||||
<h1 id='pageHeadline'>Extreme Sailing Series 2014</h1>
|
||||
<h2>Next Event</h2>
|
||||
<p>
|
||||
Act 2 - Muscat, Oman
|
||||
Act 3 - Qingdao, Oman
|
||||
</p>
|
||||
<p>
|
||||
March 19-22
|
||||
April 30-May 4
|
||||
</p>
|
||||
</div>
|
||||
<div id='navigation'>
|
||||
<a class='navButton overall' href='http://ess40-2014.sapsailing.com/gwt/Leaderboard.html?name=Extreme%20Sailing%20Series%202014%20Overall&showRaceDetails=false&refreshIntervalMillis=10000&displayName=Overall%20Standings&leaderboardGroupName=Extreme%20Sailing%20Series%202014&root=leaderboardGroupPanel'>
|
||||
Overall Leaderboard
|
||||
</a>
|
||||
<a class='navButton event' href='http://ess40-2014.sapsailing.com/gwt/Leaderboard.html?name=ESS%202014%20Qingdao%20%28Extreme40%29&displayName=Muscat&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'>
|
||||
Qingdao
|
||||
<a class='navButton event' href='http://ess40-2014.sapsailing.com/gwt/Leaderboard.html?name=ESS%202014%20Muscat%20%28Extreme40%29&displayName=Muscat&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'>
|
||||
Muscat
|
||||
</a>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
Extreme Sailing Series 2014
|
||||
</li>
|
||||
<li class='raceLocation'>
|
||||
Muscat, Oman
|
||||
Qingdao, China
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
@@ -95,7 +95,30 @@
|
||||
<a href='http://www.extremesailingseries.com' target='_blank'>Official website</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class='raceSection'>
|
||||
|
||||
<div class='raceSection'>
|
||||
<h5 class='date'>
|
||||
2013 Season
|
||||
</h5>
|
||||
<div class='raceTimepoint'></div>
|
||||
<a href='http://bundesliga2013.sapsailing.com' target='_blank'>
|
||||
<ul class='racePanel'>
|
||||
<li class='raceName'>
|
||||
German Sailing League / Bundesliga 2013
|
||||
</li>
|
||||
<li class='raceLocation'>
|
||||
Germany
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
<div class='raceDescription'>
|
||||
<p>German Sailing League / Bundesliga 2013</p>
|
||||
<a href='http://bundesliga2013.sapsailing.com' target='_blank'>SAP Sailing Analytics</a><br/>
|
||||
<a href='http://segelbundesliga.de' target='_blank'>Official website</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='raceSection'>
|
||||
<h5 class='date'>
|
||||
Sep 19-22
|
||||
</h5>
|
||||
@@ -115,7 +138,7 @@
|
||||
<a href='http://505idm2013.sapsailing.com' target='_blank'>SAP Sailing Analytics</a><br/>
|
||||
<a href='http://505idm2013.de' target='_blank'>Official website</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='raceSection'>
|
||||
<h5 class='date'>
|
||||
July 19 - 28
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
<h2 class="articleSubheadline">May 2014</h2>
|
||||
|
||||
<ul class="bulletList">
|
||||
<li>A hint regarding the "distance to start line" metrics: please keep in mind that currently this
|
||||
measurement is calculated as the distance of the GPS tracker from the line. Unless the tracker
|
||||
is mounted near the bow (or at the bow sprit, if any), the distance calculated this way will
|
||||
be greater than what counts for crossing the line. Ordering, comparing and aggregating over this
|
||||
figure still makes sense as long as you remember this detail.
|
||||
</li>
|
||||
<li>Introduced a new leaderboard entry "XTE +/-" featuring a signed cross-track error which shows
|
||||
for a leg and also aggregated for an entire race on which side of the leg a competitor stayed on
|
||||
average. Currently, the "side" is defined as viewing in the leg's direction, so "left" for a downwind
|
||||
|
||||
@@ -389,6 +389,8 @@ public class LeaderboardData extends ExportAction {
|
||||
}
|
||||
Distance averageCrossTrackError = race.getAverageAbsoluteCrossTrackError(competitor, race.getStartOfRace(), race.getEndOfRace(), /*upwindOnly*/ false, /*waitForLatestAnalysis*/ false);
|
||||
addNamedElementWithValue(competitorRaceDataElement, "average_cross_track_error_in_meters", averageCrossTrackError != null ? averageCrossTrackError.getMeters() : -1.0);
|
||||
Distance averageSignedCrossTrackError = race.getAverageSignedCrossTrackError(competitor, race.getStartOfRace(), race.getEndOfRace(), /*upwindOnly*/ false, /*waitForLatestAnalysis*/ false);
|
||||
addNamedElementWithValue(competitorRaceDataElement, "average_signed_cross_track_error_in_meters", averageSignedCrossTrackError != null ? averageSignedCrossTrackError.getMeters() : -1.0);
|
||||
competitorElement.addContent(competitorRaceDataElement);
|
||||
raceElement.addContent(competitorElement);
|
||||
}
|
||||
@@ -553,6 +555,8 @@ public class LeaderboardData extends ExportAction {
|
||||
|
||||
Distance averageCrossTrackError = competitorLeg.getAverageAbsoluteCrossTrackError(legFinishTime, /*waitForLatestAnalysis*/ false);
|
||||
addNamedElementWithValue(competitorLegDataElement, "average_cross_track_error_in_meters", averageCrossTrackError != null ? averageCrossTrackError.getMeters() : -1);
|
||||
Distance averageSignedCrossTrackError = competitorLeg.getAverageSignedCrossTrackError(legFinishTime, /*waitForLatestAnalysis*/ false);
|
||||
addNamedElementWithValue(competitorLegDataElement, "average_signed_cross_track_error_in_meters", averageSignedCrossTrackError != null ? averageSignedCrossTrackError.getMeters() : -1);
|
||||
|
||||
competitorElement.addContent(competitorLegDataElement);
|
||||
legElement.addContent(competitorElement);
|
||||
@@ -607,6 +611,10 @@ public class LeaderboardData extends ExportAction {
|
||||
messages.add("Competitor " + competitorLeg.getCompetitor().getName() + " has no average cross track error for this leg!");
|
||||
simpleConfidence -= 0.02;
|
||||
}
|
||||
if (competitorLeg.getAverageSignedCrossTrackError(now, /*waitForLatestAnalysis*/ false) == null) {
|
||||
messages.add("Competitor " + competitorLeg.getCompetitor().getName() + " has no average signed cross track error for this leg!");
|
||||
simpleConfidence -= 0.02;
|
||||
}
|
||||
if (competitorLeg.getStartTime() != null && competitorLeg.getStartTime().before(leg.getTrackedRace().getStartOfRace())) {
|
||||
if ((leg.getTrackedRace().getStartOfRace().asMillis()-competitorLeg.getStartTime().asMillis())>=MAX_EARLY_START_DIFFERENCE) {
|
||||
messages.add("Competitor " + competitorLeg.getCompetitor().getName() + " has a start time of " + competitorLeg.getStartTime() + " that is 10 seconds or more before start of race time (" + leg.getTrackedRace().getStartOfRace() + ").");
|
||||
|
||||
+23
-1
@@ -198,4 +198,26 @@ Assume that you want to know which threads exactly eat up all the CPU time. This
|
||||
|
||||
<pre>
|
||||
/opt/jdk1.7.0_02/bin/jconsole -pluginpath topthreads-1.1.jar
|
||||
</pre>
|
||||
</pre>
|
||||
|
||||
### Activate logging of GPS fixes
|
||||
|
||||
It can happen that you want to log every GPS fix added to a track. If you want to see these then add the following to your logging.properties file. NEVER use it in production.
|
||||
|
||||
<pre>
|
||||
com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl.level=FINEST
|
||||
</pre>
|
||||
|
||||
If you want to log all GPS fixes sent to the client (RaceBoard) then activate the following:
|
||||
|
||||
<pre>
|
||||
com.sap.sailing.gwt.ui.server.SailingServiceImpl.level=FINEST
|
||||
</pre>
|
||||
|
||||
### Remove GWT SerializationException messages from log
|
||||
|
||||
When installing a new version of server code that contains changes related to GWT it can happen that viewers that did not reload their view after the server restart will trigger a SerializationException because the information needed to serialize is no longer matching the one on the server. GWT will start throwing loads of SerializationException that are filling up your logs quite quick. As these messages can be considered blather it is good to disable them to be able to see really relevant messages. Disabling is not that easy as Jetty is using it's own logging system. But there is a trick. Just add the following to your VM argument list (ADDITIONAL_JAVA_ARGS) and you're good to enjoy a life without these messages.
|
||||
|
||||
<pre>
|
||||
-Dorg.eclipse.jetty.LEVEL=OFF -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
</pre>
|
||||
@@ -1,14 +1,14 @@
|
||||
# RaceLog Tracking
|
||||
RaceLog-tracking allows fully self-sustained tracking with the Sailing Analytics, with no need for an additional tracking provider. Instead, any source of positional data (e.g. smartphones, providers such as Igtimi without race-management) can be integrated.
|
||||
RaceLog-tracking allows fully self-sustained tracking with the Sailing Analytics, with no need for an additional tracking provider. Instead, any source of positional data (e.g. smartphones, providers such as Igtimi without race management) can be integrated.
|
||||
|
||||
The term itself should be revised at some point, and currently refers to the technical implementation on how master-data related to a race (e.g. competitor registrations, course layout) is recorded and persisted: namely in a RaceLog.
|
||||
The term itself should be revised at some point, and currently refers to the technical implementation on how master data related to a race (e.g. competitor registrations, course layout) is recorded and stored persistently: namely in a RaceLog.
|
||||
|
||||
This is the second iteration of implementing this idea. For previous documentation, see [[smartphone tracking|wiki/smartphone-tracking]].
|
||||
|
||||
[[_TOC_]]
|
||||
|
||||
# Development Infos
|
||||
Currently, RaceLog-tracking has not been merged into the master branch. Instead, refer to the following branches:
|
||||
Currently, RaceLog tracking has not been merged into the master branch. Instead, refer to the following branches:
|
||||
* ``gpsfixstore-independent-of-race``: The main RaceLog-tracking development
|
||||
* ``ftes-rltracking-equestrian``: Serves as a reference implementation of a tracking adapter built for RaceLog-tracking.
|
||||
|
||||
@@ -26,14 +26,14 @@ Not all identifiers represent actual class or bundle names. For a more technical
|
||||
# AdminConsole Components
|
||||
Currently, RaceLogs can be filled with the race metadata via the _RaceLog Tracking_ panel in the AdminConsole. Here one can denote RaceColumn/Fleet combinations within RegattaLeaderboards for RaceLog-tracking, register competitors, define the course layout, add device mappings, and finally start tracking.
|
||||
|
||||
When thinking about smartphone tracking, it would of course be a good idea to also integrate similar functionality into the tracking app. Some parts of the communication (registering competitors, defining the course, mapping decies) can be handled via the existing RaceLog communication mechanism. Other parts (creating the Leaderboard structure in the first place, adding the RaceLog tracker) have to be dealt with separately, e.g. via a REST API.
|
||||
When thinking about smartphone tracking, it would of course be a good idea to also integrate similar functionality into the tracking app. Some parts of the communication (registering competitors, defining the course, mapping devices) can be handled via the existing RaceLog communication mechanism. Other parts (creating the Leaderboard structure in the first place, adding the RaceLog tracker) have to be dealt with separately, e.g. via a REST API.
|
||||
|
||||
# ToDos
|
||||
## Archiving old Races
|
||||
Since RaceLog-tracking allows fully independent tracking, storing and reloading of races, it may be a good foundation for archiving old races. Currently, we rely on tracking providers to retain all data indefinitely, so that it can be reloaded e.g. after restarting the server. Also see [[bug 2|http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2]] for more details.
|
||||
Since RaceLog-tracking allows for fully independent tracking, storing and reloading of races, it may be a good foundation for archiving old races. Currently, we rely on tracking providers to retain all data indefinitely, so that it can be reloaded, e.g., after restarting the server. Also see [[bug 2|http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2]] for more details.
|
||||
|
||||
## Smartphone Tracking
|
||||
RaceLog-tracking provides the server-side abilities for independent tracking. Therefore, using smartphones for tracking is one possible application. So far, there is no implementation in terms of a smartphone app. The relation to the existing RaceCommittee app has be be considered, as both should share a common code base, and as there should be certain points of integration (e.g. first defining the race in the tracking app, then setting the start time in the RC-app, and finally tracking one boat with that smartphone - again with the tracking app).
|
||||
RaceLog-tracking provides the server-side abilities for independent tracking. Therefore, using smartphones for tracking is one possible application. So far, there is no implementation in terms of a smartphone app. The relation to the existing RaceCommittee app has be be considered, as both should share a common code base, and as there should be certain points of integration (e.g., first defining the race in the tracking app, then setting the start time in the RC-app, and finally tracking one boat with that smartphone - again with the tracking app).
|
||||
|
||||
The first option is to simply share code through a Android library module, the second option is integrating everything into one app with two launchers (one for tracking, one for the RaceCommittee functionality).
|
||||
|
||||
|
||||
Reference in new issue
Block a user