diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java new file mode 100755 index 00000000000..cc86744e89b --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/BoatChangeListener.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.base; + +public interface BoatChangeListener { + void sailIdChanged(String oldSailId, String newSailId); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java index b078bf8162c..0a0d508cdc2 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java @@ -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); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java new file mode 100755 index 00000000000..f5a0b722976 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java @@ -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); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java new file mode 100755 index 00000000000..04171c3b454 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/NationalityChangeListener.java @@ -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); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java index a16c72f28e1..1d9fc3bbb25 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/BoatImpl.java @@ -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 listeners; public BoatImpl(String name, BoatClass boatClass, String sailID) { super(name); this.boatClass = boatClass; this.sailID = sailID; + this.listeners = new HashSet(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); } @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 getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index 38ed945fc75..3426e4d22ac 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -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 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(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); } @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 getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java index 0b4e8f1e278..b3131fc9e4c 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicBoat.java @@ -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); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java index d5d13c3b45c..231192564e6 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicPerson.java @@ -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); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java index 807b159842a..fb0e14ba7a2 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicTeam.java @@ -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); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java index 48115fd27f7..87297b31df3 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/PersonImpl.java @@ -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 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(); } + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + listeners = new HashSet(); + } + @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 getListeners() { + synchronized (listeners) { + return new HashSet(listeners); + } + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java index d9332f79763..f2bb400b44e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java @@ -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 sailors; - private final Person coach; + private final DynamicPerson coach; + private final NationalityChangeListener personNationalityChangeForwarder; + private transient Set listeners; - public TeamImpl(String name, Iterable sailors, Person coach) { + public TeamImpl(String name, Iterable sailors, DynamicPerson coach) { super(name); + listeners = new HashSet(); 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(); + setTransitiveListeners(); + } + + public void addNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public void removeNationalityChangeListener(NationalityChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private Iterable getNationalityChangeListeners() { + synchronized (listeners) { + return new HashSet(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); + } } } - + } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java new file mode 100755 index 00000000000..08741bc72b1 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java @@ -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(); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 4e92d3e1f1c..a9db6d4d6a9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -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 scoreCorrectionListeners; - private final NamedReentrantReadWriteLock scoreCorrectionListenersLock; + private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock; + private final WeakHashMap competitorChangeListeners; private final WeakHashMap>> invalidationListenersPerLeaderboard; private final WeakHashMap 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 observedCompetitors; + + /** + * Registers this listener as listener for all of the leaderboard'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 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>>(); + this.invalidationListenersPerLeaderboard = new WeakHashMap<>(); this.raceColumnListeners = new WeakHashMap(); - this.scoreCorrectionListeners = new WeakHashMap(); - 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); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java index 6a54a72d5ba..95e1b281fc7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java @@ -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) { + } }; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 184c2746ac1..726746f055b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -132,10 +132,14 @@ public class CompetitorEditDialog extends DataEntryDialog { @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, diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/TeamJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/TeamJsonDeserializer.java index afd30db343b..ed8d5f5ceb1 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/TeamJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/TeamJsonDeserializer.java @@ -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 { 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 sailors = new HashSet(); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index f7b7969fdde..d972a09c1a6 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailorsToOverride = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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 sailors = new HashSet(); 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 sailors2 = new HashSet(); 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"); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java index 01c236ff0d8..4938413b5bb 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java @@ -29,7 +29,9 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperationOfficial website -
+ +
+
+ 2013 Season +
+
+ +
    +
  • + German Sailing League / Bundesliga 2013 +
  • +
  • + Germany +
  • +
+
+
+

German Sailing League / Bundesliga 2013

+ SAP Sailing Analytics
+ Official website +
+
+ +
Sep 19-22
@@ -115,7 +138,7 @@ SAP Sailing Analytics
Official website
-
+
July 19 - 28 diff --git a/java/com.sap.sailing.www/release_notes_analyze.html b/java/com.sap.sailing.www/release_notes_analyze.html index 10a4ba85a29..b81d97c70a0 100644 --- a/java/com.sap.sailing.www/release_notes_analyze.html +++ b/java/com.sap.sailing.www/release_notes_analyze.html @@ -38,6 +38,12 @@

May 2014

    +
  • 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. +
  • 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