Merge remote-tracking branch 'server/master'

This commit is contained in:
Axel Uhl committed 2013-06-11 15:55:42 +02:00
commit 22f66dadea
64 files changed
+1831 -669

No files matched your search

@@ -9,6 +9,7 @@ Bundle-ActivationPolicy: lazy
Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.filter,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.media,
com.sap.sailing.domain.common.quadtree,
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sailing.domain.common.racelog,
@@ -1,15 +1,16 @@
package com.sap.sailing.gwt.ui.client.shared.media;
package com.sap.sailing.domain.common.media;
import java.io.Serializable;
import java.util.Date;
import com.google.gwt.user.client.rpc.IsSerializable;
/**
* See http://my.opera.com/core/blog/2010/03/03/everything-you-need-to-know-about-html5-video-and-audio-2
* @author D047974
*
*/
public class MediaTrack implements IsSerializable {
public class MediaTrack implements Serializable {
private static final long serialVersionUID = 1L;
public enum MimeType {
@@ -56,13 +57,6 @@ public class MediaTrack implements IsSerializable {
}
}
public class MediaSection implements IsSerializable {
public String title;
public long offsetInMillis;
public Date startTime;
public Date endTime;
}
public String dbId;
public String title;
public String url;
@@ -72,11 +66,17 @@ public class MediaTrack implements IsSerializable {
public Status status = Status.UNDEFINED;
public MediaTrack() {
super();
}
public MediaTrack(String title, String url, Date startTime, int durationInMillis, MimeType mimeType) {
this.title = title;
this.url = url;
this.startTime = startTime;
this.durationInMillis = durationInMillis;
this.mimeType = mimeType;
}
public MediaTrack(String dbId, String title, String url, Date startTime, int durationInMillis, MimeType mimeType) {
super();
this.dbId = dbId;
this.title = title;
this.url = url;
@@ -88,6 +88,14 @@ public class MediaTrack implements IsSerializable {
public String toString() {
return title + " - " + url + " [" + typeToString() + ']' + startTime + " [" + durationInMillis + status + ']';
}
public Date deriveEndTime() {
if (startTime != null) {
return new Date(startTime.getTime() + durationInMillis);
} else {
return null;
}
}
public String typeToString() {
return mimeType == null ? "undefined" : mimeType.toString();
@@ -96,5 +104,35 @@ public class MediaTrack implements IsSerializable {
public boolean isYoutube() {
return (mimeType != null) && MediaSubType.youtube.equals(mimeType.mediaSubType);
}
/**
* Checks for overlap of this start time and duration with the given startTime and endTime, excluding boundaries!
* Behaviour for given endTime being earlier than given startTime is not specified.
* endTime being null represents "open end". Open beginning is not allow, though!
* @param startTime Must not be null.
* @param endTime May be null representing "open end".
*/
public boolean overlapsWith(Date startTime, Date endTime) {
if (this.startTime == null) {
return false;
} else {
return this.deriveEndTime().getTime() > startTime.getTime() && (endTime == null || this.startTime.getTime() < endTime.getTime());
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MediaTrack) {
MediaTrack mediaTrack = (MediaTrack) obj;
return this.dbId == null? mediaTrack.dbId == null : this.dbId.equals(mediaTrack.dbId);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.dbId == null ? 0 : this.dbId.hashCode();
}
}
@@ -0,0 +1,27 @@
package com.sap.sailing.domain.common.media;
import java.util.Date;
public class MediaUtil {
public static int compareDatesAllowingNull(Date date1, Date date2) {
if (date1 == null) {
return date2 == null ? 0 : -1;
} else if (date2 == null) {
return 1;
} else {
return date1.compareTo(date2);
}
}
public static boolean equalsDatesAllowingNull(Date date1, Date date2) {
if (date1 == null) {
return date2 == null;
} else if (date2 == null) {
return false;
} else {
return date1.equals(date2);
}
}
}
@@ -1,6 +1,6 @@
package com.sap.sailing.domain.persistence.media;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -14,11 +14,9 @@ public interface MediaDB {
String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType);
DBMediaTrack loadMediaTrack(String dbId);
List<DBMediaTrack> loadAllMediaTracks();
void deleteMediaTrack(String title);
void deleteMediaTrack(String dbId);
void updateTitle(String dbId, String title);
@@ -28,6 +26,34 @@ public interface MediaDB {
void updateDuration(String dbId, int durationInMillis);
Collection<DBMediaTrack> queryOverlappingMediaTracks(Date rangeStart, Date rangeEnd);
MediaDB TEST_STUB = new MediaDB() {
@Override
public String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType) {
return "0";
}
@Override
public List<DBMediaTrack> loadAllMediaTracks() {
return Collections.emptyList();
}
@Override
public void deleteMediaTrack(String dbId) {}
@Override
public void updateTitle(String dbId, String title) {}
@Override
public void updateUrl(String dbId, String url) {}
@Override
public void updateStartTime(String dbId, Date startTime) {}
@Override
public void updateDuration(String dbId, int durationInMillis) {}
};
}
@@ -6,8 +6,6 @@ import com.sap.sailing.mongodb.MongoDBService;
public interface MediaDBFactory {
MediaDBFactory INSTANCE = new MediaDBFactoryImpl();
MediaObjectFactory getDefaultDomainObjectFactory();
MediaObjectFactory getDomainObjectFactory(MongoDBService mongoDBService);
MediaDB getDefaultMediaDB();
MediaDB getMediaDB(MongoDBService mongoDBService);
}
@@ -1,11 +0,0 @@
package com.sap.sailing.domain.persistence.media;
/**
* Offers methods to load media objects from a Mongo DB
*
* @author Jens Rommel (d047974)
*
*/
public interface MediaObjectFactory {
}
@@ -1,50 +0,0 @@
package com.sap.sailing.domain.persistence.media.impl;
/**
* Defines literals providing the names for MongoDB collections. The literal documentation described the semantics
* of the collection identified by that literal.
*
* @author Axel Uhl (D043530)
*
*/
public enum CollectionNames {
/**
* Stores the wind fixes recorded from persistent wind sources.
*/
WIND_TRACKS,
/**
* Stores the leaderboards with their names, score corrections, competitor display name overrides, race
* columns and their fleets as well as the per-fleet tracked race assignments.
*/
LEADERBOARDS,
/**
* Stores the leaderboard group configurations with references to the {@link #LEADERBOARDS} collection
*/
LEADERBOARD_GROUPS,
/**
* Top-level event information about events such as Kiel Week 2011, or IDM Travem�nde 2011, including name and
* course areas.
*/
EVENTS,
/**
* Stores regatta definitions including their series layout and fleets and race columns. Regattas can reference
* the event from the {@link #EVENTS} collection to which they belong.
*/
REGATTAS,
/**
* Stores boat class-specific master data such as the class's hull length, logo, name, number of sailors, etc.
* To be implemented in future versions.
*/
BOAT_CLASSES,
/**
* Stores the mapping of {@link RaceDefinition#getId race IDs} to regatta names for automatic re-association
* when tracking races again without explicitly specifying a regatta.
*/
REGATTA_FOR_RACE_ID;
}
@@ -2,29 +2,16 @@ package com.sap.sailing.domain.persistence.media.impl;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.persistence.media.MediaObjectFactory;
import com.sap.sailing.mongodb.MongoDBService;
public class MediaDBFactoryImpl implements MediaDBFactory {
private final MediaObjectFactory defaultMediaObjectFactory;
private final MediaDB defaultMediaDB;
public MediaDBFactoryImpl() {
super();
this.defaultMediaObjectFactory = new MediaObjectFactoryImpl(MongoDBService.INSTANCE.getDB());
this.defaultMediaDB = new MediaDBImpl(MongoDBService.INSTANCE.getDB());
}
@Override
public MediaObjectFactory getDefaultDomainObjectFactory() {
return defaultMediaObjectFactory;
}
@Override
public MediaObjectFactory getDomainObjectFactory(MongoDBService mongoDBService) {
return new MediaObjectFactoryImpl(mongoDBService.getDB());
}
@Override
public MediaDB getDefaultMediaDB() {
return defaultMediaDB;
@@ -1,8 +1,6 @@
package com.sap.sailing.domain.persistence.media.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -13,7 +11,6 @@ import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
import com.sap.sailing.domain.persistence.media.MediaDB;
@@ -63,23 +60,6 @@ public class MediaDBImpl implements MediaDB {
}
}
@Override
public DBMediaTrack loadMediaTrack(String dbId) {
ObjectId objectId = new ObjectId(dbId);
DBCursor cursor = getVideoCollection().find(new BasicDBObject(DbNames.Fields._id.name(), objectId));
switch (cursor.count()) {
case 0:
throw new RuntimeException("Video not found with id: " + dbId);
case 1:
DBObject dbObject = cursor.next();
DBMediaTrack dbMediaTrack = createMediaObjectFromDB(dbObject);
return dbMediaTrack;
default:
throw new RuntimeException("Ambiguous videos not found with id: " + dbId);
}
}
private DBMediaTrack createMediaObjectFromDB(DBObject dbObject) {
String dbId = ((ObjectId) dbObject.get(DbNames.Fields._id.name())).toString();
String title = (String) dbObject.get(DbNames.Fields.MEDIA_TITLE.name());
@@ -152,34 +132,4 @@ public class MediaDBImpl implements MediaDB {
getVideoCollection().update(updateQuery, update);
}
@Override
public Collection<DBMediaTrack> queryOverlappingMediaTracks(Date rangeStart, Date rangeEnd) {
if ((rangeStart != null) && (rangeEnd != null)) {
BasicDBObject startTimeCondition = new BasicDBObject();
startTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$gt", rangeStart));
startTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$lt", rangeEnd));
BasicDBObject endTimeCondition = new BasicDBObject();
// Should actually be "AND greater than rangeStart - duration".
// However, using values calculated on server side turns out to be a nightmare with mongodb.
// Instead do the remaining filtering on client side...
endTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$lt", rangeStart));
DBObject query = QueryBuilder.start().or(startTimeCondition, endTimeCondition).get();
DBCursor cursor = getVideoCollection().find(query).sort(sortByStartTimeAndTitle);
List<DBMediaTrack> result = new ArrayList<>(cursor.count());
while (cursor.hasNext()) {
DBMediaTrack resultCandidate = createMediaObjectFromDB(cursor.next());
if (resultCandidate.durationInMillis > rangeStart.getTime() - resultCandidate.startTime.getTime()) {
result.add(resultCandidate);
}
}
return result;
} else {
return Collections.emptyList();
}
}
}
@@ -1,17 +0,0 @@
package com.sap.sailing.domain.persistence.media.impl;
import com.mongodb.DB;
import com.sap.sailing.domain.persistence.media.MediaObjectFactory;
public class MediaObjectFactoryImpl implements MediaObjectFactory {
// private static final Logger logger = Logger.getLogger(MediaObjectFactoryImpl.class.getName());
// private final DB database;
public MediaObjectFactoryImpl(DB db) {
super();
// this.database = db;
}
}
@@ -1,39 +0,0 @@
package com.sap.sailing.domain.persistence.media.impl;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class MongoUtils {
/**
* Key names in MongoDB must not start with a $ sign and must not contain a "." character. This method escapes
* those. {@link #unescapeDollarAndDot} is the inverse function.
*
* @param key
* a non-<code>null</code> string which may have length 0
* @return a string that can be used as a key in a MongoDB key-value pair and that can be decoded again using
* {@link #unescapeDollarAndDot(String)}.
*/
public static String escapeDollarAndDot(String key) {
String result = key.replace("%", "%25").replace("+", "%2B");
if (result.length() > 0 && result.charAt(0) == '$') {
result = "%24"+result.substring(1);
}
return result.replace(".", "%2E");
}
/**
* @see #escapeDollarAndDot(String)
* @param key
* a non-<code>null</code> string which may have length 0 and that was returned by
* {@link #escapeDollarAndDot(String)}.
* @return the string originally passed to {@link #escapeDollarAndDot(String)}
*/
public static String unescapeDollarAndDot(String escapedKey) {
try {
return URLDecoder.decode(escapedKey, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("How come this VM doesn't know UTF-8?");
}
}
}
@@ -15,7 +15,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.common,
com.sap.sailing.domain.persistence,
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.domain.swisstimingadapter.persistence;bundle-version="1.0.0"
com.sap.sailing.domain.swisstimingadapter.persistence;bundle-version="1.0.0",
org.hamcrest.core;bundle-version="1.1.0"
Bundle-ClassPath: resources/,
.
Import-Package: com.sap.sailing.server,
@@ -0,0 +1,75 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.domain.swisstimingadapter.RaceType;
import com.sap.sailing.domain.swisstimingadapter.RaceType.OlympicRaceCode;
import static org.junit.Assert.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
public class DomainFactoryTest {
private com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl swissTimingDomainFactory;
@Before
public void before() {
com.sap.sailing.domain.base.DomainFactory baseDomainFactory = new com.sap.sailing.domain.base.impl.DomainFactoryImpl();
swissTimingDomainFactory = new com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl(
baseDomainFactory);
}
@Test
public void testGetFinnBoatClass() throws Exception {
String raceID = "SAM002";
RaceType raceType = swissTimingDomainFactory.getRaceTypeFromRaceID(raceID );
assertThat(raceType, is(notNullValue()));
assertThat(raceType.getRaceCode(), is(OlympicRaceCode.FINN));
}
@Test
public void testGetFinnBoatClass_LowerCase() throws Exception {
String raceID = "sam002";
RaceType raceType = swissTimingDomainFactory.getRaceTypeFromRaceID(raceID );
assertThat(raceType, is(notNullValue()));
assertThat(raceType.getRaceCode(), is(OlympicRaceCode.FINN));
}
@Test
public void testGet470WomenBoatClass() throws Exception {
String raceID = "SAW005";
RaceType raceType = swissTimingDomainFactory.getRaceTypeFromRaceID(raceID );
assertThat(raceType, is(notNullValue()));
assertThat(raceType.getRaceCode(), is(OlympicRaceCode._470_WOMEN));
}
@Test
public void testGet470WomenBoatClass2() throws Exception {
String raceID = "SAW005_xxx";
RaceType raceType = swissTimingDomainFactory.getRaceTypeFromRaceID(raceID );
assertThat(raceType, is(notNullValue()));
assertThat(raceType.getRaceCode(), is(OlympicRaceCode._470_WOMEN));
}
@Test
public void testDontGetWrongBoatClass() throws Exception {
String raceID = "ABC";
RaceType raceType = swissTimingDomainFactory.getRaceTypeFromRaceID(raceID );
assertThat(raceType, is(notNullValue()));
assertThat(raceType.getRaceCode(), is(OlympicRaceCode.UNKNOWN));
}
@Ignore
@Test
public void testCompetitorLookup() throws Exception {
before();
// swissTimingDomainFactory.getOrCreateCompetitor(boatID, threeLetterIOCCode, name, boatClass);
}
}
@@ -1,10 +1,5 @@
package com.sap.sailing.domain.swisstimingadapter.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -35,6 +30,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.swisstimingadapter.MessageType;
import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector;
@@ -59,6 +55,11 @@ import com.sap.sailing.mongodb.MongoDBService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class EndToEndListeningStoreAndFowardTest {
private static final Logger logger = Logger.getLogger(EndToEndListeningStoreAndFowardTest.class.getName());
@@ -117,7 +118,7 @@ public class EndToEndListeningStoreAndFowardTest {
final com.sap.sailing.domain.base.impl.DomainFactoryImpl baseDomainFactory = new com.sap.sailing.domain.base.impl.DomainFactoryImpl();
racingEventService = new RacingEventServiceImpl(mongoDBService, SwissTimingFactory.INSTANCE,
new DomainFactoryImpl(baseDomainFactory),
new com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl(baseDomainFactory));
new com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl(baseDomainFactory), MediaDB.TEST_STUB);
raceHandles = new ArrayList<RacesHandle>();
}
@@ -2,8 +2,6 @@ package com.sap.sailing.domain.swisstimingadapter;
import java.util.List;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Course;
@@ -31,18 +29,20 @@ public interface DomainFactory {
Nationality getOrCreateNationality(String threeLetterIOCCode);
Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, BoatClass boatClass);
Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, RaceType raceType);
Competitor getOrCreateCompetitor(String boatID, String threeLetterIOCCode, String name, BoatClass boatClass);
Competitor getOrCreateCompetitor(String boatID, String threeLetterIOCCode, String name, RaceType raceType);
Competitor getCompetitorByBoatIDAndRaceType(String boatID, RaceType raceType);
Competitor getCompetitorByBoatIDAndRaceID(String boatID, String raceID);
RaceDefinition createRaceDefinition(Regatta regatta, Race race, StartList startList, com.sap.sailing.domain.swisstimingadapter.Course course);
com.sap.sailing.domain.base.Mark getOrCreateMark(String trackerID);
GPSFixMoving createGPSFix(TimePoint timePointOfTransmission, Fix fix);
Competitor getCompetitorByBoatIDAndBoatClass(String boatID, BoatClass boatClass);
void updateCourseWaypoints(Course courseToUpdate, Iterable<Mark> marks) throws PatchFailedException;
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint waypoint, Competitor competitor);
@@ -54,10 +54,11 @@ public interface DomainFactory {
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, WindStore windStore,
RaceSpecificMessageLoader messageLoader);
BoatClass getOrCreateBoatClassFromRaceID(String raceID);
RaceType getRaceTypeFromRaceID(String raceID);
ControlPoint getOrCreateControlPoint(Iterable<String> devices);
RaceDefinition createRaceDefinition(Regatta regatta, String raceID, Iterable<Competitor> competitors,
List<ControlPoint> courseDefinition);
}
@@ -0,0 +1,61 @@
package com.sap.sailing.domain.swisstimingadapter;
import com.sap.sailing.domain.base.BoatClass;
public interface RaceType {
public enum OlympicRaceCode {
// SAM102000 Men's Windsurfer = Windsufer M�nner RS:X
// SAW102000 Women's Windsurfer = Windsurfer Damen RS:X
// SAM004000 Men's One Person Dinghy = Laser M�nner
// SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
// SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
// SAM005000 Men's Two Person Dinghy = 470er M�nner
// SAW005000 Women's Two Person Dinghy = 470er Damen
// SAM009000 Men's Skiff = 49er M�nner
// SAM007000 Men's Keelboat = Starboot M�nner
// SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
UNKNOWN("Unspecified", "Unspecified", Gender.unspecified),
FINN("SAM002", "Finn", Gender.male),
LASER("SAM004", "Laser", Gender.male),
_470_WOMEN("SAW005", "470", Gender.female),
_470_MEN("SAM005", "470", Gender.male),
STAR("SAM007", "Star", Gender.male),
_49ER("SAM009", "49er", Gender.male),
ELLIOTT6M("SAW007", "Elliott 6M", Gender.female),
RSX_WOMEN("SAW102", "RS:X", Gender.female),
RSX_MEN("SAM102", "RS:X", Gender.male),
LASER_RADIAL("SAW103", "Laser Radial", Gender.female),
_49ERFX_TODO("xxxxx_TODO_xxxxx", "49er FX", Gender.female),
NACRA17_TOTO("xxxxx_TODO_xxxxx", "Nacra 17", Gender.mixed);
enum Gender {
male, female, mixed, unspecified;
}
public final String swissTimingCode;
public final String boatClassName;
public final Gender gender;
public final boolean typicallyStartsUpwind;
private OlympicRaceCode(String swissTimingCode, String boatClassName, Gender gender) {
this.swissTimingCode = swissTimingCode;
this.boatClassName = boatClassName;
this.gender = gender;
this.typicallyStartsUpwind = true;
}
}
OlympicRaceCode getRaceCode();
BoatClass getBoatClass();
}
@@ -34,4 +34,8 @@ public class CompetitorImpl implements Competitor {
return name+"/"+boatID;
}
public String getID() {
return null;
}
}
@@ -42,6 +42,8 @@ import com.sap.sailing.domain.swisstimingadapter.MessageType;
import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
import com.sap.sailing.domain.swisstimingadapter.StartList;
import com.sap.sailing.domain.swisstimingadapter.RaceType;
import com.sap.sailing.domain.swisstimingadapter.RaceType.OlympicRaceCode;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -64,36 +66,23 @@ public class DomainFactoryImpl implements DomainFactory {
private final static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
private final Map<String, Regatta> raceIDToRegattaCache;
private final Map<Iterable<String>, ControlPoint> controlPointCache;
private final Map<String, BoatClass> olympicClassesByID;
private final BoatClass unknownBoatClass;
private final Map<String, RaceType> raceTypeByID;
private final RaceType unknownRaceType;
private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
public DomainFactoryImpl(com.sap.sailing.domain.base.DomainFactory baseDomainFactory) {
this.baseDomainFactory = baseDomainFactory;
raceIDToRegattaCache = new HashMap<String, Regatta>();
controlPointCache = new HashMap<Iterable<String>, ControlPoint>();
olympicClassesByID = new HashMap<String, BoatClass>();
/*
SAM102000 Men's Windsurfer = Windsufer M�nner RS:X
SAW102000 Women's Windsurfer = Windsurfer Damen RS:X
SAM004000 Men's One Person Dinghy = Laser M�nner
SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy M�nner
SAM005000 Men's Two Person Dinghy = 470er M�nner
SAW005000 Women's Two Person Dinghy = 470er Damen
SAM009000 Men's Skiff = 49er M�nner
SAM007000 Men's Keelboat = Starboot M�nner
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
*/
olympicClassesByID.put("102", baseDomainFactory.getOrCreateBoatClass("RS:X", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("004", baseDomainFactory.getOrCreateBoatClass("Laser", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("103", baseDomainFactory.getOrCreateBoatClass("Laser Radial", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("002", baseDomainFactory.getOrCreateBoatClass("Finn", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("005", baseDomainFactory.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("009", baseDomainFactory.getOrCreateBoatClass("49er", /* alwaysStartsUpwind */ true));
olympicClassesByID.put("007", baseDomainFactory.getOrCreateBoatClass("Star", /* typicallyStartsUpwind */ true));
olympicClassesByID.put("010", baseDomainFactory.getOrCreateBoatClass("Elliott 6M", /* typicallyStartsUpwind */ true));
unknownBoatClass = baseDomainFactory.getOrCreateBoatClass("Unknown", /* typicallyStartsUpwind */ true);
raceTypeByID = new HashMap<String, RaceType>();
for (OlympicRaceCode olympicRaceCode : OlympicRaceCode.values()) {
raceTypeByID.put(
olympicRaceCode.swissTimingCode,
new RaceTypeImpl(olympicRaceCode, baseDomainFactory.getOrCreateBoatClass(
olympicRaceCode.boatClassName, olympicRaceCode.typicallyStartsUpwind)));
}
unknownRaceType = new RaceTypeImpl(OlympicRaceCode.UNKNOWN, baseDomainFactory.getOrCreateBoatClass("Unknown", OlympicRaceCode.UNKNOWN.typicallyStartsUpwind));
}
@Override
@@ -108,7 +97,7 @@ public class DomainFactoryImpl implements DomainFactory {
result = raceIDToRegattaCache.get(raceID);
}
if (result == null) {
result = new RegattaImpl(raceLogStore, raceID, getOrCreateBoatClassFromRaceID(raceID), trackedRegattaRegistry,
result = new RegattaImpl(raceLogStore, raceID, getRaceTypeFromRaceID(raceID).getBoatClass(), trackedRegattaRegistry,
getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), raceID, null);
logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+")");
raceIDToRegattaCache.put(raceID, result);
@@ -117,26 +106,36 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public Competitor getCompetitorByBoatIDAndBoatClass(String boatID, BoatClass boatClass) {
return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, boatClass));
public Competitor getCompetitorByBoatIDAndRaceType(String boatID, RaceType raceType) {
return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, raceType));
}
private String getCompetitorID(String boatID, BoatClass boatClass) {
return boatID + "/" + boatClass.getName();
@Override
public Competitor getCompetitorByBoatIDAndRaceID(String boatID, String raceID) {
RaceType raceType = getRaceTypeFromRaceID(raceID);
if (raceType != null) {
return getCompetitorByBoatIDAndRaceType(boatID, raceType);
} else {
return null;
}
}
private String getCompetitorID(String boatID, RaceType raceType) {
return boatID + "/" + raceType.getRaceCode();
}
@Override
public Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, BoatClass boatClass) {
Competitor result = getCompetitorByBoatIDAndBoatClass(competitor.getBoatID(), boatClass);
public Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, RaceType raceType) {
Competitor result = getCompetitorByBoatIDAndRaceType(competitor.getBoatID(), raceType);
if (result == null) {
Boat boat = new BoatImpl(competitor.getName(), boatClass, competitor.getBoatID());
Boat boat = new BoatImpl(competitor.getName(), raceType.getBoatClass(), competitor.getBoatID());
List<Person> teamMembers = new ArrayList<Person>();
for (String teamMemberName : competitor.getName().split("[-+&]")) {
teamMembers.add(new PersonImpl(teamMemberName.trim(), getOrCreateNationality(competitor.getThreeLetterIOCCode()),
/* dateOfBirth */ null, teamMemberName.trim()));
}
Team team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null);
result = baseDomainFactory.getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), boatClass),
result = baseDomainFactory.getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceType),
competitor.getName(), team, boat);
}
return result;
@@ -144,8 +143,8 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public Competitor getOrCreateCompetitor(String boatID, String threeLetterIOCCode, String name,
BoatClass boatClass) {
return getOrCreateCompetitor(new CompetitorImpl(boatID, threeLetterIOCCode, name), boatClass);
RaceType raceType) {
return getOrCreateCompetitor(new CompetitorImpl(boatID, threeLetterIOCCode, name), raceType);
}
@Override
@@ -157,7 +156,7 @@ public class DomainFactoryImpl implements DomainFactory {
waypoints.add(waypoint);
}
com.sap.sailing.domain.base.Course domainCourse = new CourseImpl("Course", waypoints);
BoatClass boatClass = getOrCreateBoatClassFromRaceID(raceID);
BoatClass boatClass = getRaceTypeFromRaceID(raceID).getBoatClass();
logger.info("Creating RaceDefinitionImpl for race "+raceID);
RaceDefinition result = new RaceDefinitionImpl(regatta.getId().toString()+"/"+raceID, domainCourse, boatClass, competitors);
regatta.addRace(result);
@@ -167,47 +166,36 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceDefinition createRaceDefinition(Regatta regatta, Race race, StartList startList, Course course) {
com.sap.sailing.domain.base.Course domainCourse = createCourse(race.getDescription(), course);
BoatClass boatClass = getOrCreateBoatClassFromRaceID(race.getRaceID());
Iterable<Competitor> competitors = createCompetitorList(startList, boatClass);
RaceType raceType = getRaceTypeFromRaceID(race.getRaceID());
Iterable<Competitor> competitors = createCompetitorList(startList, raceType);
logger.info("Creating RaceDefinitionImpl for race "+race.getRaceID());
RaceDefinition result = new RaceDefinitionImpl(race.getRaceID(), domainCourse,
boatClass, competitors);
raceType.getBoatClass(), competitors);
regatta.addRace(result);
return result;
}
/**
* Returns the SwissTiming olympic race type or race type "UNKNOWN" when no corresponding olympic race type can be found.
* Will never return null neither throw a content-related exception.
*/
@Override
public BoatClass getOrCreateBoatClassFromRaceID(String raceID) {
BoatClass result;
/*
SAM102000 Men's Windsurfer = Windsurfer Maenner RS:X
SAW102000 Women's Windsurfer = Windsurfer Damen RS:X
SAM004000 Men's One Person Dinghy = Laser Maenner
SAW103000 Women's One Person Dinghy = Laser Damen Laser Radial
SAM002000 Men's One Person Dinghy Heavy = Finn Dinghy Maenner
SAM005000 Men's Two Person Dinghy = 470er Maenner
SAW005000 Women's Two Person Dinghy = 470er Damen
SAM009000 Men's Skiff = 49er Maenner
SAM007000 Men's Keelboat = Starboot Maenner
SAW010000 Women's Match Racing = Matchrace Damen Elliott 6M (modified)
*/
if (raceID.toUpperCase().startsWith("SA") && raceID.length() == 9) {
String classID = raceID.substring(3, 6);
result = olympicClassesByID.get(classID);
if (result == null) {
// unknown code
result = unknownBoatClass;
public RaceType getRaceTypeFromRaceID(String raceID) {
if (raceID != null && raceID.length() >= 6) {
String swissTimingRaceCode = raceID.substring(0, 6).toUpperCase();
RaceType result = raceTypeByID.get(swissTimingRaceCode);
if (result != null) {
return result;
}
} else {
result = unknownBoatClass;
}
return result;
//else
return unknownRaceType;
}
private Iterable<Competitor> createCompetitorList(StartList startList, BoatClass boatClass) {
private Iterable<Competitor> createCompetitorList(StartList startList, RaceType raceType) {
List<Competitor> result = new ArrayList<Competitor>();
for (com.sap.sailing.domain.swisstimingadapter.Competitor swissTimingCompetitor : startList.getCompetitors()) {
Competitor domainCompetitor = getOrCreateCompetitor(swissTimingCompetitor, boatClass);
Competitor domainCompetitor = getOrCreateCompetitor(swissTimingCompetitor, raceType);
result.add(domainCompetitor);
}
return result;
@@ -0,0 +1,27 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.swisstimingadapter.RaceType;
public class RaceTypeImpl implements RaceType {
private final OlympicRaceCode raceCode;
private final BoatClass boatClass;
public RaceTypeImpl(OlympicRaceCode raceCode, BoatClass boatClass) {
this.raceCode = raceCode;
this.boatClass = boatClass;
}
@Override
public final OlympicRaceCode getRaceCode() {
return raceCode;
}
@Override
public BoatClass getBoatClass() {
return boatClass;
}
}
@@ -13,7 +13,6 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
@@ -232,8 +231,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
trackedRace.recordFix(mark, gpsFix);
break;
case COMPETITOR:
Competitor competitor = domainFactory.getCompetitorByBoatIDAndBoatClass(
fix.getBoatID(), domainFactory.getOrCreateBoatClassFromRaceID(raceID));
Competitor competitor = domainFactory.getCompetitorByBoatIDAndRaceID(
fix.getBoatID(), raceID);
DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack = trackedRace.getTrack(competitor);
competitorTrack.addGPSFix(gpsFix);
break;
@@ -258,9 +257,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
List<Triple<Integer, Integer, Long>> markIndicesRanksAndTimesSinceStartInMilliseconds) {
assert this.raceID.equals(raceID);
if (isTrackedRaceStillReachable()) {
final BoatClass boatClass = domainFactory.getOrCreateBoatClassFromRaceID(raceID);
Competitor competitor = domainFactory.getCompetitorByBoatIDAndBoatClass(boatID,
boatClass);
Competitor competitor = domainFactory.getCompetitorByBoatIDAndRaceID(boatID,
raceID);
// the list of mark indices and time stamps is partial and usually only shows the last mark passing;
// we need to use this to *update* the competitor's mark passings list, not *replace* it
TreeMap<Integer, MarkPassing> markPassingsByMarkIndex = new TreeMap<Integer, MarkPassing>();
@@ -288,7 +286,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
MillisecondsTimePoint timePoint = new MillisecondsTimePoint(
startTime.asMillis() + markIndexRankAndTimeSinceStartInMilliseconds.getC());
MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint,
domainFactory.getCompetitorByBoatIDAndBoatClass(boatID, boatClass));
domainFactory.getCompetitorByBoatIDAndRaceID(boatID, raceID));
markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), markPassing);
} else {
//
@@ -101,8 +101,8 @@ public class TMDMessageQueue {
// otherwise, this could lead to the "guessed time" to
while (i.hasNext()) {
TMDMessageContents messageContents = i.next();
Competitor competitor = raceTracker.getDomainFactory().getCompetitorByBoatIDAndBoatClass(messageContents.getBoatID(),
trackedRace.getRace().getBoatClass());
Competitor competitor = raceTracker.getDomainFactory().getCompetitorByBoatIDAndRaceID(messageContents.getBoatID(),
messageContents.getRaceID());
NavigableSet<MarkPassing> oldMarkPassings = trackedRace.getMarkPassings(competitor);
TreeMap<Integer, MarkPassing> cleansedMarkPassingsForCompetitor = cleansedMarkPassings.get(competitor);
if (cleansedMarkPassingsForCompetitor == null) {
@@ -211,7 +211,8 @@ public class SwissTimingReplayAdapterServiceTest {
@Test
@Ignore
public void printRaceData_SAM009903_20120731_sapsailing_replay() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM009903.20120731_sapsailing.replay"),
new SwissTimingReplayParserImpl().readData(
getClass().getResourceAsStream("/SAM009903.20120731_sapsailing.replay"),
new SwissTimingReplayPrintListener());
}
@@ -225,19 +226,36 @@ public class SwissTimingReplayAdapterServiceTest {
@Test
@Ignore
public void printReadRaceData_SAW010955_20120802() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW010955.20120802.replay"), new SwissTimingReplayPrintListener());
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW010955.20120802.replay"),
new SwissTimingReplayPrintListener());
}
@Test
@Ignore
public void printReadRaceData_SAM002901() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM002901.replay"), new SwissTimingReplayPrintListener());
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM002901.replay"),
new SwissTimingReplayPrintListener());
}
@Test
@Ignore
public void printReadRaceData_SAM009904_20120731() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM009904.20120731.replay"), new SwissTimingReplayPrintListener());
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM009904.20120731.replay"),
new SwissTimingReplayPrintListener());
}
@Test
@Ignore
public void printRaceData_SAW102101_20120807() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW102101.20120807.replay"),
new SwissTimingReplayPrintListener());
}
@Test
@Ignore
public void printRaceData_SAM102101_20120807() throws Exception {
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAM102101.20120807.replay"),
new SwissTimingReplayPrintListener());
}
}
@@ -15,7 +15,6 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Course;
@@ -39,6 +38,7 @@ import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
import com.sap.sailing.domain.swisstimingadapter.RaceType;
import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus;
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener;
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayParser;
@@ -83,6 +83,8 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
*/
private String currentRaceID;
private RaceType currentRaceType;
/**
* Reference time point for time specifications
*/
@@ -158,10 +160,6 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
return trackedRacePerRaceID.values();
}
private BoatClass getCurrentBoatClass() {
return domainFactory.getOrCreateBoatClassFromRaceID(currentRaceID);
}
@Override
public void referenceTimestamp(long referenceTimestampMillis) {
referenceTimePoint = new MillisecondsTimePoint(referenceTimestampMillis);
@@ -175,6 +173,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
@Override
public void raceID(String raceID) {
currentRaceID = raceID;
currentRaceType = domainFactory.getRaceTypeFromRaceID(currentRaceID);
}
private boolean isValid(int threeByteValue) {
@@ -236,7 +235,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
short ctPoints_x10_Winner) {
if (boatType == BoatType.Competitor) {
Competitor competitor = domainFactory.getOrCreateCompetitor(sailNumberOrTrackerID, threeLetterIOCCode.trim(), name.trim(),
getCurrentBoatClass());
currentRaceType);
Set<Competitor> competitorsOfCurrentRace = competitorsPerRaceID.get(currentRaceID);
if (competitorsOfCurrentRace == null) {
competitorsOfCurrentRace = new HashSet<>();
@@ -0,0 +1,133 @@
package com.sap.sailing.domain.common;
import java.util.Date;
import org.junit.Test;
import com.sap.sailing.domain.common.media.MediaTrack;
import static org.junit.Assert.*;
public class MediaTrackTest {
@Test
public void testExactOverlap() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime());
Date endTime = new Date(mediaTrack.deriveEndTime().getTime());
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testNoOverlapLeft() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime() + 2);
Date endTime = new Date(startTime.getTime() + 1);
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testNoOverlapRight() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime() - 2);
Date endTime = new Date(startTime.getTime() + 1);
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testPartialOverlapLeft() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 2;
Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
Date endTime = new Date(startTime.getTime() + 2);
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testPartialOverlapRight() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 2;
Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
Date endTime = new Date(startTime.getTime() + 2);
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testMediaFullyIncluded() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
Date endTime = new Date(startTime.getTime() + 3);
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testMediaFullyIncluding() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 3;
Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
Date endTime = new Date(startTime.getTime() + 1);
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testOverlapOpenEndStartingEarlier() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
Date endTime = null; //--> open end
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testOverlapOpenEndStartingLater() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 2;
Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
Date endTime = null; //--> open end
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
}
@Test
public void testOpenEndNoOverlap() throws Exception {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 1;
Date startTime = new Date(mediaTrack.startTime.getTime() + 2);
Date endTime = null; //--> open end
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
}
}
@@ -0,0 +1,84 @@
package com.sap.sailing.domain.common;
import java.util.Date;
import org.junit.Test;
import com.sap.sailing.domain.common.media.MediaUtil;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MediaUtilTest {
@Test
public void testCompareDatesAllNull() throws Exception {
Date date1 = null;
Date date2 = null;
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) == 0);
}
@Test
public void testCompareDatesFirstNull() throws Exception {
Date date1 = null;
Date date2 = new Date();
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) < 0);
}
@Test
public void testCompareDatesSecondNull() throws Exception {
Date date1 = new Date();
Date date2 = null;
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) > 0);
}
@Test
public void testCompareDatesFirstGreaterSecond() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() - 1);
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) > 0);
}
@Test
public void testCompareDatesSecondGreaterFirst() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() + 1);
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) < 0);
}
@Test
public void testEqualsDatesAllNull() throws Exception {
Date date1 = null;
Date date2 = null;
assertTrue(MediaUtil.equalsDatesAllowingNull(date1, date2));
}
@Test
public void testEqualsDatesFirstNull() throws Exception {
Date date1 = null;
Date date2 = new Date();
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
}
@Test
public void testEqualsDatesSecondNull() throws Exception {
Date date1 = new Date();
Date date2 = null;
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
}
@Test
public void testEqualsDatesBothEqual() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime());
assertTrue(MediaUtil.equalsDatesAllowingNull(date1, date2));
}
@Test
public void testEqualsDatesNotEqual() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() + 1);
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
}
}
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TcGVjdGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVXNlck1hbmFnZW1lbnQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVHZWaWV3PC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlZpZGVvUG9wdXA8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuWW91dHViZVBvcHVwPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlBvbGFyU2hlZXRzPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJlZ2F0dGFPdmVydmlldzwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TaW11bGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TcGVjdGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVXNlck1hbmFnZW1lbnQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVHZWaWV3PC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlZpZGVvUG9wdXA8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuWW91dHViZVBvcHVwPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlBvbGFyU2hlZXRzPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJlZ2F0dGFPdmVydmlldzwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TaW11bGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
@@ -1,43 +1,42 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Sailing App GWT UI
Bundle-SymbolicName: com.sap.sailing.gwt.ui
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.server.gateway,
com.sap.sailing.server,
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.mongodb,
com.mongodb.driver;bundle-version="2.6.2",
com.sap.sailing.domain.swisstimingadapter,
com.sap.sailing.domain.persistence,
com.sap.sailing.domain.swisstimingadapter.persistence,
com.sap.sailing.domain.tractracadapter.persistence,
com.google.gwt.osgi;bundle-version="2.4.0",
com.sap.sailing.domain.common,
com.sap.sailing.resultimport,
org.moxieapps.gwt.highcharts;bundle-version="1.5.0",
com.sap.sailing.geocoding,
com.sap.sailing.server.replication,
com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.freg.resultimport,
com.sap.sailing.domain.swisstimingreplayadapter,
com.sap.sailing.simulator;bundle-version="1.0.0"
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
Export-Package: com.sap.sailing.gwt.ui.client.shared.media
Import-Package: javax.jms;version="1.1.0",
javax.servlet;version="2.5.0",
javax.servlet.http;version="2.5.0",
org.json.simple,
org.json.simple.parser,
org.osgi.framework,
org.osgi.util.tracker
Web-ContextPath: /gwt
Bundle-ClassPath: WEB-INF/classes/,
WEB-INF/lib/gwt-maps.jar,
WEB-INF/lib/gwt-servlet.jar
TrackLifecycle: true
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Sailing App GWT UI
Bundle-SymbolicName: com.sap.sailing.gwt.ui
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.server.gateway,
com.sap.sailing.server,
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.mongodb,
com.mongodb.driver;bundle-version="2.6.2",
com.sap.sailing.domain.swisstimingadapter,
com.sap.sailing.domain.persistence,
com.sap.sailing.domain.swisstimingadapter.persistence,
com.sap.sailing.domain.tractracadapter.persistence,
com.google.gwt.osgi;bundle-version="2.4.0",
com.sap.sailing.domain.common,
com.sap.sailing.resultimport,
org.moxieapps.gwt.highcharts;bundle-version="1.5.0",
com.sap.sailing.geocoding,
com.sap.sailing.server.replication,
com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.freg.resultimport,
com.sap.sailing.domain.swisstimingreplayadapter,
com.sap.sailing.simulator;bundle-version="1.0.0"
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
Import-Package: javax.jms;version="1.1.0",
javax.servlet;version="2.5.0",
javax.servlet.http;version="2.5.0",
org.json.simple,
org.json.simple.parser,
org.osgi.framework,
org.osgi.util.tracker
Web-ContextPath: /gwt
Bundle-ClassPath: WEB-INF/classes/,
WEB-INF/lib/gwt-maps.jar,
WEB-INF/lib/gwt-servlet.jar
TrackLifecycle: true
@@ -21,8 +21,9 @@ import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaUtil;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -218,13 +219,22 @@ public class MediaPanel extends FlowPanel {
startTimeColumn.setSortable(true);
sortHandler.setComparator(startTimeColumn, new Comparator<MediaTrack>() {
public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) {
return mediaTrack1.startTime.compareTo(mediaTrack2.startTime);
return MediaUtil.compareDatesAllowingNull(mediaTrack1.startTime, mediaTrack2.startTime);
}
});
startTimeColumn.setFieldUpdater(new FieldUpdater<MediaTrack, String>() {
public void update(int index, MediaTrack mediaTrack, String newStartTime) {
// Called when the user changes the value.
mediaTrack.startTime = TimeFormatUtil.DATETIME_FORMAT.parse(newStartTime);
newStartTime = newStartTime.trim();
if ("".equals(newStartTime)) {
mediaTrack.startTime = null;
} else {
try {
mediaTrack.startTime = TimeFormatUtil.DATETIME_FORMAT.parse(newStartTime);
} catch (IllegalArgumentException e) {
errorReporter.reportError(stringMessages.mediaDateFormatError(TimeFormatUtil.DATETIME_FORMAT.toString()));
}
}
mediaService.updateStartTime(mediaTrack, new AsyncCallback<Void>() {
@Override
@@ -12,10 +12,10 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.MimeType;
public class MediaTrackDialog extends DataEntryDialog<MediaTrack> {
@@ -10,10 +10,10 @@ import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.MimeType;
public class MediaTrackYoutubeDialog extends DataEntryDialog<MediaTrack> {
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.client;
import java.util.Date;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public interface MediaPlayer {
@@ -5,7 +5,7 @@ import java.util.Collection;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
@RemoteServiceRelativePath("media")
public interface MediaService extends RemoteService {
@@ -4,7 +4,7 @@ import java.util.Collection;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public interface MediaServiceAsync {
@@ -586,6 +586,7 @@ public interface StringMessages extends Messages {
String loading();
String showHiddenRaces();
String selectedCompetitors();
String mediaDateFormatError(String expectedTimeFormat);
String filterNothing();
String topNCompetitorsByRaceRank(int n);
String topNCompetitorsByTotalRank(int n);
@@ -588,6 +588,7 @@ enableRaceFilter=Enable filter
raceStatusColumn=Race Status
loading=Loading (on a slow internet connection this can take some minutes)...
showHiddenRaces=Show HIDDEN races
mediaDateFormatError=Invalid date format. Use: {0}
selectedCompetitors=Selected competitors
filterNothing=Filter nothing
topNCompetitorsByRaceRank=Race Top {0}
@@ -645,4 +646,4 @@ showOnlyCurrentlyRunningRaces=Show currently running races
seriesDefinesResultDiscardingRule=Series defines result discarding rules
addSeries=Add series
regattaDefinesResultDiscardingRules=Regatta defines result discarding rules
startsWithZeroScore=Series starts with zero score
startsWithZeroScore=Series starts with zero score
@@ -2,8 +2,8 @@ package com.sap.sailing.gwt.ui.raceboard;
import java.util.Date;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.MediaPlayer;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
public abstract class AbstractMediaPlayer implements MediaPlayer {
@@ -3,7 +3,7 @@ package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.dom.client.AudioElement;
import com.google.gwt.dom.client.MediaElement;
import com.google.gwt.media.client.Audio;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public class AudioPlayer extends AbstractMediaPlayer {
@@ -18,7 +18,7 @@ import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public class MediaSelectionDialog implements CloseHandler<PopupPanel> {
@@ -25,6 +25,9 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MediaType;
import com.sap.sailing.domain.common.media.MediaTrack.Status;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.MediaPlayer;
import com.sap.sailing.gwt.ui.client.PlayStateListener;
@@ -32,9 +35,6 @@ import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
import com.sap.sailing.gwt.ui.client.TimeListener;
import com.sap.sailing.gwt.ui.client.Timer.PlayModes;
import com.sap.sailing.gwt.ui.client.Timer.PlayStates;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.MediaType;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.Status;
import com.sap.sailing.gwt.ui.raceboard.MediaSelectionDialog.MediaSelectionListener;
import com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer.PopupCloseListener;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
@@ -143,10 +143,10 @@ public class MediaSelector implements RaceTimesInfoProviderListener, PlayStateLi
native void addLoadMetadataHandler(MediaElement mediaElement, MediaTrack mediaTrack) /*-{
var that = this;
mediaElement.addEventListener('loadedmetadata', function() {
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::loadedmetadata(Lcom/sap/sailing/gwt/ui/client/shared/media/MediaTrack;)(mediaTrack);
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::loadedmetadata(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack);
});
mediaElement.addEventListener('error', function() {
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::mediaError(Lcom/sap/sailing/gwt/ui/client/shared/media/MediaTrack;)(mediaTrack);
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::mediaError(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack);
});
}-*/;
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.Window;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public abstract class PopupWindowPlayer extends AbstractMediaPlayer {
@@ -1,7 +1,7 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.http.client.URL;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public class VideoWindowPlayer extends PopupWindowPlayer {
@@ -1,7 +1,7 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.http.client.URL;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack;
public class YoutubeWindowPlayer extends PopupWindowPlayer {
@@ -2,24 +2,15 @@ package com.sap.sailing.gwt.ui.server;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.MediaService;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.MimeType;
import com.sap.sailing.server.RacingEventService;
public class MediaServiceImpl extends RemoteServiceServlet implements MediaService {
@@ -31,7 +22,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
private ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
private static final long serialVersionUID = -8917349579281305977L;
public MediaServiceImpl() {
super();
BundleContext context = Activator.getDefault();
@@ -40,83 +31,50 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
racingEventServiceTracker.open();
}
private RacingEventService racingEventService() {
return racingEventServiceTracker.getService();
}
@Override
public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
TrackedRace trackedRace = racingEventServiceTracker.getService().getExistingTrackedRace(regattaAndRaceIdentifier);
if (trackedRace != null) {
Date raceStart = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
Date raceEnd = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
return createMediaTracksFromDB(mediaDB().queryOverlappingMediaTracks(raceStart , raceEnd));
} else {
return Collections.emptyList();
}
return racingEventService().getMediaTracksForRace(regattaAndRaceIdentifier);
}
@Override
public Collection<MediaTrack> getAllMediaTracks() {
List<DBMediaTrack> allDBMediaTracks = mediaDB().loadAllMediaTracks();
Collection<MediaTrack> result = createMediaTracksFromDB(allDBMediaTracks);
return result;
// try {
// Date date = dateFormat.parse("2012-09-15 14:42:10 +0100");
// return Arrays
// .asList(new MediaTrack[] { new MediaTrack(
// MediaType.VIDEO,
// "Day 2 Final - 49er Europeans 2012",
// "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv",
// date) });
// } catch (ParseException e) {
// throw new RuntimeException(e); // just due to annoying date parse error potential
// }
}
private Collection<MediaTrack> createMediaTracksFromDB(Collection<DBMediaTrack> allDBMediaTracks) {
Collection<MediaTrack> result = new ArrayList<MediaTrack>();
for (DBMediaTrack dbMediaTrack : allDBMediaTracks) {
MediaTrack mediaTrack = createMediaTrackFromDB(dbMediaTrack);
result.add(mediaTrack );
}
return result;
}
private MediaTrack createMediaTrackFromDB(DBMediaTrack dbMediaTrack) {
MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.valueOf(dbMediaTrack.mimeType) : null;
MediaTrack mediaTrack = new MediaTrack(dbMediaTrack.dbId, dbMediaTrack.title, dbMediaTrack.url, dbMediaTrack.startTime, dbMediaTrack.durationInMillis, mimeType);
return mediaTrack;
return racingEventService().getAllMediaTracks();
}
@Override
public void addMediaTrack(MediaTrack mediaTrack) {
String mimeType = mediaTrack.mimeType != null ? mediaTrack.mimeType.name() : null;
mediaDB().insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime, mediaTrack.durationInMillis, mimeType);
}
private MediaDB mediaDB() {
return MediaDBFactory.INSTANCE.getDefaultMediaDB();
if (mediaTrack.dbId != null) {
throw new IllegalStateException("Property dbId must not be null for newly created media track.");
}
racingEventService().mediaTrackAdded(mediaTrack);
}
@Override
public void deleteMediaTrack(MediaTrack mediaTrack) {
mediaDB().deleteMediaTrack(mediaTrack.dbId);
racingEventService().mediaTrackDeleted(mediaTrack);
}
@Override
public void updateTitle(MediaTrack mediaTrack) {
mediaDB().updateTitle(mediaTrack.dbId, mediaTrack.title);
racingEventService().mediaTrackTitleChanged(mediaTrack);
}
@Override
public void updateUrl(MediaTrack mediaTrack) {
mediaDB().updateUrl(mediaTrack.dbId, mediaTrack.url);
racingEventService().mediaTrackUrlChanged(mediaTrack);
}
@Override
public void updateStartTime(MediaTrack mediaTrack) {
mediaDB().updateStartTime(mediaTrack.dbId, mediaTrack.startTime);
racingEventService().mediaTrackStartTimeChanged(mediaTrack);
}
@Override
public void updateDuration(MediaTrack mediaTrack) {
mediaDB().updateDuration(mediaTrack.dbId, mediaTrack.durationInMillis);
racingEventService().mediaTrackDurationChanged(mediaTrack);
}
}
@@ -1913,7 +1913,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
List<SwissTimingRaceRecordDTO> result = new ArrayList<SwissTimingRaceRecordDTO>();
for (com.sap.sailing.domain.swisstimingadapter.RaceRecord rr : getService().getSwissTimingRaceRecords(hostname, port, canSendRequests)) {
SwissTimingRaceRecordDTO swissTimingRaceRecordDTO = new SwissTimingRaceRecordDTO(rr.getRaceID(), rr.getDescription(), rr.getStartTime());
BoatClass boatClass = com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE.getOrCreateBoatClassFromRaceID(rr.getRaceID());
BoatClass boatClass = com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE.getRaceTypeFromRaceID(rr.getRaceID()).getBoatClass();
swissTimingRaceRecordDTO.boatClass = boatClass != null ? boatClass.getName() : null;
swissTimingRaceRecordDTO.discipline = rr.getRaceID().length() >= 3 ? rr.getRaceID().substring(2, 3) : null;
result.add(swissTimingRaceRecordDTO);
@@ -0,0 +1,105 @@
package com.sap.sailing.media.persistence.test;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.List;
import org.hamcrest.core.Is;
import org.junit.Test;
import com.mongodb.MongoException;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.hamcrest.core.Is.is;
public class MediaDBTest extends AbstractMongoDBTest {
public MediaDBTest() throws UnknownHostException, MongoException {
super();
}
@Test
public void testCreateVideoTrack() {
final String videoTitle = "Test Video";
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
DBMediaTrack videoTrack = mongoDB.loadAllMediaTracks().iterator().next();
assertNotNull(videoTrack);
assertThat(videoTrack.dbId, Is.is(dbId));
assertThat(videoTrack.title, Is.is(videoTitle));
}
@Test
public void testLoadAllVideoTracks() {
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
int testEntryCount = 10;
storeNumberOfTestMediaTracks(mongoDB, testEntryCount);
List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
assertThat(allMediaTracks.size(), is(testEntryCount));
}
private void storeNumberOfTestMediaTracks(MediaDB mongoDB, int count) {
final String videoTitleTemplate = "Test Video ";
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
for (int i = 0; i < count; i++) {
mongoDB.insertMediaTrack(videoTitleTemplate + i, url, date, durationInMillis, mimeType);
}
}
@Test
public void testDeleteVideoTrack() {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
//delete
mongoDB.deleteMediaTrack(dbId);
//assert not exists --> RuntimeException
assertThat(mongoDB.loadAllMediaTracks().size(), is(0));
}
@Test
public void testUpdateStartTime() {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date originalDate = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, originalDate, durationInMillis, mimeType);
//update with new date
Date newDate = new Date(originalDate.getTime() + 1000);
mongoDB.updateStartTime(dbId, newDate);
//assert start time is updated and everything else preserved
DBMediaTrack videoTrack = mongoDB.loadAllMediaTracks().iterator().next();
assertThat(videoTrack.startTime, is(newDate));
assertThat(videoTrack.durationInMillis, is(durationInMillis));
assertThat(videoTrack.title, is(videoTitle));
assertThat(videoTrack.url, is(url));
assertThat(videoTrack.mimeType, is(mimeType));
}
}
@@ -1,223 +0,0 @@
package com.sap.sailing.media.persistence.test;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.hamcrest.core.Is;
import org.junit.Test;
import com.mongodb.MongoException;
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.gwt.ui.client.shared.media.MediaTrack.MimeType;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.hamcrest.core.Is.is;
public class TestMediaDB extends AbstractMongoDBTest {
private static final int FIFTEEN_MINUTES_IN_MILLIS = 15 * 60 * 1000;
private static final int THIRTY_MINUTES_IN_MILLIS = 30 * 60 * 1000;
private static final int ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
// private static final Logger logger = Logger.getLogger(TestCRUDMedia.class.getName());
public TestMediaDB() throws UnknownHostException, MongoException {
super();
}
@Test
public void testCreateVideoTrack() {
final String videoTitle = "Test Video";
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
DBMediaTrack videoTrack = mongoDB.loadMediaTrack(dbId);
assertNotNull(videoTrack);
assertThat(videoTrack.title, Is.is(videoTitle));
}
@Test(expected = RuntimeException.class)
public void testFailReadMissingVideoTrack() {
final String dbId = "non-existing id";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
mongoDB.loadMediaTrack(dbId);
}
@Test
public void testLoadAllVideoTracks() {
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
int testEntryCount = 10;
storeNumberOfTestMediaTracks(mongoDB, testEntryCount);
List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
assertThat(allMediaTracks.size(), is(testEntryCount));
}
private void storeNumberOfTestMediaTracks(MediaDB mongoDB, int count) {
final String videoTitleTemplate = "Test Video ";
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
for (int i = 0; i < count; i++) {
mongoDB.insertMediaTrack(videoTitleTemplate + i, url, date, durationInMillis, mimeType);
}
}
@Test(expected=RuntimeException.class)
public void testDeleteVideoTrack() {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date date = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
//delete
mongoDB.deleteMediaTrack(videoTitle);
//assert not exists --> RuntimeException
mongoDB.loadMediaTrack(dbId);
}
@Test
public void testUpdateStartTime() {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
Date originalDate = new Date();
int durationInMillis = 23;
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, originalDate, durationInMillis, mimeType);
//update with new date
Date newDate = new Date(originalDate.getTime() + 1000);
mongoDB.updateStartTime(dbId, newDate);
//assert start time is updated and everything else preserved
DBMediaTrack videoTrack = mongoDB.loadMediaTrack(dbId);
assertThat(videoTrack.startTime, is(newDate));
assertThat(videoTrack.durationInMillis, is(durationInMillis));
assertThat(videoTrack.title, is(videoTitle));
assertThat(videoTrack.url, is(url));
assertThat(videoTrack.mimeType, is(mimeType));
}
@Test
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackLongerThanEndTime() {
Date startTime = new Date();
int duration = ONE_HOUR_IN_MILLIS;
Date rangeStart = startTime;
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackShorterThanEndTime() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = startTime;
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsBeforeEndsAfterRange() {
Date startTime = new Date();
int duration = ONE_HOUR_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
Date rangeEnd = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsAfterEndsBeforeRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
int duration = FIFTEEN_MINUTES_IN_MILLIS;
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsAfterEndsAfterRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
int duration = THIRTY_MINUTES_IN_MILLIS;
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsBeforeEndsBeforeRange() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_TrackEndsBeforeRange() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + duration + 1);
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_TrackStartsAfterRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
int duration = THIRTY_MINUTES_IN_MILLIS;
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
}
private void assertOverlap(Date startTime, int oneHourInMillis, Date rangeStart, Date rangeEnd) {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
String mimeType = MimeType.ogv.name();
String dbId = mongoDB.insertMediaTrack(videoTitle, url, startTime, oneHourInMillis, mimeType);
Collection<DBMediaTrack> videoTracks = mongoDB.queryOverlappingMediaTracks(rangeStart, rangeEnd);
assertThat(videoTracks.size(), is(1));
assertThat(videoTracks.iterator().next().dbId, is(dbId));
}
private void assertNoOverlap(Date startTime, int oneHourInMillis, Date rangeStart, Date rangeEnd) {
//insert test object
final String videoTitle = "Test Video";
final String url = "test";
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
String mimeType = MimeType.ogv.name();
mongoDB.insertMediaTrack(videoTitle, url, startTime, oneHourInMillis, mimeType);
Collection<DBMediaTrack> videoTracks = mongoDB.queryOverlappingMediaTracks(rangeStart, rangeEnd);
assertThat(videoTracks.size(), is(0));
}
}
@@ -20,4 +20,5 @@ Require-Bundle: com.sap.sailing.server,
com.tractrac.clientmodule;bundle-version="2.0.0",
com.sap.sailing.expeditionconnector,
com.sap.sailing.domain.swisstimingadapter,
org.json.simple;bundle-version="1.1.0"
org.json.simple;bundle-version="1.1.0",
org.hamcrest;bundle-version="1.1.0"
@@ -3,10 +3,13 @@ package com.sap.sailing.server.replication.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.core.Is.is;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@@ -25,6 +28,8 @@ import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.test.TrackBasedTest;
import com.sap.sailing.mongodb.MongoDBService;
@@ -99,6 +104,14 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
assertNotNull(master.getLeaderboardGroupByName(leaderBoardGroupName));
assertNull(replica.getLeaderboardGroupByName(leaderBoardGroupName));
/* Media Library */
MediaTrack mediaTrack1 = new MediaTrack("title-1", "url", new Date(), 1, MimeType.mp4);
master.mediaTrackAdded(mediaTrack1);
MediaTrack mediaTrack2 = new MediaTrack("title-2", "url", new Date(), 1, MimeType.ogv);
master.mediaTrackAdded(mediaTrack2);
MediaTrack mediaTrack3 = new MediaTrack("title-3", "url", new Date(), 1, MimeType.mp4);
master.mediaTrackAdded(mediaTrack3);
/* fire up replication */
performReplicationSetup();
ReplicationMasterDescriptor the_master = replicationDescriptorPair.getB(); /* master descriptor */
@@ -116,6 +129,10 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
assertNotNull(replica.getLeaderboardGroupByName(leaderBoardGroupName));
assertNotNull(replica.getLeaderboardByName(leaderboardName));
assertTrue(replica.getAllRegattas().iterator().hasNext());
//System.out.println("InitialLoadReplicationObjectIdentityTest.testInitialLoad - replica.getAllMediaTracks: " + replica.getAllMediaTracks());
assertThat(replica.getAllMediaTracks().size(), is(3));
}
@Test
@@ -0,0 +1,103 @@
package com.sap.sailing.server.replication.test;
import java.util.Date;
import org.junit.Test;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThat;
import static org.hamcrest.core.Is.is;
public class MediaReplicationTest extends AbstractServerReplicationTest {
private void waitSomeTime() throws InterruptedException {
Thread.sleep(1000); // wait for JMS to deliver the message and the message to be applied
}
/* util */
private MediaTrack createMediaTrack() {
String title = "title";
String url = "url";
Date startTime = new Date();
int durationInMillis = 1;
MimeType mimeType = MimeType.mp4;
MediaTrack mediaTrack = new MediaTrack(title, url, startTime, durationInMillis, mimeType);
return mediaTrack;
}
@Test
public void testBasicInitialLoad() throws Exception {
assertNotSame(master, replica);
assertEquals(master.getAllMediaTracks().size(), replica.getAllMediaTracks().size());
}
@Test
public void testAddMediaTrackReplication() throws InterruptedException {
assertThat(master.getAllMediaTracks().size(), is(0));
assertThat(replica.getAllMediaTracks().size(), is(0));
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
assertThat(master.getAllMediaTracks().size(), is(1));
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(1));
}
@Test
public void testDeleteMediaTrackReplication() throws InterruptedException {
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
master.mediaTrackDeleted(mediaTrack);
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(0));
}
@Test
public void testUpdateMediaTrackTitleReplication() throws InterruptedException {
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
mediaTrack.title = mediaTrack.title + "x";
master.mediaTrackTitleChanged(mediaTrack);
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(1));
assertThat(replica.getAllMediaTracks().iterator().next().title, is(mediaTrack.title));
}
@Test
public void testUpdateMediaTrackUrlReplication() throws InterruptedException {
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
mediaTrack.url = mediaTrack.url + "x";
master.mediaTrackUrlChanged(mediaTrack);
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(1));
assertThat(replica.getAllMediaTracks().iterator().next().url, is(mediaTrack.url));
}
@Test
public void testUpdateMediaTrackStartTimeReplication() throws InterruptedException {
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
mediaTrack.startTime = new Date(mediaTrack.startTime.getTime() + 1000);
master.mediaTrackStartTimeChanged(mediaTrack);
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(1));
assertThat(replica.getAllMediaTracks().iterator().next().startTime, is(mediaTrack.startTime));
}
@Test
public void testUpdateMediaTrackDurationReplication() throws InterruptedException {
MediaTrack mediaTrack = createMediaTrack();
master.mediaTrackAdded(mediaTrack);
mediaTrack.durationInMillis = mediaTrack.durationInMillis + 1000;
master.mediaTrackDurationChanged(mediaTrack);
waitSomeTime();
assertThat(replica.getAllMediaTracks().size(), is(1));
assertThat(replica.getAllMediaTracks().iterator().next().durationInMillis, is(mediaTrack.durationInMillis));
}
}
@@ -15,9 +15,9 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.mongodb,
com.tractrac.clientmodule;bundle-version="2.0.0",
com.mongodb.driver;bundle-version="2.6.2",
com.sap.sailing.domain.test
com.sap.sailing.domain.test,
org.hamcrest;bundle-version="1.1.0"
Import-Package: junit.framework;version="4.8.1",
org.junit;version="4.8.1",
org.json.simple
Export-Package: com.sap.sailing.server,
com.sap.sailing.server.impl
Export-Package: com.sap.sailing.server.impl
@@ -0,0 +1,308 @@
package com.sap.sailing.server.impl;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.server.impl.MediaLibrary.Interval;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.core.Is.is;
public class MediaLibaryTest {
private static final int FIFTEEN_MINUTES_IN_MILLIS = 15 * 60 * 1000;
private static final int THIRTY_MINUTES_IN_MILLIS = 30 * 60 * 1000;
private static final int ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
private MediaLibrary mediaLibary;
@Before
public void before() {
this.mediaLibary = new MediaLibrary();
}
/* util */
private MediaTrack createMediaTrack(String dbId) {
String title = "title";
String url = "url";
Date startTime = new Date();
int durationInMillis = 1;
MimeType mimeType = MimeType.mp4;
MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
return mediaTrack;
}
@Test
public void testGetElementsFromEmtpy() throws Exception {
assertThat(this.mediaLibary.allTracks().size(), is(0));
}
@Test
public void testCreateOneElement() throws Exception {
String dbId = "1234";
MediaTrack mediaTrack = createMediaTrack(dbId);
this.mediaLibary.addMediaTrack(mediaTrack);
Iterator<MediaTrack> allMediaTracks = this.mediaLibary.allTracks().iterator();
assertThat(allMediaTracks.hasNext(), is(true));
MediaTrack item = allMediaTracks.next();
assertThat(allMediaTracks.hasNext(), is(false));
assertTrue(item.equals(mediaTrack));
assertThat(item.title, is(mediaTrack.title));
assertThat(item.url, is(mediaTrack.url));
assertThat(item.startTime, is(mediaTrack.startTime));
assertThat(item.durationInMillis, is(mediaTrack.durationInMillis));
assertThat(item.mimeType, is(mediaTrack.mimeType));
}
@Test
public void testCreateDeleteOneElement() throws Exception {
String dbId = "1234";
MediaTrack mediaTrack = createMediaTrack(dbId);
this.mediaLibary.addMediaTrack(mediaTrack);
this.mediaLibary.deleteMediaTrack(mediaTrack);
Collection<MediaTrack> allMediaTracks = this.mediaLibary.allTracks();
assertThat(allMediaTracks.size(), is(0));
}
@Test
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackLongerThanEndTime() {
Date startTime = new Date();
int duration = ONE_HOUR_IN_MILLIS;
Date rangeStart = startTime;
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackShorterThanEndTime() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = startTime;
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsBeforeEndsAfterRange() {
Date startTime = new Date();
int duration = ONE_HOUR_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
Date rangeEnd = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsAfterEndsBeforeRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
int duration = FIFTEEN_MINUTES_IN_MILLIS;
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsAfterEndsAfterRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
int duration = THIRTY_MINUTES_IN_MILLIS;
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_StartsBeforeEndsBeforeRange() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
assertOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_TrackEndsBeforeRange() {
Date startTime = new Date();
int duration = THIRTY_MINUTES_IN_MILLIS;
Date rangeStart = new Date(startTime.getTime() + duration + 1);
Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testQueryMediaTracksBetween_TrackStartsAfterRange() {
Date rangeStart = new Date();
Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
Date startTime = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
int duration = THIRTY_MINUTES_IN_MILLIS;
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
}
@Test
public void testCacheChangeStartTime() {
MediaTrack originalMediaTrack = new MediaTrack();
originalMediaTrack.dbId = "a";
originalMediaTrack.startTime = new Date();
originalMediaTrack.durationInMillis = 100;
Date queryStartTime = new Date(originalMediaTrack.startTime.getTime() + 1);
Date queryEndTime = new Date(originalMediaTrack.deriveEndTime().getTime() - 1);
mediaLibary.addMediaTrack(originalMediaTrack);
Set<MediaTrack> firstQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
assertThat(firstQueryResult.size(), is(1));
MediaTrack changedMediaTrack = new MediaTrack();
changedMediaTrack.dbId = "a";
changedMediaTrack.startTime = new Date(originalMediaTrack.startTime.getTime() + 101);
changedMediaTrack.durationInMillis = 100;
mediaLibary.startTimeChanged(changedMediaTrack);
Set<MediaTrack> secondQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
assertThat(secondQueryResult.size(), is(0));
Set<MediaTrack> thirdQueryResult = mediaLibary.findMediaTracksInTimeRange(changedMediaTrack.startTime, changedMediaTrack.deriveEndTime());
assertThat(thirdQueryResult.size(), is(1));
}
@Test
public void testCacheRemoveMediaTrack() {
MediaTrack mediaTrack = new MediaTrack();
mediaTrack.dbId = "a";
mediaTrack.startTime = new Date();
mediaTrack.durationInMillis = 100;
Date originalStartTime = mediaTrack.startTime;
Date originalEndTime = mediaTrack.deriveEndTime();
mediaLibary.addMediaTrack(mediaTrack);
Set<MediaTrack> firstQueryResult = mediaLibary.findMediaTracksInTimeRange(originalStartTime, originalEndTime);
assertThat(firstQueryResult.size(), is(1));
mediaLibary.deleteMediaTrack(mediaTrack);
Set<MediaTrack> secondQueryResult = mediaLibary.findMediaTracksInTimeRange(originalStartTime, originalEndTime);
assertThat(secondQueryResult.size(), is(0));
}
@Test
public void testCacheAddSecondMediaTrackWithSameInterval() {
MediaTrack firstMediaTrack = new MediaTrack();
firstMediaTrack.dbId = "a";
firstMediaTrack.startTime = new Date();
firstMediaTrack.durationInMillis = 100;
mediaLibary.addMediaTrack(firstMediaTrack);
Date queryStartTime = new Date(firstMediaTrack.startTime.getTime() + 1);
Date queryEndTime = new Date(firstMediaTrack.deriveEndTime().getTime() - 1);
Set<MediaTrack> firstQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
assertThat(firstQueryResult.size(), is(1));
MediaTrack secondMediaTrack = new MediaTrack();
secondMediaTrack.dbId = "b";
secondMediaTrack.startTime = firstMediaTrack.startTime;
secondMediaTrack.durationInMillis = firstMediaTrack.durationInMillis;
mediaLibary.addMediaTrack(secondMediaTrack);
Set<MediaTrack> secondQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
assertThat(secondQueryResult.size(), is(2));
Date uncachedStartTime = new Date(firstMediaTrack.startTime.getTime() + 2);
Date uncachedEndTime = new Date(firstMediaTrack.deriveEndTime().getTime() - 2);
Set<MediaTrack> thirdQueryResult = mediaLibary.findMediaTracksInTimeRange(uncachedStartTime, uncachedEndTime);
assertThat(thirdQueryResult.size(), is(2));
}
private void assertOverlap(Date startTime, int durationInMillis, Date rangeStart, Date rangeEnd) {
//insert test object
String dbId = "1234";
String title = "title";
String url = "url";
MimeType mimeType = MimeType.mp4;
MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
mediaLibary.addMediaTrack(mediaTrack);
Collection<MediaTrack> mediaTracks = mediaLibary.findMediaTracksInTimeRange(rangeStart, rangeEnd);
assertThat(mediaTracks.size(), is(1));
assertThat(mediaTracks.iterator().next().dbId, is(dbId));
}
private void assertNoOverlap(Date startTime, int durationInMillis, Date rangeStart, Date rangeEnd) {
//insert test object
String dbId = "1234";
String title = "title";
String url = "url";
MimeType mimeType = MimeType.mp4;
MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
mediaLibary.addMediaTrack(mediaTrack);
Collection<MediaTrack> mediaTracks = mediaLibary.findMediaTracksInTimeRange(rangeStart, rangeEnd);
assertThat(mediaTracks.size(), is(0));
}
@Test
public void testIntervalEqualsIdentical() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() + 1);
Interval interval = new Interval(date1, date2);
assertTrue(interval.equals(interval));
}
@Test
public void testIntervalEqualsSame() throws Exception {
Date date1_1 = new Date();
Date date1_2 = new Date(date1_1.getTime() + 1);
Date date2_1 = new Date(date1_1.getTime());
Date date2_2 = new Date(date1_2.getTime());
Interval interval1 = new Interval(date1_1, date1_2);
Interval interval2 = new Interval(date2_1, date2_2);
assertTrue(interval1.equals(interval2));
assertEquals(interval1.hashCode(), interval2.hashCode());
}
@Test
public void testIntervalEqualsNull() throws Exception {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() + 1);
Interval interval = new Interval(date1, date2);
assertFalse(interval.equals(null));
}
@Test
public void testIntervalNotEquals() throws Exception {
Date date1_1 = new Date();
Date date1_2 = new Date(date1_1.getTime() + 1);
Date date2_1 = new Date(date1_1.getTime() + 2);
Date date2_2 = new Date(date1_1.getTime() + 3);
Interval interval1 = new Interval(date1_1, date1_2);
Interval interval2 = new Interval(date2_1, date2_2);
assertFalse(interval1.equals(interval2));
}
}
@@ -26,6 +26,6 @@
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfigArea" value="false"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.osgi@default:default,com.googlecode.java-diff-utils@default:default,com.mongodb.driver@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@default:default,com.sap.sailing.domain.swisstimingreplayadapter@default:default,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@default:default,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@default:default,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.mongodb@default:default,com.sap.sailing.operationaltransformation@default:default,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.ess40@default:default,com.sap.sailing.server.gateway@default:default,com.sap.sailing.server.replication@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator*1.0.0.qualifier@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www.events@default:default,com.sap.sailing.www@default:default,com.sap.sailing.xcelsiusadapter@default:default,com.sap.sailing.xrr.resultimport@default:default,com.tractrac.clientmodule@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.osgi@default:default,com.googlecode.java-diff-utils@default:default,com.mongodb.driver@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@default:default,com.sap.sailing.domain.swisstimingreplayadapter@default:default,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@default:default,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@default:default,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.mongodb@default:default,com.sap.sailing.operationaltransformation@default:default,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.ess40@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@default:default,com.sap.sailing.server.replication@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www.events@default:default,com.sap.sailing.www@default:default,com.sap.sailing.xcelsiusadapter@default:default,com.sap.sailing.xrr.resultimport@default:default,com.tractrac.clientmodule@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
<stringAttribute key="yk-options" value="&#13;&#10;additional-options2=onexit\=snapshot&#13;&#10;"/>
</launchConfiguration>
@@ -12,11 +12,11 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Fleet;
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
@@ -501,4 +502,22 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
CourseArea getCourseArea(Serializable courseAreaId);
void mediaTrackAdded(MediaTrack mediaTrack);
void mediaTracksAdded(Collection<MediaTrack> mediaTracks);
void mediaTrackTitleChanged(MediaTrack mediaTrack);
void mediaTrackUrlChanged(MediaTrack mediaTrack);
void mediaTrackStartTimeChanged(MediaTrack mediaTrack);
void mediaTrackDurationChanged(MediaTrack mediaTrack);
void mediaTrackDeleted(MediaTrack mediaTrack);
Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
Collection<MediaTrack> getAllMediaTracks();
}
@@ -0,0 +1,305 @@
package com.sap.sailing.server.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaUtil;
class MediaLibrary {
static class Interval {
public final Date begin;
public final Date end;
public Interval(Date begin, Date end) {
this.begin = begin;
this.end = end;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof Interval) {
Interval interval = (Interval) obj;
return MediaUtil.equalsDatesAllowingNull(this.begin, interval.begin)
&& MediaUtil.equalsDatesAllowingNull(this.end, interval.end);
} else {
return false;
}
}
@Override
public int hashCode() {
return Arrays.hashCode(new Date[] { begin, end });
}
}
/**
* The set of MediaTracks kept by this library. Due to complex write operations which require explicit write-locking
* anyway there's no need to realize this collection with built-in concurrency support.
*/
private final Map<MediaTrack, MediaTrack> mediaTracksByDbId = new HashMap<MediaTrack, MediaTrack>();
/**
* The cache used during lookups by overlapping interval. Realized as ConcurrentMap to prevent from need for
* write-locking during lookup operation. Cached values are realized as Set to allow for quicker removal from cache
* result in case a MediaTrack is being removed from the library or changes values such that it needs to be removed
* from the cache.
*/
private final ConcurrentMap<Interval, Set<MediaTrack>> cacheByInterval = new ConcurrentHashMap<Interval, Set<MediaTrack>>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
// /**
// * Sort in reverse order of start time! For equal start times compare dbId to distinguish different instances.
// */
// private static final Comparator<MediaTrack> COMPARATOR_BY_REVERSE_STARTTIME = new Comparator<MediaTrack>() {
//
// @Override
// public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) {
// int result = compareDatesAllowingNull(mediaTrack2.startTime, mediaTrack1.startTime);
// if (result == 0) {
// return mediaTrack1.dbId.compareTo(mediaTrack2.dbId);
// } else {
// return result;
// }
// }
//
// };
/**
* NOTE: The implementation of this lookup using simple linear search is a trade off between development effort and
* performance gain.
*
* Actually, efficient lookup of overlapping intervals is supposed to be performed using an interval tree, e.g.
* http://thekevindolan.com/2010/02/interval-tree/
*
* However, considering the expected low number of media entries and the expected high rate of cache hits doesn't
* justify providing a dedicated interval tree implementation (given that there's none readily available).
*
* TODO: A slight performance gain might be achieved assuming that more recent media tracks are requested more frequently
* than older ones. Thus, sorting the list of media tracks by start time and starting linear search from the more
* recent end might reduce loop cycles during linear search. E.g. use a SortedMap with COMPARATOR_BY_REVERSE_STARTTIME commented out above.
*
* @param startTime
* @param endTime
* @return
*/
Set<MediaTrack> findMediaTracksInTimeRange(Date startTime, Date endTime) {
if (startTime != null) {
Interval interval = new Interval(startTime, endTime);
readLock.lock();
try {
Set<MediaTrack> cachedMediaTracks = cacheByInterval.get(interval);
if (cachedMediaTracks == null) {
Set<MediaTrack> result = new HashSet<MediaTrack>();
for (MediaTrack mediaTrack : mediaTracksByDbId.values()) {
if (mediaTrack.overlapsWith(startTime, endTime)) {
result.add(mediaTrack);
}
}
cachedMediaTracks = cacheByInterval.putIfAbsent(interval, result);
if (cachedMediaTracks != null) {
return cachedMediaTracks;
} else {
return result;
}
} else {
return cachedMediaTracks;
}
} finally {
readLock.unlock();
}
}
// else
return Collections.emptySet();
}
/**
* Adding media tracks is expected to happen so rarely that we don't optimize for it and simply re-use the add-many
* method.
*
* @param mediaTrack
*/
void addMediaTrack(MediaTrack mediaTrack) {
addMediaTracks(Collections.singleton(mediaTrack));
}
void addMediaTracks(Collection<MediaTrack> mediaTracks) {
writeLock.lock();
try {
for (MediaTrack mediaTrack : mediaTracks) {
this.mediaTracksByDbId.put(mediaTrack, mediaTrack);
updateCache_Add(mediaTrack);
}
} finally {
writeLock.unlock();
}
}
void deleteMediaTrack(MediaTrack mediaTrack) {
writeLock.lock();
try {
MediaTrack deletedMediaTrack = mediaTracksByDbId.remove(mediaTrack);
updateCache_Remove(deletedMediaTrack);
} finally {
writeLock.unlock();
}
}
void titleChanged(MediaTrack changedMediaTrack) {
writeLock.lock();
try {
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
if (mediaTrack != null) {
mediaTrack.title = changedMediaTrack.title;
}
} finally {
writeLock.unlock();
}
}
void urlChanged(MediaTrack changedMediaTrack) {
writeLock.lock();
try {
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
if (mediaTrack != null) {
mediaTrack.url = changedMediaTrack.url;
}
} finally {
writeLock.unlock();
}
}
void mimeTypeChanged(MediaTrack changedMediaTrack) {
writeLock.lock();
try {
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
if (mediaTrack != null) {
mediaTrack.mimeType = changedMediaTrack.mimeType;
}
} finally {
writeLock.unlock();
}
}
void startTimeChanged(MediaTrack changedMediaTrack) {
writeLock.lock();
try {
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
if (mediaTrack != null) {
mediaTrack.startTime = changedMediaTrack.startTime;
updateCache_Change(mediaTrack);
}
} finally {
writeLock.unlock();
}
}
void durationChanged(MediaTrack changedMediaTrack) {
writeLock.lock();
try {
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
if (mediaTrack != null) {
mediaTrack.durationInMillis = changedMediaTrack.durationInMillis;
updateCache_Change(mediaTrack);
}
} finally {
writeLock.unlock();
}
}
/**
* To be called only under write lock!
*/
private void updateCache_Add(MediaTrack mediaTrack) {
for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
Interval interval = cacheEntry.getKey();
if (mediaTrack.overlapsWith(interval.begin, interval.end)) {
cacheEntry.getValue().add(mediaTrack);
}
}
}
/**
* To be called only under write lock!
*/
private void updateCache_Change(MediaTrack mediaTrack) {
for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
cacheEntry.getValue().remove(mediaTrack);
Interval interval = cacheEntry.getKey();
if (mediaTrack.overlapsWith(interval.begin, interval.end)) {
cacheEntry.getValue().add(mediaTrack);
}
}
}
/**
* To be called only under write lock!
*/
private void updateCache_Remove(MediaTrack mediaTrack) {
for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
cacheEntry.getValue().remove(mediaTrack);
}
}
Collection<MediaTrack> allTracks() {
readLock.lock();
try {
return new ArrayList<MediaTrack>(mediaTracksByDbId.values());
} finally {
readLock.unlock();
}
}
void serialize(ObjectOutputStream stream) throws IOException {
readLock.lock();
try {
stream.writeObject(new ArrayList<MediaTrack>(mediaTracksByDbId.values()));
} finally {
readLock.unlock();
}
}
@SuppressWarnings("unchecked")
void deserialize(ObjectInputStream stream) throws ClassNotFoundException, IOException {
addMediaTracks((Collection<MediaTrack>) stream.readObject());
}
public void clear() {
writeLock.lock();
try {
mediaTracksByDbId.clear();
cacheByInterval.clear();
} finally {
writeLock.unlock();
}
}
}
@@ -12,7 +12,9 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -27,11 +29,6 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.CourseArea;
@@ -60,6 +57,8 @@ import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -77,6 +76,9 @@ import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
import com.sap.sailing.domain.persistence.media.MediaDB;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector;
@@ -115,6 +117,7 @@ import com.sap.sailing.server.RacingEventServiceOperation;
import com.sap.sailing.server.Replicator;
import com.sap.sailing.server.operationaltransformation.AddCourseArea;
import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta;
import com.sap.sailing.server.operationaltransformation.AddMediaTrackOperation;
import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta;
import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
@@ -124,11 +127,16 @@ import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix;
import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix;
import com.sap.sailing.server.operationaltransformation.RecordWindFix;
import com.sap.sailing.server.operationaltransformation.RemoveEvent;
import com.sap.sailing.server.operationaltransformation.RemoveMediaTrackOperation;
import com.sap.sailing.server.operationaltransformation.RemoveWindFix;
import com.sap.sailing.server.operationaltransformation.RenameEvent;
import com.sap.sailing.server.operationaltransformation.TrackRegatta;
import com.sap.sailing.server.operationaltransformation.UpdateEvent;
import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings;
import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation;
import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation;
import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackTitleOperation;
import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackUrlOperation;
import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive;
import com.sap.sailing.server.operationaltransformation.UpdateRaceTimes;
import com.sap.sailing.server.operationaltransformation.UpdateSpecificRegatta;
@@ -208,22 +216,26 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
private final RaceLogScoringReplicator raceLogScoringReplicator;
private final MediaDB mediaDB;
private final MediaLibrary mediaLibrary;
public RacingEventServiceImpl() {
this(MongoFactory.INSTANCE.getDefaultDomainObjectFactory(), MongoFactory.INSTANCE.getDefaultMongoObjectFactory());
this(MongoFactory.INSTANCE.getDefaultDomainObjectFactory(), MongoFactory.INSTANCE.getDefaultMongoObjectFactory(), MediaDBFactory.INSTANCE.getDefaultMediaDB());
}
/**
* Uses the default factories for the tracking adapters
*/
private RacingEventServiceImpl(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) {
private RacingEventServiceImpl(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, MediaDB mediaDB) {
this(domainObjectFactory, mongoObjectFactory, SwissTimingFactory.INSTANCE,
com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE, DomainFactory.INSTANCE);
com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE, DomainFactory.INSTANCE, mediaDB);
}
private RacingEventServiceImpl(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory,
SwissTimingFactory swissTimingFactory,
com.sap.sailing.domain.swisstimingadapter.DomainFactory swissTimingDomainFactory,
DomainFactory tractracDomainFactory) {
DomainFactory tractracDomainFactory, MediaDB mediaDb) {
assert swissTimingDomainFactory.getBaseDomainFactory() == tractracDomainFactory.getBaseDomainFactory();
logger.info("Created " + this);
this.tractracDomainFactory = tractracDomainFactory;
@@ -246,6 +258,8 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
delayToLiveInMillis = TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS;
this.raceLogReplicator = new RaceLogReplicator(this);
this.raceLogScoringReplicator = new RaceLogScoringReplicator(this);
this.mediaDB = mediaDb;
this.mediaLibrary = new MediaLibrary();
// Add one default leaderboard that aggregates all races currently tracked by this service.
// This is more for debugging purposes than for anything else.
@@ -255,17 +269,18 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
loadRaceIDToRegattaAssociations();
loadStoredLeaderboardsAndGroups();
loadStoredEvents();
loadMediaLibary();
}
public RacingEventServiceImpl(MongoDBService mongoDBService) {
this(MongoFactory.INSTANCE.getDomainObjectFactory(mongoDBService), MongoFactory.INSTANCE.getMongoObjectFactory(mongoDBService));
this(MongoFactory.INSTANCE.getDomainObjectFactory(mongoDBService), MongoFactory.INSTANCE.getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService));
}
public RacingEventServiceImpl(MongoDBService mongoDBService, SwissTimingFactory swissTimingFactory,
com.sap.sailing.domain.swisstimingadapter.DomainFactory swissTimingDomainFactory,
DomainFactory tractracDomainFactory) {
DomainFactory tractracDomainFactory, MediaDB mediaDB) {
this(MongoFactory.INSTANCE.getDomainObjectFactory(mongoDBService), MongoFactory.INSTANCE.getMongoObjectFactory(mongoDBService),
swissTimingFactory, swissTimingDomainFactory, tractracDomainFactory);
swissTimingFactory, swissTimingDomainFactory, tractracDomainFactory, mediaDB);
}
@Override
@@ -300,7 +315,24 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
}
}
}
/**
* Collects media track references from the configured sources (mongo DB by default, ftp folder yet to be implemented).
* The method is expected to be called initially blocking the API until finished.
*
* Subsequent calls (assumed to be triggered from the admin console or in scheduled intervals) don't need to block. In that case,
* the API will simply serve the current state.
*
*/
private void loadMediaLibary() {
Collection<DBMediaTrack> allDBMediaTracks = mediaDB.loadAllMediaTracks();
for (DBMediaTrack dbMediaTrack : allDBMediaTracks) {
MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.valueOf(dbMediaTrack.mimeType) : null;
MediaTrack mediaTrack = new MediaTrack(dbMediaTrack.dbId, dbMediaTrack.title, dbMediaTrack.url, dbMediaTrack.startTime, dbMediaTrack.durationInMillis, mimeType);
mediaTrackAdded(mediaTrack);
}
}
@Override
public void addLeaderboard(Leaderboard leaderboard) {
synchronized (leaderboardsByName) {
@@ -1624,6 +1656,8 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
oos.writeObject(leaderboardGroupsByName);
logger.info("serializing leaderboardsByName");
oos.writeObject(leaderboardsByName);
logger.info("serializing mediaLibrary");
mediaLibrary.serialize(oos);
}
@SuppressWarnings("unchecked") // the type-parameters in the casts of the de-serialized collection objects can't be checked
@@ -1649,6 +1683,7 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
leaderboardGroupsByName.clear();
leaderboardsByName.clear();
eventsById.clear();
mediaLibrary.clear();
logger.info("receiving eventsById");
eventsById.putAll((Map<Serializable, Event>) ois.readObject());
logger.info("Recieved " + eventsById.size() + " NEW events");
@@ -1675,6 +1710,8 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
((LeaderboardGroupMetaLeaderboard) leaderboard).registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
}
}
logger.info("receiving mediaLibrary");
mediaLibrary.deserialize(ois);
logger.info("Done with initial replication on "+this);
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassloader);
@@ -1782,4 +1819,71 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen
return courseArea;
}
@Override
public void mediaTrackAdded(MediaTrack mediaTrack) {
String mimeType = mediaTrack.mimeType != null ? mediaTrack.mimeType.name() : null;
if (mediaTrack.dbId == null) {
mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime, mediaTrack.durationInMillis, mimeType);
}
mediaLibrary.addMediaTrack(mediaTrack);
replicate(new AddMediaTrackOperation(mediaTrack));
}
@Override
public void mediaTracksAdded(Collection<MediaTrack> mediaTracks) {
mediaLibrary.addMediaTracks(mediaTracks);
}
@Override
public void mediaTrackTitleChanged(MediaTrack mediaTrack) {
mediaDB.updateTitle(mediaTrack.dbId, mediaTrack.title);
mediaLibrary.titleChanged(mediaTrack);
replicate(new UpdateMediaTrackTitleOperation(mediaTrack));
}
@Override
public void mediaTrackUrlChanged(MediaTrack mediaTrack) {
mediaDB.updateUrl(mediaTrack.dbId, mediaTrack.url);
mediaLibrary.urlChanged(mediaTrack);
replicate(new UpdateMediaTrackUrlOperation(mediaTrack));
}
@Override
public void mediaTrackStartTimeChanged(MediaTrack mediaTrack) {
mediaDB.updateStartTime(mediaTrack.dbId, mediaTrack.startTime);
mediaLibrary.startTimeChanged(mediaTrack);
replicate(new UpdateMediaTrackStartTimeOperation(mediaTrack));
}
@Override
public void mediaTrackDurationChanged(MediaTrack mediaTrack) {
mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.durationInMillis);
mediaLibrary.durationChanged(mediaTrack);
replicate(new UpdateMediaTrackDurationOperation(mediaTrack));
}
@Override
public void mediaTrackDeleted(MediaTrack mediaTrack) {
mediaDB.deleteMediaTrack(mediaTrack.dbId);
mediaLibrary.deleteMediaTrack(mediaTrack);
replicate(new RemoveMediaTrackOperation(mediaTrack));
}
@Override
public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier);
if (trackedRace != null) {
Date raceStart = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
Date raceEnd = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
return mediaLibrary.findMediaTracksInTimeRange(raceStart , raceEnd);
} else {
return Collections.emptyList();
}
}
@Override
public Collection<MediaTrack> getAllMediaTracks() {
return mediaLibrary.allTracks();
}
}
@@ -0,0 +1,28 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventServiceOperation;
public abstract class AbstractMediaTrackOperation extends AbstractRacingEventServiceOperation<Void> {
private static final long serialVersionUID = 1L;
protected final MediaTrack mediaTrack;
public AbstractMediaTrackOperation(MediaTrack mediaTrack) {
this.mediaTrack = mediaTrack;
}
@Override
public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
// TODO Auto-generated method stub
return null;
}
@Override
public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
// TODO Auto-generated method stub
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class AddMediaTrackOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public AddMediaTrackOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackAdded(this.mediaTrack);
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class RemoveMediaTrackOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public RemoveMediaTrackOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackDeleted(this.mediaTrack);
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class UpdateMediaTrackDurationOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public UpdateMediaTrackDurationOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackDurationChanged(this.mediaTrack);
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class UpdateMediaTrackStartTimeOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public UpdateMediaTrackStartTimeOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackStartTimeChanged(this.mediaTrack);
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class UpdateMediaTrackTitleOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public UpdateMediaTrackTitleOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackTitleChanged(this.mediaTrack);
return null;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.server.RacingEventService;
public class UpdateMediaTrackUrlOperation extends AbstractMediaTrackOperation {
private static final long serialVersionUID = 1L;
public UpdateMediaTrackUrlOperation(MediaTrack mediaTrack) {
super(mediaTrack);
}
@Override
public Void internalApplyTo(RacingEventService toState) throws Exception {
toState.mediaTrackUrlChanged(this.mediaTrack);
return null;
}
}