diff --git a/home.md b/home.md index 54d69649deb..f6fca6bdba8 100644 --- a/home.md +++ b/home.md @@ -57,6 +57,7 @@ SAP is at the center of today’s technology revolution, developing innovations * [[Create clickable UI prototypes with Axure|wiki/ui-clickable-prototypes]] * [[Uploading Media Content|wiki/uploading-media-content]] * Information for Event Managers + * [[Set up local network with replication server|wiki/event-network-with-replica]] * [[Operating Igtimi WindBots/windbot-operations]] * [[Linking Race Videos|wiki/linking-race-videos]] * [[Import official results|wiki/results-import]] @@ -66,6 +67,7 @@ SAP is at the center of today’s technology revolution, developing innovations * [[Information about Extreme Sailing Series|wiki/extreme-sailing-series]] * [[Travemünder Woche 2014 event page|wiki/tw2014]] * [[505 worlds Kiel 2014 event page|wiki/505-worlds-kiel-2014]] + * [[Kieler Woche event page|wiki/kieler-woche-2015]] ### Internal services (not related to wiki but useful) diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index aefd1e3ffb6..3df14c0b671 100755 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -43,5 +43,5 @@ - + diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java index f42cb8519d4..5eb4044e2d3 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java @@ -8,6 +8,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Image; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.media.ImageDTO; public class EventLogo extends Image { @@ -24,9 +25,9 @@ public class EventLogo extends Image { @Override public void onSuccess(final EventDTO event) { if (event != null) { - final String logoUrl = event.getLogoImageURL(); - if (logoUrl != null) { - eventLogo.getElement().setAttribute("src", logoUrl); + final ImageDTO logo = event.getLogoImage(); + if (logo != null) { + eventLogo.getElement().setAttribute("src", logo.getSourceRef()); } else { eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN); } @@ -34,6 +35,7 @@ public class EventLogo extends Image { Window.setTitle(event.getName() + " Dashboard"); } } + } @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index 7834eeb525c..1d30e8dee86 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -4,7 +4,7 @@ import com.sap.sailing.domain.common.impl.MeterDistance; public enum BoatClassMasterdata { - _2_4M ("2.4 Meter", true, 4.11, 0.81, BoatHullType.MONOHULL, false, "2.4M", "2.4mR"), + _2_4M ("2.4 Meter", true, 4.11, 0.81, BoatHullType.MONOHULL, false, "2.4M", "2.4mR", "2.4 Metre"), _5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"), _12M ("12 Meter", true, 5.97, 1.43, BoatHullType.MONOHULL, true, "12M", "12mR", "12SQM"), _29ER ("29er", true, 4.45, 1.70, BoatHullType.MONOHULL, true), diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java index e524246e193..a117994d7a8 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java @@ -29,6 +29,7 @@ import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sse.common.Util.Pair; /** * Offers methods to load domain objects from a Mongo DB @@ -97,7 +98,7 @@ public interface DomainObjectFactory { Event loadEvent(String name); - Iterable loadAllEvents(); + Iterable> loadAllEvents(); /** * The {@link MongoObjectFactory#storeEvent(Event)} method stores events and their links to leaderboard groups. diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java old mode 100755 new mode 100644 index 7e442fd7e4b..414c80b9148 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1,2065 +1,2185 @@ -package com.sap.sailing.domain.persistence.impl; - -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.bson.types.ObjectId; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBCursor; -import com.mongodb.DBObject; -import com.mongodb.MongoException; -import com.mongodb.WriteResult; -import com.mongodb.util.JSON; -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; -import com.sap.sailing.domain.abstractlog.race.CompetitorResults; -import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogEventRestoreFactory; -import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; -import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; -import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; -import com.sap.sailing.domain.abstractlog.race.scoring.AdditionalScoringInformationType; -import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent; -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnTimeFactorEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; -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.ControlPointWithTwoMarks; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.RegattaRegistry; -import com.sap.sailing.domain.base.RemoteSailingServerReference; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.configuration.DeviceConfiguration; -import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; -import com.sap.sailing.domain.base.configuration.RegattaConfiguration; -import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationImpl; -import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; -import com.sap.sailing.domain.base.impl.CourseDataImpl; -import com.sap.sailing.domain.base.impl.EventImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.base.impl.VenueImpl; -import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.MarkType; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.WindImpl; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; -import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.RacingProcedureType; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections; -import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener; -import com.sap.sailing.domain.leaderboard.EventResolver; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; -import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.impl.DelayedLeaderboardCorrectionsImpl; -import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; -import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; -import com.sap.sailing.domain.persistence.DomainObjectFactory; -import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; -import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory; -import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; -import com.sap.sailing.domain.racelogtracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.ranking.RankingMetricConstructor; -import com.sap.sailing.domain.ranking.RankingMetricsFactory; -import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; -import com.sap.sailing.domain.regattalog.RegattaLogStore; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.domain.tracking.impl.WindTrackImpl; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.Helpers; -import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer; -import com.sap.sse.common.Color; -import com.sap.sse.common.Duration; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.TimeRange; -import com.sap.sse.common.TypeBasedServiceFinder; -import com.sap.sse.common.TypeBasedServiceFinderFactory; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Triple; -import com.sap.sse.common.impl.MillisecondsDurationImpl; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.impl.RGBColor; -import com.sap.sse.common.impl.TimeRangeImpl; - -public class DomainObjectFactoryImpl implements DomainObjectFactory { - private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName()); - private final CompetitorJsonDeserializer competitorDeserializer; - - private final DB database; - - private RaceLogEventRestoreFactory raceLogEventFactory; - private final DomainFactory baseDomainFactory; - private final TypeBasedServiceFinder deviceIdentifierServiceFinder; - private final TypeBasedServiceFinderFactory serviceFinderFactory; - - /** - * Uses null as the {@link TypeBasedServiceFinder}, meaning that no {@link DeviceIdentifier}s can be loaded - * using this instance of a {@link DomainObjectFactory}. - */ - public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory) { - this(db, baseDomainFactory, /* deviceTypeServiceFinder */ null); - } - - public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory) { - super(); - this.serviceFinderFactory = serviceFinderFactory; - if (serviceFinderFactory != null) { - this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); - this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); - } else { - this.deviceIdentifierServiceFinder = null; - } - this.baseDomainFactory = baseDomainFactory; - this.competitorDeserializer = CompetitorJsonDeserializer.create(baseDomainFactory); - this.database = db; - this.raceLogEventFactory = RaceLogEventRestoreFactory.INSTANCE; - } - - @Override - public DomainFactory getBaseDomainFactory() { - return baseDomainFactory; - } - - public Wind loadWind(DBObject object) { - return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object)); - } - - public Position loadPosition(DBObject object) { - Number latNumber = (Number) object.get(FieldNames.LAT_DEG.name()); - Double lat = latNumber == null ? null : latNumber.doubleValue(); - Number lngNumber = (Number) object.get(FieldNames.LNG_DEG.name()); - Double lng = lngNumber == null ? null : lngNumber.doubleValue(); - if (lat != null && lng != null) { - return new DegreePosition(lat, lng); - } else { - return null; - } - } - - public static TimePoint loadTimePoint(DBObject object, String fieldName) { - TimePoint result = null; - Number timePointAsNumber = (Number) object.get(fieldName); - if (timePointAsNumber != null) { - result = new MillisecondsTimePoint(timePointAsNumber.longValue()); - } - return result; - } - - public static TimePoint loadTimePoint(DBObject object, FieldNames field) { - return loadTimePoint(object, field.name()); - } - - public static TimeRange loadTimeRange(DBObject object, FieldNames field) { - DBObject timeRangeObj = (DBObject) object.get(field.name()); - if (timeRangeObj == null) { - return null; - } - TimePoint from = loadTimePoint(timeRangeObj, FieldNames.FROM_MILLIS); - TimePoint to = loadTimePoint(timeRangeObj, FieldNames.TO_MILLIS); - return new TimeRangeImpl(from, to); - } - /** - * Loads a {@link TimePoint} on the given object at {@link FieldNames#TIME_AS_MILLIS}. - */ - public TimePoint loadTimePoint(DBObject object) { - return loadTimePoint(object, FieldNames.TIME_AS_MILLIS); - } - - public SpeedWithBearing loadSpeedWithBearing(DBObject object) { - return new KnotSpeedWithBearingImpl(((Number) object.get(FieldNames.KNOT_SPEED.name())).doubleValue(), - new DegreeBearingImpl(((Number) object.get(FieldNames.DEGREE_BEARING.name())).doubleValue())); - } - - @Override - public RaceIdentifier loadRaceIdentifier(DBObject dbObject) { - RaceIdentifier result = null; - String regattaName = (String) dbObject.get(FieldNames.EVENT_NAME.name()); - String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name()); - if (regattaName != null && raceName != null) { - result = new RegattaNameAndRaceName(regattaName, raceName); - } - return result; - } - - private void ensureIndicesOnWindTracks(DBCollection windTracks) { - windTracks.createIndex(new BasicDBObject(FieldNames.RACE_ID.name(), 1)); // for new programmatic access - windTracks.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1)); // for export or human look-up - // for legacy access to not yet migrated fixes - windTracks.createIndex(new BasicDBObjectBuilder().add(FieldNames.EVENT_NAME.name(), 1) - .add(FieldNames.RACE_NAME.name(), 1).get()); - // Unique index - try { - windTracks.createIndex( - new BasicDBObjectBuilder().add(FieldNames.RACE_ID.name(), 1) - .add(FieldNames.WIND_SOURCE_NAME.name(), 1).add(FieldNames.WIND_SOURCE_ID.name(), 1) - .add(FieldNames.WIND.name() + "." + FieldNames.TIME_AS_MILLIS.name(), 1).get(), - new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).get()); - } catch (MongoException exception) { - - if (exception.getCode() == 10092) { - logger.warning(String.format( - "Setting the unique index on the %s collection failed because you have too many duplicates. " - + "This leads to the mongo error code %s and the following message: %s \nTo fix this follow " - + "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/cook-book#Remove-" - + "duplicates-from-WIND_TRACK-collection", CollectionNames.WIND_TRACKS.name(), - exception.getCode(), exception.getMessage())); - } else { - logger.severe(String.format( - "Setting the unique index on the %s collection failed with error code %s and message: %s", - CollectionNames.WIND_TRACKS.name(), exception.getCode(), exception.getMessage())); - } - } - } - - @Override - public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) { - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); - Leaderboard result = null; - try { - BasicDBObject query = new BasicDBObject(); - query.put(FieldNames.LEADERBOARD_NAME.name(), name); - for (DBObject o : leaderboardCollection.find(query)) { - result = loadLeaderboard(o, regattaRegistry, /* leaderboardRegistry */ null, /* groupForMetaLeaderboard */ null); - } - } catch (Exception e) { - // something went wrong during DB access; report, then use empty new wind track - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard "+name+"."); - logger.log(Level.SEVERE, "loadLeaderboard", e); - } - return result; - } - - @Override - public Iterable getAllLeaderboards(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); - Set result = new HashSet(); - try { - for (DBObject o : leaderboardCollection.find()) { - final Leaderboard loadedLeaderboard = loadLeaderboard(o, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null); - if (loadedLeaderboard != null) { - result.add(loadedLeaderboard); - } - } - } catch (Exception e) { - // something went wrong during DB access; report, then use empty new wind track - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards."); - logger.log(Level.SEVERE, "getAllLeaderboards", e); - } - return result; - } - - /** - * If the DBObject has a field {@link FieldNames#REGATTA_NAME} then the object represents a - * {@link RegattaLeaderboard}. Otherwise, a {@link FlexibleLeaderboard} will be loaded. - * - * @param leaderboardRegistry - * if not null, then before creating and loading the leaderboard it is looked up in this - * registry and only loaded if not found there. If leaderboardRegistry is null, - * the leaderboard is loaded in any case. If the leaderboard is loaded and - * leaderboardRegistry is not null, the leaderboard loaded is - * {@link LeaderboardRegistry#addLeaderboard(Leaderboard) added to the registry}. - * @param groupForMetaLeaderboard - * if not null, a {@link LeaderboardGroupMetaLeaderboard} instance is created and set as the - * group's {@link LeaderboardGroup#setOverallLeaderboard(Leaderboard) overall leaderboard} - * - * @return null in case the leaderboard couldn't be loaded, e.g., because the regatta referenced by a - * {@link RegattaLeaderboard} cannot be found; the leaderboard loaded or found in - * leaderboardRegistry, otherwise - */ - private Leaderboard loadLeaderboard(DBObject dbLeaderboard, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry, - LeaderboardGroup groupForMetaLeaderboard) { - Leaderboard result = null; - String leaderboardName = (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()); - if (leaderboardRegistry != null) { - result = leaderboardRegistry.getLeaderboardByName(leaderboardName); - } - if (result == null) { - ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS); - String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name()); - if (groupForMetaLeaderboard != null) { - result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard, - loadScoringScheme(dbLeaderboard), resultDiscardingRule); - groupForMetaLeaderboard.setOverallLeaderboard(result); - } else if (regattaName == null) { - result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule); - } else { - result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard, resultDiscardingRule, regattaRegistry); - } - if (result != null) { - final Leaderboard finalResult = result; - finalResult.setDisplayName((String) dbLeaderboard.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name())); - - DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory); - final boolean[] needsMigration = new boolean[1]; - loadedLeaderboardCorrections.addLeaderboardCorrectionsResolvedListener(new LeaderboardCorrectionsResolvedListener() { - @Override - public void correctionsResolved(DelayedLeaderboardCorrections delayedLeaderboardCorrections) { - if (needsMigration[0]) { - new MongoObjectFactoryImpl(database).storeLeaderboard(finalResult); - } - } - }); - needsMigration[0] = loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection()) || needsMigration[0]; - needsMigration[0] = loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections) || needsMigration[0]; - loadColumnFactors(dbLeaderboard, result); - // add the leaderboard to the registry - if (leaderboardRegistry != null) { - leaderboardRegistry.addLeaderboard(result); - logger.info("loaded leaderboard "+result.getName()+" into "+leaderboardRegistry); - } - } - } - return result; - } - - private void loadColumnFactors(DBObject dbLeaderboard, Leaderboard result) { - DBObject dbColumnFactors = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMN_FACTORS.name()); - if (dbColumnFactors != null) { - for (String encodedRaceColumnName : dbColumnFactors.keySet()) { - double factor = ((Number) dbColumnFactors.get(encodedRaceColumnName)).doubleValue(); - String raceColumnName = MongoUtils.unescapeDollarAndDot(encodedRaceColumnName); - final RaceColumn raceColumn = result.getRaceColumnByName(raceColumnName); - if (raceColumn != null) { - raceColumn.setFactor(factor); - } else { - logger.warning("Expected to find race column named "+raceColumnName+" in leaderboard "+result.getName()+ - " to apply column factor "+factor+", but the race column wasn't found. Ignoring factor."); - } - } - } - } - - private boolean loadSuppressedCompetitors(DBObject dbLeaderboard, - DelayedLeaderboardCorrections loadedLeaderboardCorrections) { - final boolean needsMigration; - BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name()); - if (dbSuppressedCompetitorNames != null) { - needsMigration = true; - for (Object escapedCompetitorName : dbSuppressedCompetitorNames) { - loadedLeaderboardCorrections.suppressCompetitorByName(MongoUtils.unescapeDollarAndDot((String) escapedCompetitorName)); - } - } else { - needsMigration = false; - } - BasicDBList dbSuppressedCompetitorIDs = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name()); - if (dbSuppressedCompetitorIDs != null) { - for (Object competitorId : dbSuppressedCompetitorIDs) { - loadedLeaderboardCorrections.suppressCompetitorById((Serializable) competitorId); - } - } - return needsMigration; - } - - /** - * @param dbObject expects to find a field identified by field which holds a {@link BasicDBList} - */ - private ThresholdBasedResultDiscardingRule loadResultDiscardingRule(DBObject dbObject, FieldNames field) { - BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) dbObject.get(field.name()); - final ThresholdBasedResultDiscardingRule result; - if (dbDiscardIndexResultsStartingWithHowManyRaces == null) { - result = null; - } else { - int[] discardIndexResultsStartingWithHowManyRaces = new int[dbDiscardIndexResultsStartingWithHowManyRaces - .size()]; - int i = 0; - for (Object discardingThresholdAsObject : dbDiscardIndexResultsStartingWithHowManyRaces) { - discardIndexResultsStartingWithHowManyRaces[i++] = (Integer) discardingThresholdAsObject; - } - result = new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces); - } - return result; - } - - /** - * @return null if the regatta cannot be resolved; otherwise the leaderboard for the regatta specified - */ - private RegattaLeaderboard loadRegattaLeaderboard(String leaderboardName, String regattaName, DBObject dbLeaderboard, - ThresholdBasedResultDiscardingRule resultDiscardingRule, RegattaRegistry regattaRegistry) { - RegattaLeaderboard result = null; - Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName)); - if (regatta == null) { - logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard."); - } else { - result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); - result.setName(leaderboardName); - } - return result; - } - - private RaceLogStore getRaceLogStore() { - return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore( - new MongoObjectFactoryImpl(database, serviceFinderFactory), this); - } - - private RegattaLogStore getRegattaLogStore() { - return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore( - new MongoObjectFactoryImpl(database, serviceFinderFactory), this); - } - - private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard, - ThresholdBasedResultDiscardingRule resultDiscardingRule) { - final FlexibleLeaderboardImpl result; - BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name()); - if (dbRaceColumns == null) { - // this was probably an orphaned overall leaderboard - logger.warning("Probably found orphan overall leaderboard named " - + dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring."); - result = null; - } else { - final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard); - - Serializable courseAreaId = (Serializable) dbLeaderboard.get(FieldNames.COURSE_AREA_ID.name()); - CourseArea courseArea = null; - if (courseAreaId != null) { - UUID courseAreaUuid = UUID.fromString(courseAreaId.toString()); - courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid); - } - - result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(), - (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()), - resultDiscardingRule, scoringScheme, courseArea); - // For a FlexibleLeaderboard, there should be only the default fleet for any race column - for (Object dbRaceColumnAsObject : dbRaceColumns) { - BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject; - String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); - - RaceColumn raceColumn = result.addRaceColumn(columnName, - (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name())); - - Map raceIdentifiers = loadRaceIdentifiers(dbRaceColumn); - RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(result.getFleet(null).getName()); - if (defaultFleetRaceIdentifier == null) { - // Backward compatibility - defaultFleetRaceIdentifier = raceIdentifiers.get(null); - } - if (defaultFleetRaceIdentifier != null) { - Fleet defaultFleet = result.getFleet(null); - if (defaultFleet != null) { - - raceColumn.setRaceIdentifier(defaultFleet, defaultFleetRaceIdentifier); - } else { - // leaderboard has no default fleet; don't know what to do with default RaceIdentifier - logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier - + " for default fleet for leaderboard " + result.getName() - + " because no default fleet was found in leaderboard"); - } - } - - } - } - return result; - } - - private ScoringScheme loadScoringScheme(DBObject dbLeaderboard) { - ScoringSchemeType scoringSchemeType = getScoringSchemeType(dbLeaderboard); - final ScoringScheme scoringScheme = baseDomainFactory.createScoringScheme(scoringSchemeType); - return scoringScheme; - } - - private boolean loadLeaderboardCorrections(DBObject dbLeaderboard, DelayedLeaderboardCorrections correctionsToUpdate, - SettableScoreCorrection scoreCorrectionToUpdate) { - boolean needsMigration = false; - DBObject carriedPoints = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_CARRIED_POINTS.name()); - if (carriedPoints != null) { - needsMigration = true; - for (String escapedCompetitorName : carriedPoints.keySet()) { - Double carriedPointsForCompetitor = ((Number) carriedPoints.get(escapedCompetitorName)).doubleValue(); - if (carriedPointsForCompetitor != null) { - correctionsToUpdate.setCarriedPointsByName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), carriedPointsForCompetitor); - } - } - } - BasicDBList carriedPointsById = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name()); - if (carriedPointsById != null) { - for (Object o : carriedPointsById) { - DBObject competitorIdAndCarriedPoints = (DBObject) o; - Serializable competitorId = (Serializable) competitorIdAndCarriedPoints.get(FieldNames.COMPETITOR_ID.name()); - Double carriedPointsForCompetitor = ((Number) competitorIdAndCarriedPoints - .get(FieldNames.LEADERBOARD_CARRIED_POINTS.name())).doubleValue(); - if (carriedPointsForCompetitor != null) { - correctionsToUpdate.setCarriedPointsByID(competitorId, carriedPointsForCompetitor); - } - } - } - DBObject dbScoreCorrection = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name()); - if (dbScoreCorrection.containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name())) { - scoreCorrectionToUpdate.setTimePointOfLastCorrectionsValidity( - new MillisecondsTimePoint((Long) dbScoreCorrection.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name()))); - dbScoreCorrection.removeField(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name()); - } - if (dbScoreCorrection.containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name())) { - scoreCorrectionToUpdate.setComment((String) dbScoreCorrection.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name())); - dbScoreCorrection.removeField(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name()); - } - for (String escapedRaceColumnName : dbScoreCorrection.keySet()) { - // deprecated style: a DBObject per race where the keys are the escaped competitor names - // new style: a BasicDBList per race where each entry is a DBObject with COMPETITOR_ID and - // LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON and LEADERBOARD_CORRECTED_SCORE fields each - DBObject dbScoreCorrectionForRace = (DBObject) dbScoreCorrection.get(escapedRaceColumnName); - final RaceColumn raceColumn = correctionsToUpdate.getLeaderboard().getRaceColumnByName(MongoUtils.unescapeDollarAndDot(escapedRaceColumnName)); - if (raceColumn != null) { - if (dbScoreCorrectionForRace instanceof BasicDBList) { - for (Object o : (BasicDBList) dbScoreCorrectionForRace) { - DBObject dbScoreCorrectionForCompetitorInRace = (DBObject) o; - Serializable competitorId = (Serializable) dbScoreCorrectionForCompetitorInRace - .get(FieldNames.COMPETITOR_ID.name()); - if (dbScoreCorrectionForCompetitorInRace - .containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())) { - correctionsToUpdate.setMaxPointsReasonByID(competitorId, raceColumn, MaxPointsReason - .valueOf((String) dbScoreCorrectionForCompetitorInRace - .get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name()))); - } - if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_CORRECTED_SCORE - .name())) { - final Double leaderboardCorrectedScore = ((Number) dbScoreCorrectionForCompetitorInRace - .get(FieldNames.LEADERBOARD_CORRECTED_SCORE.name())).doubleValue(); - correctionsToUpdate.correctScoreByID(competitorId, raceColumn, - (Double) leaderboardCorrectedScore); - } - } - } else { - needsMigration = true; - for (String competitorName : dbScoreCorrectionForRace.keySet()) { - DBObject dbScoreCorrectionForCompetitorInRace = (DBObject) dbScoreCorrectionForRace - .get(competitorName); - if (dbScoreCorrectionForCompetitorInRace - .containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())) { - correctionsToUpdate.setMaxPointsReasonByName(MongoUtils - .unescapeDollarAndDot(competitorName), raceColumn, MaxPointsReason - .valueOf((String) dbScoreCorrectionForCompetitorInRace - .get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name()))); - } - if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_CORRECTED_SCORE - .name())) { - final Double leaderboardCorrectedScore = ((Number) dbScoreCorrectionForCompetitorInRace - .get(FieldNames.LEADERBOARD_CORRECTED_SCORE.name())).doubleValue(); - correctionsToUpdate.correctScoreByName(MongoUtils.unescapeDollarAndDot(competitorName), - raceColumn, (Double) leaderboardCorrectedScore); - } - } - } - } else { - logger.warning("Couldn't find race column " + MongoUtils.unescapeDollarAndDot(escapedRaceColumnName) - + " in leaderboard " + correctionsToUpdate.getLeaderboard().getName()); - } - } - DBObject competitorDisplayNames = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name()); - // deprecated style: a DBObject whose keys are the escaped competitor names - // new style: a BasicDBList whose entries are DBObjects with COMPETITOR_ID and COMPETITOR_DISPLAY_NAME fields - if (competitorDisplayNames != null) { - if (competitorDisplayNames instanceof BasicDBList) { - for (Object o : (BasicDBList) competitorDisplayNames) { - DBObject competitorDisplayName = (DBObject) o; - final Serializable competitorId = (Serializable) competitorDisplayName.get(FieldNames.COMPETITOR_ID.name()); - final String displayName = (String) competitorDisplayName.get(FieldNames.COMPETITOR_DISPLAY_NAME.name()); - correctionsToUpdate.setDisplayNameByID(competitorId, displayName); - } - } else { - needsMigration = true; - for (String escapedCompetitorName : competitorDisplayNames.keySet()) { - correctionsToUpdate.setDisplayNameByName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), - (String) competitorDisplayNames.get(escapedCompetitorName)); - } - } - } - return needsMigration; - } - - /** - * Expects a DBObject under the key {@link FieldNames#RACE_IDENTIFIERS} whose keys are the fleet names and whose - * values are the race identifiers as DBObjects (see {@link #loadRaceIdentifier(DBObject)}). If legacy DB instances - * have a {@link RaceIdentifier} that is not associated with a fleet name, it may be stored directly in the - * dbRaceColumn. In this case, it is returned with null as the fleet name key. - * - * @return a map with fleet names as key and the corresponding fleet's race identifier as value; the special - * null key is used to identify a "default fleet" for backward compatibility with stored - * leaderboards which don't know about fleets yet; this key should be mapped to the leaderboard's default - * fleet. - */ - private Map loadRaceIdentifiers(DBObject dbRaceColumn) { - Map result = new HashMap(); - // try to load a deprecated single race identifier to associate with the default fleet: - RaceIdentifier singleLegacyRaceIdentifier = loadRaceIdentifier(dbRaceColumn); - if (singleLegacyRaceIdentifier != null) { - result.put(null, singleLegacyRaceIdentifier); - } - DBObject raceIdentifiersPerFleet = (DBObject) dbRaceColumn.get(FieldNames.RACE_IDENTIFIERS.name()); - if (raceIdentifiersPerFleet != null) { - for (String escapedFleetName : raceIdentifiersPerFleet.keySet()) { - String fleetName = MongoUtils.unescapeDollarAndDot(escapedFleetName); - result.put(fleetName, loadRaceIdentifier((DBObject) raceIdentifiersPerFleet.get(fleetName))); - } - } - return result; - } - - @Override - public LeaderboardGroup loadLeaderboardGroup(String name, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { - DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); - LeaderboardGroup leaderboardGroup = null; - try { - BasicDBObject query = new BasicDBObject(); - query.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), name); - leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry, leaderboardRegistry); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard group "+name+"."); - logger.log(Level.SEVERE, "loadLeaderboardGroup", e); - } - - return leaderboardGroup; - } - - @Override - public Iterable getAllLeaderboardGroups(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { - DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); - Set leaderboardGroups = new HashSet(); - try { - for (DBObject o : leaderboardGroupCollection.find()) { - boolean hasUUID = o.containsField(FieldNames.LEADERBOARD_GROUP_UUID.name()); - final LeaderboardGroup leaderboardGroup = loadLeaderboardGroup(o, regattaRegistry, leaderboardRegistry); - leaderboardGroups.add(leaderboardGroup); - if (!hasUUID) { - // in an effort to migrate leaderboard groups without ID to such that have a UUID as their ID, we need - // to write a leaderboard group to the database again after it just received a UUID for the first time: - logger.info("Existing LeaderboardGroup " + leaderboardGroup.getName() - + " received a UUID during migration; updating the leaderboard group in the database"); - new MongoObjectFactoryImpl(database).storeLeaderboardGroup(leaderboardGroup); - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups."); - logger.log(Level.SEVERE, "loadLeaderboardGroup", e); - } - - return leaderboardGroups; - } - - private LeaderboardGroup loadLeaderboardGroup(DBObject o, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); - String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name()); - UUID uuid = (UUID) o.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); - if (uuid == null) { - uuid = UUID.randomUUID(); - logger.info("Leaderboard group "+name+" receives UUID "+uuid+" in a migration effort"); - // migration: leaderboard groups that don't yet have a UUID receive a random one - } - String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name()); - String displayName = (String) o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name()); - boolean displayGroupsInReverseOrder = false; // default value - Object displayGroupsInReverseOrderObj = o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name()); - if (displayGroupsInReverseOrderObj != null) { - displayGroupsInReverseOrder = (Boolean) displayGroupsInReverseOrderObj; - } - ArrayList leaderboards = new ArrayList(); - BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name()); - for (Object object : dbLeaderboardIds) { - ObjectId dbLeaderboardId = (ObjectId) object; - DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId); - if (dbLeaderboard != null) { - final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry, - leaderboardRegistry, /* groupForMetaLeaderboard */null); - if (loadedLeaderboard != null) { - leaderboards.add(loadedLeaderboard); - } - } else { - logger.warning("couldn't find leaderboard with ID "+dbLeaderboardId+" referenced by leaderboard group "+name); - } - } - logger.info("loaded leaderboard group "+name); - LeaderboardGroupImpl result = new LeaderboardGroupImpl(uuid, name, description, displayName, displayGroupsInReverseOrder, leaderboards); - Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name()); - if (overallLeaderboardIdOrName != null) { - final DBObject dbOverallLeaderboard; - if (overallLeaderboardIdOrName instanceof ObjectId) { - dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardIdOrName); - } else { - dbOverallLeaderboard = (DBObject) overallLeaderboardIdOrName; - } - if (dbOverallLeaderboard != null) { - // the loadLeaderboard call adds the overall leaderboard to the leaderboard registry and sets it as the - // overall leaderboard of the leaderboard group - loadLeaderboard(dbOverallLeaderboard, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ result); - } - } - return result; - } - - @Override - public Iterable getLeaderboardsNotInGroup(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); - Set result = new HashSet(); - try { - // For MongoDB 2.4 $where with refs to global objects no longer works - // http://docs.mongodb.org/manual/reference/operator/where/#op._S_where - // Also a single where leads to a table walk without using indexes. So avoid $where. - - // Don't change the query object, unless you know what you're doing. - // It queries all leaderboards not referenced to be part of a leaderboard group - // and in particular not being an overall leaderboard of a leaderboard group. - DBCursor allLeaderboards = leaderboardCollection.find(); - for (DBObject leaderboardFromDB : allLeaderboards) { - DBObject inLeaderboardGroupsQuery = new BasicDBObject(); - inLeaderboardGroupsQuery.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), ((ObjectId)leaderboardFromDB.get("_id")).toString()); - boolean inLeaderboardGroups = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupsQuery).size()>0; - - DBObject inLeaderboardGroupOverallQuery = new BasicDBObject(); - inLeaderboardGroupOverallQuery.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), ((ObjectId)leaderboardFromDB.get("_id")).toString()); - boolean inLeaderboardGroupOverall = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupOverallQuery).size()>0; - - DBObject inLeaderboardGroupOverallQueryName = new BasicDBObject(); - inLeaderboardGroupOverallQueryName.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), leaderboardFromDB.get(FieldNames.LEADERBOARD_NAME.name())); - boolean inLeaderboardGroupOverallName = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupOverallQueryName).size()>0; - - if (!inLeaderboardGroups && !inLeaderboardGroupOverall && !inLeaderboardGroupOverallName) { - final Leaderboard loadedLeaderboard = loadLeaderboard(leaderboardFromDB, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null); - if (loadedLeaderboard != null) { - result.add(loadedLeaderboard); - } - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards."); - logger.log(Level.SEVERE, "getAllLeaderboards", e); - } - return result; - } - - @Override - public WindTrack loadWindTrack(String regattaName, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage) { - final WindTrack result; - Map resultMap = loadWindTracks(regattaName, race, windSource, millisecondsOverWhichToAverage); - if (resultMap.containsKey(windSource)) { - result = resultMap.get(windSource); - } else { - // create an empty wind track as result if no fixes were found in store for the wind source requested - result = new WindTrackImpl(millisecondsOverWhichToAverage, windSource.getType().getBaseConfidence(), - windSource.getType().useSpeed(), - /* nameForReadWriteLock */ WindTrackImpl.class.getSimpleName()+" for source "+windSource.toString()); - } - return result; - } - - @Override - public Map loadWindTracks(String regattaName, RaceDefinition race, - long millisecondsOverWhichToAverageWind) { - Map result = loadWindTracks(regattaName, race, /* constrain wind source */ null, millisecondsOverWhichToAverageWind); - return result; - } - - /** - * @param constrainToWindSource - * if null, wind for all sources will be loaded; otherwise, only wind data for the wind - * source specified by this argument will be loaded - */ - private Map loadWindTracks(String regattaName, RaceDefinition race, - WindSource constrainToWindSource, long millisecondsOverWhichToAverageWind) { - Map result = new HashMap(); - try { - DBCollection windTracks = database.getCollection(CollectionNames.WIND_TRACKS.name()); - ensureIndicesOnWindTracks(windTracks); - BasicDBObject queryById = new BasicDBObject(); - // the default query is by the RACE_ID key: - queryById.put(FieldNames.RACE_ID.name(), race.getId()); - if (constrainToWindSource != null) { - queryById.put(FieldNames.WIND_SOURCE_NAME.name(), constrainToWindSource.name()); - } - for (DBObject dbWind : windTracks.find(queryById)) { - loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); - } - // Additionally check for legacy wind fixes stored with the old EVENT_NAME key; if any are found, migrate them - BasicDBObject queryByName = new BasicDBObject(); - queryByName.put(FieldNames.EVENT_NAME.name(), regattaName); - queryByName.put(FieldNames.RACE_NAME.name(), race.getName()); - if (constrainToWindSource != null) { - queryByName.put(FieldNames.WIND_SOURCE_NAME.name(), constrainToWindSource.name()); - } - final DBCursor windFixesFoundByName = windTracks.find(queryByName); - if (windFixesFoundByName.hasNext()) { - List windFixesToMigrate = new ArrayList(); - for (DBObject dbWind : windFixesFoundByName) { - Util.Pair wind = loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); - // write the wind fix with the new ID-based key and remove the legacy wind fix from the DB - windFixesToMigrate.add(new MongoObjectFactoryImpl(database).storeWindTrackEntry(race, regattaName, - wind.getB(), wind.getA())); - } - logger.info("Migrating "+windFixesFoundByName.size()+" wind fixes of regatta "+regattaName+ - " and race "+race.getName()+" to ID-based keys"); - windTracks.insert(windFixesToMigrate.toArray(new DBObject[windFixesToMigrate.size()])); - logger.info("Removing "+windFixesFoundByName.size()+" wind fixes that were keyed by the names of regatta "+regattaName+ - " and race "+race.getName()); - windTracks.remove(queryByName); - } - } catch (Exception e) { - // something went wrong during DB access; report, then use empty new wind track - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded wind data. Check MongoDB settings."); - logger.log(Level.SEVERE, "loadWindTrack", e); - } - return result; - } - - private Util.Pair loadWindFix(Map result, DBObject dbWind, long millisecondsOverWhichToAverageWind) { - Wind wind = loadWind((DBObject) dbWind.get(FieldNames.WIND.name())); - WindSourceType windSourceType = WindSourceType.valueOf((String) dbWind.get(FieldNames.WIND_SOURCE_NAME.name())); - WindSource windSource; - if (dbWind.containsField(FieldNames.WIND_SOURCE_ID.name())) { - windSource = new WindSourceWithAdditionalID(windSourceType, (String) dbWind.get(FieldNames.WIND_SOURCE_ID.name())); - } else { - windSource = new WindSourceImpl(windSourceType); - } - WindTrack track = result.get(windSource); - if (track == null) { - track = new WindTrackImpl(millisecondsOverWhichToAverageWind, windSource.getType().getBaseConfidence(), - windSource.getType().useSpeed(), - /* nameForReadWriteLock */ WindTrackImpl.class.getSimpleName()+" for source "+windSource.toString()); - result.put(windSource, track); - } - track.add(wind); - return new Util.Pair(wind, windSource); - } - - @Override - public void loadLeaderboardGroupLinksForEvents(EventResolver eventResolver, - LeaderboardGroupResolver leaderboardGroupResolver) { - DBCollection links = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); - for (Object o : links.find()) { - DBObject dbLink = (DBObject) o; - UUID eventId = (UUID) dbLink.get(FieldNames.EVENT_ID.name()); - Event event = eventResolver.getEvent(eventId); - if (event == null) { - logger.info("Found leaderboard group IDs for event with ID "+eventId+" but couldn't find that event."); - } else { - @SuppressWarnings("unchecked") - List leaderboardGroupIDs = (List) dbLink.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); - for (UUID leaderboardGroupID : leaderboardGroupIDs) { - LeaderboardGroup leaderboardGroup = leaderboardGroupResolver.getLeaderboardGroupByID(leaderboardGroupID); - if (leaderboardGroup != null) { - event.addLeaderboardGroup(leaderboardGroup); - } - } - } - } - } - - @Override - public Event loadEvent(String name) { - Event result; - BasicDBObject query = new BasicDBObject(); - query.put(FieldNames.EVENT_NAME.name(), name); - DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); - DBObject eventDBObject = eventCollection.findOne(query); - if (eventDBObject != null) { - result = loadEvent(eventDBObject); - } else { - result = null; - } - return result; - } - - @Override - public Iterable loadAllEvents() { - ArrayList result = new ArrayList(); - DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); - - try { - for (DBObject o : eventCollection.find()) { - result.add(loadEvent(o)); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events."); - logger.log(Level.SEVERE, "loadAllEvents", e); - } - - return result; - } - - - private RemoteSailingServerReference loadSailingSever(DBObject serverDBObject) { - RemoteSailingServerReference result = null; - String name = (String) serverDBObject.get(FieldNames.SERVER_NAME.name()); - String urlAsString = (String) serverDBObject.get(FieldNames.SERVER_URL.name()); - try { - URL serverUrl = new URL(urlAsString); - result = new RemoteSailingServerReferenceImpl(name, serverUrl); - } catch (MalformedURLException e) { - logger.log(Level.SEVERE, "Can't load the sailing server with URL " + urlAsString, e); - } - return result; - } - - @Override - public Iterable loadAllRemoteSailingServerReferences() { - ArrayList result = new ArrayList(); - DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); - try { - for (DBObject o : serverCollection.find()) { - if (loadSailingSever(o) != null) { - result.add(loadSailingSever(o)); - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load sailing server instances URLs."); - logger.log(Level.SEVERE, "loadAllSailingServers", e); - } - return result; - } - - /** - * An event doesn't store its regattas; it's the regatta that stores a reference to its event; the regatta - * needs to add itself to the event when loaded or instantiated. - */ - private Event loadEvent(DBObject eventDBObject) { - String name = (String) eventDBObject.get(FieldNames.EVENT_NAME.name()); - String description = (String) eventDBObject.get(FieldNames.EVENT_DESCRIPTION.name()); - UUID id = (UUID) eventDBObject.get(FieldNames.EVENT_ID.name()); - TimePoint startDate = loadTimePoint(eventDBObject, FieldNames.EVENT_START_DATE); - TimePoint endDate = loadTimePoint(eventDBObject, FieldNames.EVENT_END_DATE); - boolean isPublic = eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) != null ? (Boolean) eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) : false; - Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name())); - Event result = new EventImpl(name, startDate, endDate, venue, isPublic, id); - result.setDescription(description); - String officialWebSiteURLAsString = (String) eventDBObject.get(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name()); - if (officialWebSiteURLAsString != null) { - try { - result.setOfficialWebsiteURL(new URL(officialWebSiteURLAsString)); - } catch (MalformedURLException e) { - logger.severe("Error parsing official website URL "+officialWebSiteURLAsString+" for event "+name+". Ignoring this URL."); - } - } - String logoImageURLAsString = (String) eventDBObject.get(FieldNames.EVENT_LOGO_IMAGE_URL.name()); - if (logoImageURLAsString != null) { - try { - result.setLogoImageURL(new URL(logoImageURLAsString)); - } catch (MalformedURLException e) { - logger.severe("Error parsing logo image URL "+logoImageURLAsString+" for event "+name+". Ignoring this URL."); - } - } - BasicDBList imageURLs = (BasicDBList) eventDBObject.get(FieldNames.EVENT_IMAGE_URLS.name()); - if (imageURLs != null) { - for (Object imageURL : imageURLs) { - try { - result.addImageURL(new URL((String) imageURL)); - } catch (MalformedURLException e) { - logger.severe("Error parsing image URL "+imageURL+" for event "+name+". Ignoring this image URL."); - } - } - } - BasicDBList videoURLs = (BasicDBList) eventDBObject.get(FieldNames.EVENT_VIDEO_URLS.name()); - if (videoURLs != null) { - for (Object videoURL : videoURLs) { - try { - result.addVideoURL(new URL((String) videoURL)); - } catch (MalformedURLException e) { - logger.severe("Error parsing video URL "+videoURL+" for event "+name+". Ignoring this video URL."); - } - } - } - BasicDBList sponsorImageURLs = (BasicDBList) eventDBObject.get(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name()); - if (sponsorImageURLs != null) { - for (Object sponsorImageURL : sponsorImageURLs) { - try { - result.addSponsorImageURL(new URL((String) sponsorImageURL)); - } catch (MalformedURLException e) { - logger.severe("Error parsing sponsor image URL "+sponsorImageURL+" for event "+name+". Ignoring this sponsor image URL."); - } - } - } - return result; - } - - private Venue loadVenue(DBObject dbObject) { - String name = (String) dbObject.get(FieldNames.VENUE_NAME.name()); - BasicDBList dbCourseAreas = (BasicDBList) dbObject.get(FieldNames.COURSE_AREAS.name()); - Venue result = new VenueImpl(name); - for (Object courseAreaDBObject : dbCourseAreas) { - CourseArea courseArea = loadCourseArea((DBObject) courseAreaDBObject); - result.addCourseArea(courseArea); - } - return result; - } - - private CourseArea loadCourseArea(DBObject courseAreaDBObject) { - String name = (String) courseAreaDBObject.get(FieldNames.COURSE_AREA_NAME.name()); - UUID id = (UUID) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name()); - return baseDomainFactory.getOrCreateCourseArea(id, name); - } - - @Override - public Iterable loadAllRegattas(TrackedRegattaRegistry trackedRegattaRegistry) { - List result = new ArrayList(); - DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name()); - for (DBObject dbRegatta : regattaCollection.find()) { - result.add(loadRegatta(dbRegatta, trackedRegattaRegistry)); - } - return result; - } - - @Override - public Regatta loadRegatta(String name, TrackedRegattaRegistry trackedRegattaRegistry) { - DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), name); - DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name()); - DBObject dbRegatta = regattaCollection.findOne(query); - Regatta result = loadRegatta(dbRegatta, trackedRegattaRegistry); - assert result == null || result.getName().equals(name); - return result; - } - - private Regatta loadRegatta(DBObject dbRegatta, TrackedRegattaRegistry trackedRegattaRegistry) { - Regatta result = null; - if (dbRegatta != null) { - String name = (String) dbRegatta.get(FieldNames.REGATTA_NAME.name()); - String boatClassName = (String) dbRegatta.get(FieldNames.BOAT_CLASS_NAME.name()); - TimePoint startDate = loadTimePoint(dbRegatta, FieldNames.REGATTA_START_DATE); - TimePoint endDate = loadTimePoint(dbRegatta, FieldNames.REGATTA_END_DATE); - Serializable id = (Serializable) dbRegatta.get(FieldNames.REGATTA_ID.name()); - if (id == null) { - id = name; - } - BoatClass boatClass = null; - if (boatClassName != null) { - boolean typicallyStartsUpwind = (Boolean) dbRegatta.get(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name()); - boatClass = baseDomainFactory.getOrCreateBoatClass(boatClassName, typicallyStartsUpwind); - } - BasicDBList dbSeries = (BasicDBList) dbRegatta.get(FieldNames.REGATTA_SERIES.name()); - Iterable series = loadSeries(dbSeries, trackedRegattaRegistry); - Serializable courseAreaId = (Serializable) dbRegatta.get(FieldNames.COURSE_AREA_ID.name()); - CourseArea courseArea = null; - if (courseAreaId != null) { - UUID courseAreaUuid = UUID.fromString(courseAreaId.toString()); - courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid); - } - RegattaConfiguration configuration = null; - if (dbRegatta.containsField(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())) { - try { - JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(dbRegatta.get(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())))); - configuration = RegattaConfigurationJsonDeserializer.create().deserialize(json); - } catch (JsonDeserializationException|ParseException e) { - logger.log(Level.WARNING, "Error loading racing procedure configration for regatta.", e); - } - } - final Boolean useStartTimeInference = (Boolean) dbRegatta.get(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name()); - final RankingMetricConstructor rankingMetricConstructor = loadRankingMetricConstructor(dbRegatta); - result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, boatClass, startDate, endDate, series, /* persistent */true, - loadScoringScheme(dbRegatta), id, courseArea, useStartTimeInference == null ? true - : useStartTimeInference, rankingMetricConstructor); - result.setRegattaConfiguration(configuration); - } - return result; - } - - private RankingMetricConstructor loadRankingMetricConstructor(DBObject dbRegatta) { - DBObject rankingMetricJson = (DBObject) dbRegatta.get(FieldNames.REGATTA_RANKING_METRIC.name()); - // default is OneDesignRankingMetric - final RankingMetricConstructor result; - if (rankingMetricJson == null) { - result = OneDesignRankingMetric::new; - } else { - final String rankingMetricTypeName = (String) rankingMetricJson.get(FieldNames.REGATTA_RANKING_METRIC_TYPE.name()); - result = RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.valueOf(rankingMetricTypeName)); - } - return result; - } - - private ScoringSchemeType getScoringSchemeType(DBObject dbObject) { - String scoringSchemeTypeName = (String) dbObject.get(FieldNames.SCORING_SCHEME_TYPE.name()); - ScoringSchemeType scoringSchemeType; - if (scoringSchemeTypeName == null) { - scoringSchemeType = ScoringSchemeType.LOW_POINT; // the default - } else { - try { - scoringSchemeType = ScoringSchemeType.valueOf(scoringSchemeTypeName); - } catch (IllegalArgumentException ila) { - // can happen that the database contains a scoring scheme that - // has not yet been implemented - fall back with a warning - scoringSchemeType = ScoringSchemeType.LOW_POINT; - logger.warning("Could not find scoring scheme " + scoringSchemeTypeName + "! Most probably this has not yet been implemented or even been removed."); - } - } - return scoringSchemeType; - } - - private Iterable loadSeries(BasicDBList dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) { - List result = new ArrayList(); - for (Object o : dbSeries) { - DBObject oneDBSeries = (DBObject) o; - Series series = loadSeries(oneDBSeries, trackedRegattaRegistry); - result.add(series); - } - return result; - } - - private Series loadSeries(DBObject dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) { - String name = (String) dbSeries.get(FieldNames.SERIES_NAME.name()); - boolean isMedal = (Boolean) dbSeries.get(FieldNames.SERIES_IS_MEDAL.name()); - Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name()); - Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name()); - Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name()); - final BasicDBList dbFleets = (BasicDBList) dbSeries.get(FieldNames.SERIES_FLEETS.name()); - List fleets = loadFleets(dbFleets); - BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name()); - Iterable raceColumnNames = loadRaceColumnNames(dbRaceColumns); - Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry); - if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) { - ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS); - series.setResultDiscardingRule(resultDiscardingRule); - } - if (startsWithZeroScore != null) { - series.setStartsWithZeroScore(startsWithZeroScore); - } - if (hasSplitFleetContiguousScoring != null) { - series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); - } - if (firstColumnIsNonDiscardableCarryForward != null) { - series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); - } - loadRaceColumnRaceLinks(dbRaceColumns, series); - return series; - } - - private Iterable loadRaceColumnNames(BasicDBList dbRaceColumns) { - List result = new ArrayList(); - for (Object o : dbRaceColumns) { - DBObject dbRaceColumn = (DBObject) o; - result.add((String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name())); - } - return result; - } - - private void loadRaceColumnRaceLinks(BasicDBList dbRaceColumns, Series series) { - for (Object o : dbRaceColumns) { - DBObject dbRaceColumn = (DBObject) o; - String name = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); - Map raceIdentifiersPerFleetName = loadRaceIdentifiers(dbRaceColumn); - for (Map.Entry e : raceIdentifiersPerFleetName.entrySet()) { - // null key for "default" fleet is not acceptable here - if (e.getKey() == null) { - logger.warning("Ignoring null fleet name while loading RaceColumn " + name); - } else { - series.getRaceColumnByName(name).setRaceIdentifier(series.getFleetByName(e.getKey()), e.getValue()); - } - } - } - } - - private List loadFleets(BasicDBList dbFleets) { - List result = new ArrayList(); - for (Object o : dbFleets) { - DBObject dbFleet = (DBObject) o; - Fleet fleet = loadFleet(dbFleet); - result.add(fleet); - } - return result; - } - - private Fleet loadFleet(DBObject dbFleet) { - Fleet result; - String name = (String) dbFleet.get(FieldNames.FLEET_NAME.name()); - Integer ordering = (Integer) dbFleet.get(FieldNames.FLEET_ORDERING.name()); - if (ordering == null) { - ordering = 0; - } - Integer colorAsInt = (Integer) dbFleet.get(FieldNames.FLEET_COLOR.name()); - Color color = null; - if (colorAsInt != null) { - int r = colorAsInt % 256; - int g = (colorAsInt / 256 ) % 256; - int b = (colorAsInt / 256 / 256) % 256; - color = new RGBColor(r, g, b); - } - result = new FleetImpl(name, ordering, color); - return result; - } - - @Override - public Map loadRaceIDToRegattaAssociations(RegattaRegistry regattaRegistry) { - DBCollection raceIDToRegattaCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name()); - Map result = new HashMap(); - for (DBObject o : raceIDToRegattaCollection.find()) { - Regatta regatta = regattaRegistry.getRegattaByName((String) o.get(FieldNames.REGATTA_NAME.name())); - if (regatta != null) { - result.put((String) o.get(FieldNames.RACE_ID_AS_STRING.name()), regatta); - } else { - logger.warning("Couldn't find regatta " + o.get(FieldNames.REGATTA_NAME.name()) - + ". Cannot restore race associations with this regatta."); - } - } - return result; - } - - @Override - public RaceLog loadRaceLog(RaceLogIdentifier identifier) { - RaceLog result = new RaceLogImpl(RaceLogImpl.class.getSimpleName(), identifier.getIdentifier()); - try { - BasicDBObject query = new BasicDBObject(); - query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(identifier.getIdentifier())); - loadRaceLogEvents(result, query); - - // query for events with deprecated identifier format... - query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(identifier.getDeprecatedIdentifier())); - - List eventsToMigrate = loadRaceLogEvents(result, query); - if (!eventsToMigrate.isEmpty()) { - // ... migrate them... - MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database, serviceFinderFactory)); - for (RaceLogEvent event : eventsToMigrate) { - event.accept(storeVisitor); - } - // ... and delete the old ones... - database.getCollection(CollectionNames.RACE_LOGS.name()).remove(query); - } - } catch (Throwable t) { - // something went wrong during DB access; report, then use empty new race log - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings."); - logger.log(Level.SEVERE, "loadRaceLog", t); - } - return result; - } - - private List loadRaceLogEvents(RaceLog targetRaceLog, BasicDBObject query) { - List result = new ArrayList<>(); - DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name()); - for (DBObject o : raceLog.find(query)) { - try { - RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name())); - if (raceLogEvent != null) { - targetRaceLog.load(raceLogEvent); - result.add(raceLogEvent); - } - } catch (IllegalStateException e) { - logger.log(Level.SEVERE, "Couldn't load race log event "+o+": "+e.getMessage(), e); - } - } - return result; - } - - public RaceLogEvent loadRaceLogEvent(DBObject dbObject) { - TimePoint logicalTimePoint = loadTimePoint(dbObject); - TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT); - Serializable id = (Serializable) dbObject.get(FieldNames.RACE_LOG_EVENT_ID.name()); - Integer passId = (Integer) dbObject.get(FieldNames.RACE_LOG_EVENT_PASS_ID.name()); - BasicDBList dbCompetitors = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name()); - List competitors = loadCompetitorsForRaceLogEvent(dbCompetitors); - final AbstractLogEventAuthor author; - String authorName = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name()); - Number authorPriority = (Number) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name()); - if (authorName != null && authorPriority != null) { - author = new LogEventAuthorImpl(authorName, authorPriority.intValue()); - } else { - author = LogEventAuthorImpl.createCompatibilityAuthor(); - } - - String eventClass = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_CLASS.name()); - if (eventClass.equals(RaceLogStartTimeEvent.class.getSimpleName())) { - return loadRaceLogStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogRaceStatusEvent.class.getSimpleName())) { - return loadRaceLogRaceStatusEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogFlagEvent.class.getSimpleName())) { - return loadRaceLogFlagEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogPassChangeEvent.class.getSimpleName())) { - return loadRaceLogPassChangeEvent(createdAt, author, logicalTimePoint, id, passId, competitors); - } else if (eventClass.equals(RaceLogCourseAreaChangedEvent.class.getSimpleName())) { - return loadRaceLogCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogCourseDesignChangedEvent.class.getSimpleName())) { - return loadRaceLogCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogFinishPositioningListChangedEvent.class.getSimpleName())) { - return loadRaceLogFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogFinishPositioningConfirmedEvent.class.getSimpleName())) { - return loadRaceLogFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogPathfinderEvent.class.getSimpleName())) { - return loadRaceLogPathfinderEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogGateLineOpeningTimeEvent.class.getSimpleName())) { - return loadRaceLogGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogStartProcedureChangedEvent.class.getSimpleName())) { - return loadRaceLogStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogProtestStartTimeEvent.class.getSimpleName())) { - return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogWindFixEvent.class.getSimpleName())) { - return loadRaceLogRaceLogWindFixEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogDeviceCompetitorMappingEvent.class.getSimpleName())) { - return loadRaceLogDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogDeviceMarkMappingEvent.class.getSimpleName())) { - return loadRaceLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogDenoteForTrackingEvent.class.getSimpleName())) { - return loadRaceLogDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogStartTrackingEvent.class.getSimpleName())) { - return loadRaceLogStartEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogRevokeEvent.class.getSimpleName())) { - return loadRaceLogRevokeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogRegisterCompetitorEvent.class.getSimpleName())) { - return loadRaceLogRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogDefineMarkEvent.class.getSimpleName())) { - return loadRaceLogDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { - return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(RaceLogAdditionalScoringInformationEvent.class.getSimpleName())) { - return loadRaceLogAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(FixedMarkPassingEvent.class.getSimpleName())){ - return loadRaceLogFixedMarkPassingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } else if (eventClass.equals(SuppressedMarkPassingsEvent.class.getSimpleName())){ - return loadRaceLogSuppressedMarkPassingsEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); - } - - throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); - } - - private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Wind wind = loadWind((DBObject) dbObject.get(FieldNames.WIND.name())); - return raceLogEventFactory.createWindFixEvent(createdAt, author, logicalTimePoint, id, competitors, passId, wind); - } - - private RaceLogEvent loadRaceLogDeviceCompetitorMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - DeviceIdentifier device = null; - try { - device = loadDeviceId(deviceIdentifierServiceFinder, - (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); - e.printStackTrace(); - } - Competitor mappedTo = baseDomainFactory.getExistingCompetitorById( - (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name())); - TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); - TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); - return raceLogEventFactory.createDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); - } - - private RaceLogEvent loadRaceLogDeviceMarkMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - DeviceIdentifier device = null; - try { - device = loadDeviceId(deviceIdentifierServiceFinder, - (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); - e.printStackTrace(); - } - //have to load complete mark, as no order is guaranteed for loading of racelog events - Mark mappedTo = loadMark((DBObject) dbObject.get(FieldNames.MARK.name())); - TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); - TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); - return raceLogEventFactory.createDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); - } - - private RaceLogEvent loadRaceLogDenoteForTrackingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name()); - BoatClass boatClass = baseDomainFactory.getOrCreateBoatClass((String) dbObject.get(FieldNames.BOAT_CLASS_NAME.name())); - Serializable raceId = (Serializable) dbObject.get(FieldNames.RACE_ID.name()); - return raceLogEventFactory.createDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, raceName, boatClass, raceId); - } - - private RaceLogEvent loadRaceLogStartEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - return raceLogEventFactory.createStartTrackingEvent(createdAt, author, logicalTimePoint, id, passId); - } - - private RaceLogEvent loadRaceLogRevokeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Serializable revokedEventId = Helpers.tryUuidConversion( - (Serializable) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name())); - String revokedEventType = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name()); - String revokedEventShortInfo = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name()); - String reason = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_REASON.name()); - return raceLogEventFactory.createRevokeEvent(createdAt, author, logicalTimePoint, id, passId, - revokedEventId, revokedEventType, revokedEventShortInfo, reason); - } - - private RaceLogEvent loadRaceLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Serializable competitorId = (Serializable) dbObject.get(FieldNames.RACE_LOG_COMPETITOR_ID.name()); - Competitor comp = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); - return raceLogEventFactory.createRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, comp); - } - - private RaceLogEvent loadRaceLogDefineMarkEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Mark mark = loadMark((DBObject) dbObject.get(FieldNames.RACE_LOG_MARK.name())); - return raceLogEventFactory.createDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, mark); - } - - private RaceLogEvent loadRaceLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name())); - TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); - return raceLogEventFactory.createCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, - deviceMappingEventId, closingTimePoint); - } - - private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Object additionalScoringInformationTypeInfo = dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()); - AdditionalScoringInformationType informationType = AdditionalScoringInformationType.UNKNOWN; - if (additionalScoringInformationTypeInfo != null) { - informationType = AdditionalScoringInformationType.valueOf(additionalScoringInformationTypeInfo.toString()); - } else { - logger.warning("Could not find additional scoring information attached to db log for " + dbObject.toString()); - } - return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId, informationType); - } - - private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME); - return raceLogEventFactory.createProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, protestStartTime); - } - - private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - RacingProcedureType type = RacingProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString()); - return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, type); - } - - private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Number gateLaunchStopTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name()); - Number golfDownTime = 0; - if (dbObject.containsField(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name())) { - golfDownTime = (Number) dbObject.get(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name()); - } - return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, competitors, - passId, gateLaunchStopTime == null ? null : gateLaunchStopTime.longValue(), golfDownTime.longValue()); - } - - private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - String pathfinderId = dbObject.get(FieldNames.RACE_LOG_PATHFINDER_ID.name()).toString(); - return raceLogEventFactory.createPathfinderEvent(createdAt, author, logicalTimePoint, id, competitors, passId, pathfinderId); - } - - private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); - CompetitorResults positionedCompetitors = null; - //When a confirmation event is loaded that does not contain the positioned competitors (this is the case for the ESS events in - //Singapore and Quingdao) then null should be set for the positionedCompetitors, which is evaluated later on. - if (dbPositionedCompetitorList != null) { - positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); - } - - return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); - } - - private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); - CompetitorResults positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); - - return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); - } - - private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors) { - return raceLogEventFactory.createPassChangeEvent(createdAt, author, logicalTimePoint, id, competitors, passId); - } - - private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name()); - CourseBase courseData = loadCourseData((BasicDBList) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()), courseName); - return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseData); - } - - private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - Serializable courseAreaId = (Serializable) dbObject.get(FieldNames.COURSE_AREA_ID.name()); - return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId); - } - - private RaceLogEvent loadRaceLogFixedMarkPassingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - TimePoint ofFixedPassing = loadTimePoint(dbObject, FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING); - Integer zeroBasedIndexOfWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_PASSED_WAYPOINT.name()); - return raceLogEventFactory.createFixedMarkPassingEvent(logicalTimePoint, author, id, competitors, passId, ofFixedPassing, zeroBasedIndexOfWaypoint); - } - - private RaceLogEvent loadRaceLogSuppressedMarkPassingsEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { - Integer zeroBasedIndexOfFirstSuppressedWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name()); - return raceLogEventFactory.createSuppressedMarkPassingsEvent(logicalTimePoint, author, id, competitors, passId, zeroBasedIndexOfFirstSuppressedWaypoint); - } - - private CompetitorResults loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) { - CompetitorResults positionedCompetitors = new CompetitorResultsImpl(); - for (Object object : dbPositionedCompetitorList) { - DBObject dbObject = (DBObject) object; - - Serializable competitorId = (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name()); - - String competitorName = (String) dbObject.get(FieldNames.COMPETITOR_DISPLAY_NAME.name()); - //The Competitor name is a new field in the list. Therefore the name might be null for existing events. In this case a standard name is set. - if (competitorName == null) { - competitorName = "loaded competitor"; - } - - //At this point we do not retrieve the competitor object since at any point in time, especially after a server restart, the DomainFactory and its competitor - //cache might be empty. But at this time the race log is loaded from database, so the competitor would be null. - //By not using the Competitor object retrieved from the DomainFactory we get completely independent from server restarts and the timepoint of loading - //competitors by tracking providers. - - MaxPointsReason maxPointsReason = MaxPointsReason.valueOf((String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())); - - Util.Triple positionedCompetitor = new Util.Triple(competitorId, competitorName, maxPointsReason); - positionedCompetitors.add(positionedCompetitor); - } - return positionedCompetitors; - } - - private List loadCompetitorsForRaceLogEvent(BasicDBList dbCompetitorList) { - List competitors = new ArrayList(); - for (Object object : dbCompetitorList) { - Serializable competitorId = (Serializable) object; - Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); - competitors.add(competitor); - } - return competitors; - } - - private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - Flags upperFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name())); - Flags lowerFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name())); - Boolean displayed = Boolean.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name())); - - if (upperFlag == null || lowerFlag == null || displayed == null) { - return null; - } - - return raceLogEventFactory.createFlagEvent(createdAt, author, logicalTimePoint, id, competitors, passId, upperFlag, lowerFlag, displayed); - } - - private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - TimePoint startTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_START_TIME); - return raceLogEventFactory.createStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, startTime); - } - - private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - RaceLogRaceStatus nextStatus = RaceLogRaceStatus.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name())); - return raceLogEventFactory.createRaceStatusEvent(createdAt, author, logicalTimePoint, id, competitors, passId, nextStatus); - } - - @Override - public RegattaLog loadRegattaLog(RegattaLikeIdentifier identifier) { - RegattaLog result = new RegattaLogImpl(RegattaLogImpl.class.getSimpleName(), identifier); - try { - BasicDBObject query = new BasicDBObject(); - query.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), identifier.getIdentifierType()); - query.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), identifier.getName()); - loadRegattaLogEvents(result, query, identifier); - } catch (Throwable t) { - // something went wrong during DB access; report, then use empty new regatta log - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data. Check MongoDB settings."); - logger.log(Level.SEVERE, "loadRegattaLog", t); - } - return result; - } - - private void loadRegattaLogEvents(RegattaLog targetRegattaLog, BasicDBObject query, RegattaLikeIdentifier regattaLogIdentifier) { - DBCollection collection = database.getCollection(CollectionNames.REGATTA_LOGS.name()); - for (DBObject o : collection.find(query)) { - try { - RegattaLogEvent event = loadRegattaLogEvent(o, regattaLogIdentifier); - if (event != null) { - targetRegattaLog.load(event); - } - } catch (IllegalStateException e) { - logger.log(Level.SEVERE, "Couldn't load regatta log event "+o+": "+e.getMessage(), e); - } - } - } - - private RegattaLogEvent loadRegattaLogEvent(DBObject o, RegattaLikeIdentifier regattaLogIdentifier) { - DBObject dbObject = (DBObject) o.get(FieldNames.REGATTA_LOG_EVENT.name()); - TimePoint logicalTimePoint = loadTimePoint(dbObject); - TimePoint createdAt = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_EVENT_CREATED_AT); - Serializable id = (Serializable) dbObject.get(FieldNames.REGATTA_LOG_EVENT_ID.name()); - final AbstractLogEventAuthor author; - String authorName = (String) dbObject.get(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name()); - Number authorPriority = (Number) dbObject.get(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name()); - author = new LogEventAuthorImpl(authorName, authorPriority.intValue()); - //CloseOpenEnded, DeviceCompMapping, DeviceMarkMapping, RegisterComp, Revoke - String eventClass = (String) dbObject.get(FieldNames.REGATTA_LOG_EVENT_CLASS.name()); - if (eventClass.equals(RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName())) { - return loadRegattaLogDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); - } else if (eventClass.equals(RegattaLogDeviceMarkMappingEvent.class.getSimpleName())) { - return loadRegattaLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); - } else if (eventClass.equals(RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { - return loadRegattaLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, dbObject); - } else if (eventClass.equals(RegattaLogRegisterCompetitorEvent.class.getSimpleName())) { - return loadRegattaLogRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, dbObject); - } else if (eventClass.equals(RegattaLogSetCompetitorTimeOnTimeFactorEvent.class.getSimpleName())) { - return loadRegattaLogSetCompetitorTimeOnTimeFactorEvent(createdAt, author, logicalTimePoint, id, dbObject); - } else if (eventClass.equals(RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent.class.getSimpleName())) { - return loadRegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent(createdAt, author, logicalTimePoint, id, dbObject); - } else if (eventClass.equals(RegattaLogRevokeEvent.class.getSimpleName())) { - return loadRegattaLogRevokeEvent(createdAt, author, logicalTimePoint, id, dbObject); - } - throw new IllegalStateException(String.format("Unknown RegattaLogEvent type %s", eventClass)); - } - - private Competitor getCompetitorByID(DBObject dbObject) { - Serializable competitorId = (Serializable) dbObject.get(FieldNames.REGATTA_LOG_COMPETITOR_ID.name()); - Competitor comp = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); - return comp; - } - - private RegattaLogEvent loadRegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent(TimePoint createdAt, - AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { - final Competitor comp = getCompetitorByID(dbObject); - final Double timeOnTimeFactor = (Double) dbObject.get(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name()); - return new RegattaLogSetCompetitorTimeOnTimeFactorEventImpl(createdAt, author, logicalTimePoint, id, comp, timeOnTimeFactor); - } - - private RegattaLogEvent loadRegattaLogSetCompetitorTimeOnTimeFactorEvent(TimePoint createdAt, - AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { - final Competitor comp = getCompetitorByID(dbObject); - final Double timeOnDistanceSecondsAllowancePerNauticalMile = (Double) dbObject.get(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name()); - final Duration timeOnDistanceAllowancePerNauticalMile = timeOnDistanceSecondsAllowancePerNauticalMile == null ? null : - new MillisecondsDurationImpl((long) (timeOnDistanceSecondsAllowancePerNauticalMile*1000)); - return new RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEventImpl(createdAt, author, logicalTimePoint, id, comp, timeOnDistanceAllowancePerNauticalMile); - } - - private RegattaLogRevokeEvent loadRegattaLogRevokeEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { - Serializable revokedEventId = Helpers.tryUuidConversion( - (Serializable) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name())); - String revokedEventType = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name()); - String revokedEventShortInfo = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name()); - String reason = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_REASON.name()); - return new RegattaLogRevokeEventImpl(createdAt, author, logicalTimePoint, id, - revokedEventId, revokedEventType, revokedEventShortInfo, reason); - } - - private RegattaLogRegisterCompetitorEvent loadRegattaLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { - Competitor comp = getCompetitorByID(dbObject); - return new RegattaLogRegisterCompetitorEventImpl(createdAt, author, logicalTimePoint, id, comp); - } - - private RegattaLogCloseOpenEndedDeviceMappingEvent loadRegattaLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, - AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { - Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get( - FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name())); - TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT); - return new RegattaLogCloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, id, - deviceMappingEventId, closingTimePoint); - } - - private RegattaLogDeviceMarkMappingEvent loadRegattaLogDeviceMarkMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, - TimePoint logicalTimePoint, Serializable id, DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { - DeviceIdentifier device = null; - try { - device = loadDeviceId(deviceIdentifierServiceFinder, - (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); - e.printStackTrace(); - } - //have to load complete mark, as no order is guaranteed for loading of racelog events - Mark mappedTo = loadMark((DBObject) dbObject.get(FieldNames.MARK.name())); - Triple times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, FieldNames.RACE_LOG_FROM, FieldNames.REGATTA_LOG_TO, FieldNames.RACE_LOG_TO); - final TimePoint from = times.getA(); - final TimePoint to = times.getB(); - final RegattaLogDeviceMarkMappingEventImpl result = new RegattaLogDeviceMarkMappingEventImpl(createdAt, author, logicalTimePoint, id, mappedTo, device, from, to); - final boolean needsMigration = times.getC(); - if (needsMigration) { - // remove old version of mapping event - WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject); - assert removeResult.getN() == 1; - // and then insert using the fixed storage implementation - new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result); - } - return result; - } - - /** - * Loads a from and a to time point from fromField and toField of dbObject. - * If the fromField is not found, the fromFieldDeprecated is attempted. If found, migration - * is deemed necessary, expressed by returning true in the {@link Triple#getC()} component of the result. - * Same for the to-field. - * - * @return the from-time in {@link Triple#getA()}, the to-time in {@link Triple#getB()} and whether or not migration is - * necessary because a value was only found in a deprecated field in {@link Triple#getC()}. - */ - private Triple loadFromToTimePoint(final DBObject dbObject, FieldNames fromField, FieldNames fromFieldDeprecated, - FieldNames toField, FieldNames toFieldDeprecated) { - boolean needsMigration = false; - TimePoint from = loadTimePoint(dbObject, fromField); - if (from == null) { - // see bug 2733: erroneously, some records before the fix were written using RACE_LOG_FROM instead of REGATTA_LOG_FROM - // If such a case is found here, migrate the record. - from = loadTimePoint(dbObject, fromFieldDeprecated); - if (from != null) { - needsMigration = true; - } - } - TimePoint to = loadTimePoint(dbObject, toField); - if (to == null) { - // see bug 2733: erroneously, some records before the fix were written using RACE_LOG_FROM instead of REGATTA_LOG_FROM - // If such a case is found here, migrate the record. - to = loadTimePoint(dbObject, toFieldDeprecated); - if (to != null) { - needsMigration = true; - } - } - return new Triple<>(from, to, needsMigration); - } - - private RegattaLogDeviceCompetitorMappingEvent loadRegattaLogDeviceCompetitorMappingEvent(TimePoint createdAt, - AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, final DBObject dbObject, - RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { - DeviceIdentifier device = null; - try { - device = loadDeviceId(deviceIdentifierServiceFinder, - (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); - e.printStackTrace(); - } - Competitor mappedTo = baseDomainFactory.getExistingCompetitorById( - (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name())); - Triple times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, FieldNames.RACE_LOG_FROM, FieldNames.REGATTA_LOG_TO, FieldNames.RACE_LOG_TO); - final TimePoint from = times.getA(); - final TimePoint to = times.getB(); - final boolean needsMigration = times.getC(); - final RegattaLogDeviceCompetitorMappingEventImpl result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, author, logicalTimePoint, id, mappedTo, device, from, to); - if (needsMigration) { - // remove old version of mapping event - WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject); - assert removeResult.getN() == 1; - // and then insert using the fixed storage implementation - new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result); - } - return result; - } - - /** - * The old field name WAYPOINT_PASSINGSIDE has been replaced by WAYPOINT_PASSINGINSTRUCTIONS. If a race with the old - * field is loaded, the value of PASSINGSIDE is used and then migrated to PASSINGINSTRUCTION. If the first or last - * Waypoint has the PassingInstructions Gate, it is transfered to Line. - * - */ - @SuppressWarnings("deprecation") // Used to migrate from PASSINGSIDE to the new PASSINGINSTRUCTIONS - private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) { - if (courseName == null) { - courseName = "Course"; - } - CourseBase courseData = new CourseDataImpl(courseName); - int i = 0; - for (Object object : dbCourseList) { - DBObject dbObject = (DBObject) object; - Waypoint waypoint = null; - PassingInstruction passingInstructions = null; - String waypointPassingInstruction = (String) dbObject.get(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name()); - if (waypointPassingInstruction == null) { - waypointPassingInstruction = (String) dbObject.get(FieldNames.WAYPOINT_PASSINGSIDE.name()); - if(waypointPassingInstruction != null) { - logger.info("Migrating PassingInstruction "+waypointPassingInstruction+" to field name WAYPOINT_PASSINGINSTRUCTIONS"); - if((i==0||i==dbCourseList.size()-1)&&waypointPassingInstruction.toLowerCase().equals("gate")){ - logger.warning("Changing PassingInstructions of first or last Waypoint from Gate to Line."); - waypointPassingInstruction = "Line"; - } - dbObject.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), waypointPassingInstruction); - dbObject.removeField(FieldNames.WAYPOINT_PASSINGSIDE.name()); - } - } - if (waypointPassingInstruction != null) { - passingInstructions = PassingInstruction.valueOfIgnoringCase(waypointPassingInstruction); - } - ControlPoint controlPoint = loadControlPoint((DBObject) dbObject.get(FieldNames.CONTROLPOINT.name())); - if (passingInstructions == null) { - waypoint = new WaypointImpl(controlPoint); - } else { - waypoint = new WaypointImpl(controlPoint, passingInstructions); - } - courseData.addWaypoint(i++, waypoint); - } - return courseData; - } - - private ControlPoint loadControlPoint(DBObject dbObject) { - String controlPointClass = (String) dbObject.get(FieldNames.CONTROLPOINT_CLASS.name()); - ControlPoint controlPoint = null; - if (controlPointClass != null) { - if (controlPointClass.equals(Mark.class.getSimpleName())) { - Mark mark = loadMark((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); - controlPoint = mark; - } else if(controlPointClass.equals("Gate")) { - ControlPointWithTwoMarks cpwtm = loadControlPointWithTwoMarks((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); - dbObject.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName()); - controlPoint = cpwtm; - } else if (controlPointClass.equals(ControlPointWithTwoMarks.class.getSimpleName())) { - ControlPointWithTwoMarks cpwtm = loadControlPointWithTwoMarks((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); - controlPoint = cpwtm; - } - } - return controlPoint; - } - - /** - * Checks for the old GATE fields and migrates them to the new CONTROLPOINTWITHTWOMARKS fields. - */ - @SuppressWarnings("deprecation") // Used for migrating old races - private ControlPointWithTwoMarks loadControlPointWithTwoMarks(DBObject dbObject) { - String controlPointName = (String) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name()); - if (controlPointName == null) { - controlPointName = (String) dbObject.get(FieldNames.GATE_NAME.name()); - logger.info("Migrating name of ControlPointWithTwoMarks " + controlPointName - + " from GATE_NAME to new field CONTROLPOINTWITHTWOMARKS_NAME."); - dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), controlPointName); - dbObject.removeField(FieldNames.GATE_NAME.name()); - } - Serializable controlPointId = (Serializable) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name()); - if (controlPointId == null) { - controlPointId = (Serializable) dbObject.get(FieldNames.GATE_ID.name()); - logger.info("Migrating id of ControlPointWithTwoMarks " + controlPointName - + " from old field GATE_ID to CONTROLPOINTWITHTWOMARKS_ID."); - dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), controlPointId); - dbObject.removeField(FieldNames.GATE_ID.name()); - } - DBObject dbLeft = (DBObject) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name()); - if (dbLeft == null) { - dbLeft = (DBObject) dbObject.get(FieldNames.GATE_LEFT.name()); - logger.info("Migrating left Mark of ControlPointWithTwoMarks " + controlPointName + " from old field GATE_LEFT to CONTROLPOINTWITHTWOMARKS_LEFT"); - dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), dbLeft); - dbObject.removeField(FieldNames.GATE_LEFT.name()); - } - Mark leftMark = loadMark(dbLeft); - DBObject dbRight = (DBObject) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name()); - if (dbRight == null) { - dbRight = (DBObject) dbObject.get(FieldNames.GATE_RIGHT.name()); - logger.info("Migrating right Mark of ControlPointWithTwoMarks " + controlPointName + " from old field GATE_RIGHT to CONTROLPOINTWITHTWOMARKS_RIGHT"); - dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), dbRight); - dbObject.removeField(FieldNames.GATE_RIGHT.name()); - } - Mark rightMark = loadMark(dbRight); - ControlPointWithTwoMarks gate = baseDomainFactory.createControlPointWithTwoMarks(controlPointId, leftMark, rightMark, controlPointName); - return gate; - } - - private Mark loadMark(DBObject dbObject) { - Serializable markId = (Serializable) dbObject.get(FieldNames.MARK_ID.name()); - String markColor = (String) dbObject.get(FieldNames.MARK_COLOR.name()); - String markName = (String) dbObject.get(FieldNames.MARK_NAME.name()); - String markPattern = (String) dbObject.get(FieldNames.MARK_PATTERN.name()); - String markShape = (String) dbObject.get(FieldNames.MARK_SHAPE.name()); - Object markTypeRaw = dbObject.get(FieldNames.MARK_TYPE.name()); - MarkType markType = markTypeRaw == null ? null : MarkType.valueOf((String) markTypeRaw); - - Mark mark = baseDomainFactory.getOrCreateMark(markId, markName, markType, markColor, markShape, markPattern); - return mark; - } - - @Override - public Collection loadAllCompetitors() { - ArrayList result = new ArrayList(); - DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); - try { - for (DBObject o : collection.find()) { - JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(o))); - Competitor c = competitorDeserializer.deserialize(json); - result.add(c); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load competitors."); - logger.log(Level.SEVERE, "loadCompetitors", e); - } - return result; - } - - @Override - public Iterable> loadAllDeviceConfigurations() { - Map result = new HashMap<>(); - DBCollection configurationCollection = database.getCollection(CollectionNames.CONFIGURATIONS.name()); - - try { - for (DBObject dbObject : configurationCollection.find()) { - Util.Pair entry = loadConfigurationEntry(dbObject); - result.put(entry.getA(), entry.getB()); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load configurations."); - logger.log(Level.SEVERE, "loadAllDeviceConfigurations", e); - } - - return result.entrySet(); - } - - private Util.Pair loadConfigurationEntry(DBObject dbObject) { - DBObject matcherObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_MATCHER.name()); - DBObject configObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_CONFIG.name()); - return new Util.Pair(loadConfigurationMatcher(matcherObject), - loadConfiguration(configObject)); - } - - private DeviceConfigurationMatcher loadConfigurationMatcher(DBObject matcherObject) { - DeviceConfigurationMatcherType type = DeviceConfigurationMatcherType.valueOf( - matcherObject.get(FieldNames.CONFIGURATION_MATCHER_TYPE.name()).toString()); - List clientIdentifiers = new ArrayList(); - BasicDBList clientIdentifiersObject = (BasicDBList) matcherObject.get(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name()); - if (clientIdentifiersObject != null) { - for (Object clientIdentifier : clientIdentifiersObject) { - clientIdentifiers.add(clientIdentifier.toString()); - } - } - return baseDomainFactory.getOrCreateDeviceConfigurationMatcher(type, clientIdentifiers); - } - - private DeviceConfiguration loadConfiguration(DBObject configObject) { - DeviceConfiguration configuration = null; - try { - JsonDeserializer deserializer = DeviceConfigurationJsonDeserializer.create(); - JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(configObject))); - configuration = deserializer.deserialize(json); - } catch (JsonDeserializationException | ParseException e) { - logger.log(Level.SEVERE, "Error parsing configuration object from MongoDB, falling back to empty configuration."); - logger.log(Level.SEVERE, "loadConfiguration", e); - configuration = new DeviceConfigurationImpl(new RegattaConfigurationImpl()); - } - return configuration; - } - - private DeviceIdentifier loadDeviceId( - TypeBasedServiceFinder deviceIdentifierServiceFinder, DBObject deviceId) - throws TransformationException, NoCorrespondingServiceRegisteredException { - String deviceType = (String) deviceId.get(FieldNames.DEVICE_TYPE.name()); - Object deviceTypeId = deviceId.get(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name()); - String stringRepresentation = (String) deviceId.get(FieldNames.DEVICE_STRING_REPRESENTATION.name()); - - try { - return deviceIdentifierServiceFinder.findService(deviceType).deserialize(deviceTypeId, deviceType, stringRepresentation); - } catch (TransformationException e) { - return new PlaceHolderDeviceIdentifierSerializationHandler().deserialize( - stringRepresentation, deviceType, stringRepresentation); - } - } - - @Override - public Map> loadResultUrls() { - Map> resultUrls = new HashMap<>(); - DBCollection resultUrlCollection = database.getCollection(CollectionNames.RESULT_URLS.name()); - for (DBObject dbObject : resultUrlCollection.find()) { - String providerName = (String) dbObject.get(FieldNames.RESULT_PROVIDERNAME.name()); - String urlString = (String) dbObject.get(FieldNames.RESULT_URL.name()); - URL url; - try { - url = new URL(urlString); - } catch (MalformedURLException e) { - logger.log(Level.SEVERE, "Failed to parse result Url String: " + urlString + ". Did not load url!"); - continue; - } - if (!resultUrls.containsKey(providerName)) { - resultUrls.put(providerName, new HashSet()); - } - Set set = resultUrls.get(providerName); - set.add(url); - } - return resultUrls; - } -} +package com.sap.sailing.domain.persistence.impl; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bson.types.ObjectId; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import com.mongodb.MongoException; +import com.mongodb.WriteResult; +import com.mongodb.util.JSON; +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.race.CompetitorResults; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogEventRestoreFactory; +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; +import com.sap.sailing.domain.abstractlog.race.scoring.AdditionalScoringInformationType; +import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnTimeFactorEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; +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.ControlPointWithTwoMarks; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RegattaRegistry; +import com.sap.sailing.domain.base.RemoteSailingServerReference; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationImpl; +import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; +import com.sap.sailing.domain.base.impl.CourseDataImpl; +import com.sap.sailing.domain.base.impl.EventImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.VenueImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.MarkType; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.WindImpl; +import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections; +import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener; +import com.sap.sailing.domain.leaderboard.EventResolver; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; +import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.impl.DelayedLeaderboardCorrectionsImpl; +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; +import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; +import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.racelogtracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.ranking.RankingMetricConstructor; +import com.sap.sailing.domain.ranking.RankingMetricsFactory; +import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; +import com.sap.sailing.domain.regattalog.RegattaLogStore; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer; +import com.sap.sse.common.Color; +import com.sap.sse.common.Duration; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.TypeBasedServiceFinder; +import com.sap.sse.common.TypeBasedServiceFinderFactory; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.impl.MillisecondsDurationImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.RGBColor; +import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; + +public class DomainObjectFactoryImpl implements DomainObjectFactory { + private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName()); + private final CompetitorJsonDeserializer competitorDeserializer; + + private final DB database; + + private RaceLogEventRestoreFactory raceLogEventFactory; + private final DomainFactory baseDomainFactory; + private final TypeBasedServiceFinder deviceIdentifierServiceFinder; + private final TypeBasedServiceFinderFactory serviceFinderFactory; + + /** + * Uses null as the {@link TypeBasedServiceFinder}, meaning that no {@link DeviceIdentifier}s can be loaded + * using this instance of a {@link DomainObjectFactory}. + */ + public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory) { + this(db, baseDomainFactory, /* deviceTypeServiceFinder */ null); + } + + public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory) { + super(); + this.serviceFinderFactory = serviceFinderFactory; + if (serviceFinderFactory != null) { + this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); + this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); + } else { + this.deviceIdentifierServiceFinder = null; + } + this.baseDomainFactory = baseDomainFactory; + this.competitorDeserializer = CompetitorJsonDeserializer.create(baseDomainFactory); + this.database = db; + this.raceLogEventFactory = RaceLogEventRestoreFactory.INSTANCE; + } + + @Override + public DomainFactory getBaseDomainFactory() { + return baseDomainFactory; + } + + public Wind loadWind(DBObject object) { + return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object)); + } + + public Position loadPosition(DBObject object) { + Number latNumber = (Number) object.get(FieldNames.LAT_DEG.name()); + Double lat = latNumber == null ? null : latNumber.doubleValue(); + Number lngNumber = (Number) object.get(FieldNames.LNG_DEG.name()); + Double lng = lngNumber == null ? null : lngNumber.doubleValue(); + if (lat != null && lng != null) { + return new DegreePosition(lat, lng); + } else { + return null; + } + } + + public static TimePoint loadTimePoint(DBObject object, String fieldName) { + TimePoint result = null; + Number timePointAsNumber = (Number) object.get(fieldName); + if (timePointAsNumber != null) { + result = new MillisecondsTimePoint(timePointAsNumber.longValue()); + } + return result; + } + + public static TimePoint loadTimePoint(DBObject object, FieldNames field) { + return loadTimePoint(object, field.name()); + } + + public static TimeRange loadTimeRange(DBObject object, FieldNames field) { + DBObject timeRangeObj = (DBObject) object.get(field.name()); + if (timeRangeObj == null) { + return null; + } + TimePoint from = loadTimePoint(timeRangeObj, FieldNames.FROM_MILLIS); + TimePoint to = loadTimePoint(timeRangeObj, FieldNames.TO_MILLIS); + return new TimeRangeImpl(from, to); + } + /** + * Loads a {@link TimePoint} on the given object at {@link FieldNames#TIME_AS_MILLIS}. + */ + public TimePoint loadTimePoint(DBObject object) { + return loadTimePoint(object, FieldNames.TIME_AS_MILLIS); + } + + public SpeedWithBearing loadSpeedWithBearing(DBObject object) { + return new KnotSpeedWithBearingImpl(((Number) object.get(FieldNames.KNOT_SPEED.name())).doubleValue(), + new DegreeBearingImpl(((Number) object.get(FieldNames.DEGREE_BEARING.name())).doubleValue())); + } + + @Override + public RaceIdentifier loadRaceIdentifier(DBObject dbObject) { + RaceIdentifier result = null; + String regattaName = (String) dbObject.get(FieldNames.EVENT_NAME.name()); + String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name()); + if (regattaName != null && raceName != null) { + result = new RegattaNameAndRaceName(regattaName, raceName); + } + return result; + } + + private void ensureIndicesOnWindTracks(DBCollection windTracks) { + windTracks.createIndex(new BasicDBObject(FieldNames.RACE_ID.name(), 1)); // for new programmatic access + windTracks.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1)); // for export or human look-up + // for legacy access to not yet migrated fixes + windTracks.createIndex(new BasicDBObjectBuilder().add(FieldNames.EVENT_NAME.name(), 1) + .add(FieldNames.RACE_NAME.name(), 1).get()); + // Unique index + try { + windTracks.createIndex( + new BasicDBObjectBuilder().add(FieldNames.RACE_ID.name(), 1) + .add(FieldNames.WIND_SOURCE_NAME.name(), 1).add(FieldNames.WIND_SOURCE_ID.name(), 1) + .add(FieldNames.WIND.name() + "." + FieldNames.TIME_AS_MILLIS.name(), 1).get(), + new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).get()); + } catch (MongoException exception) { + + if (exception.getCode() == 10092) { + logger.warning(String.format( + "Setting the unique index on the %s collection failed because you have too many duplicates. " + + "This leads to the mongo error code %s and the following message: %s \nTo fix this follow " + + "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/cook-book#Remove-" + + "duplicates-from-WIND_TRACK-collection", CollectionNames.WIND_TRACKS.name(), + exception.getCode(), exception.getMessage())); + } else { + logger.severe(String.format( + "Setting the unique index on the %s collection failed with error code %s and message: %s", + CollectionNames.WIND_TRACKS.name(), exception.getCode(), exception.getMessage())); + } + } + } + + @Override + public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) { + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); + Leaderboard result = null; + try { + BasicDBObject query = new BasicDBObject(); + query.put(FieldNames.LEADERBOARD_NAME.name(), name); + for (DBObject o : leaderboardCollection.find(query)) { + result = loadLeaderboard(o, regattaRegistry, /* leaderboardRegistry */ null, /* groupForMetaLeaderboard */ null); + } + } catch (Exception e) { + // something went wrong during DB access; report, then use empty new wind track + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard "+name+"."); + logger.log(Level.SEVERE, "loadLeaderboard", e); + } + return result; + } + + @Override + public Iterable getAllLeaderboards(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); + Set result = new HashSet(); + try { + for (DBObject o : leaderboardCollection.find()) { + final Leaderboard loadedLeaderboard = loadLeaderboard(o, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null); + if (loadedLeaderboard != null) { + result.add(loadedLeaderboard); + } + } + } catch (Exception e) { + // something went wrong during DB access; report, then use empty new wind track + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards."); + logger.log(Level.SEVERE, "getAllLeaderboards", e); + } + return result; + } + + /** + * If the DBObject has a field {@link FieldNames#REGATTA_NAME} then the object represents a + * {@link RegattaLeaderboard}. Otherwise, a {@link FlexibleLeaderboard} will be loaded. + * + * @param leaderboardRegistry + * if not null, then before creating and loading the leaderboard it is looked up in this + * registry and only loaded if not found there. If leaderboardRegistry is null, + * the leaderboard is loaded in any case. If the leaderboard is loaded and + * leaderboardRegistry is not null, the leaderboard loaded is + * {@link LeaderboardRegistry#addLeaderboard(Leaderboard) added to the registry}. + * @param groupForMetaLeaderboard + * if not null, a {@link LeaderboardGroupMetaLeaderboard} instance is created and set as the + * group's {@link LeaderboardGroup#setOverallLeaderboard(Leaderboard) overall leaderboard} + * + * @return null in case the leaderboard couldn't be loaded, e.g., because the regatta referenced by a + * {@link RegattaLeaderboard} cannot be found; the leaderboard loaded or found in + * leaderboardRegistry, otherwise + */ + private Leaderboard loadLeaderboard(DBObject dbLeaderboard, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry, + LeaderboardGroup groupForMetaLeaderboard) { + Leaderboard result = null; + String leaderboardName = (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()); + if (leaderboardRegistry != null) { + result = leaderboardRegistry.getLeaderboardByName(leaderboardName); + } + if (result == null) { + ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS); + String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name()); + if (groupForMetaLeaderboard != null) { + result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard, + loadScoringScheme(dbLeaderboard), resultDiscardingRule); + groupForMetaLeaderboard.setOverallLeaderboard(result); + } else if (regattaName == null) { + result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule); + } else { + result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard, resultDiscardingRule, regattaRegistry); + } + if (result != null) { + final Leaderboard finalResult = result; + finalResult.setDisplayName((String) dbLeaderboard.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name())); + + DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory); + final boolean[] needsMigration = new boolean[1]; + loadedLeaderboardCorrections.addLeaderboardCorrectionsResolvedListener(new LeaderboardCorrectionsResolvedListener() { + @Override + public void correctionsResolved(DelayedLeaderboardCorrections delayedLeaderboardCorrections) { + if (needsMigration[0]) { + new MongoObjectFactoryImpl(database).storeLeaderboard(finalResult); + } + } + }); + needsMigration[0] = loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection()) || needsMigration[0]; + needsMigration[0] = loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections) || needsMigration[0]; + loadColumnFactors(dbLeaderboard, result); + // add the leaderboard to the registry + if (leaderboardRegistry != null) { + leaderboardRegistry.addLeaderboard(result); + logger.info("loaded leaderboard "+result.getName()+" into "+leaderboardRegistry); + } + } + } + return result; + } + + private void loadColumnFactors(DBObject dbLeaderboard, Leaderboard result) { + DBObject dbColumnFactors = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMN_FACTORS.name()); + if (dbColumnFactors != null) { + for (String encodedRaceColumnName : dbColumnFactors.keySet()) { + double factor = ((Number) dbColumnFactors.get(encodedRaceColumnName)).doubleValue(); + String raceColumnName = MongoUtils.unescapeDollarAndDot(encodedRaceColumnName); + final RaceColumn raceColumn = result.getRaceColumnByName(raceColumnName); + if (raceColumn != null) { + raceColumn.setFactor(factor); + } else { + logger.warning("Expected to find race column named "+raceColumnName+" in leaderboard "+result.getName()+ + " to apply column factor "+factor+", but the race column wasn't found. Ignoring factor."); + } + } + } + } + + private boolean loadSuppressedCompetitors(DBObject dbLeaderboard, + DelayedLeaderboardCorrections loadedLeaderboardCorrections) { + final boolean needsMigration; + BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name()); + if (dbSuppressedCompetitorNames != null) { + needsMigration = true; + for (Object escapedCompetitorName : dbSuppressedCompetitorNames) { + loadedLeaderboardCorrections.suppressCompetitorByName(MongoUtils.unescapeDollarAndDot((String) escapedCompetitorName)); + } + } else { + needsMigration = false; + } + BasicDBList dbSuppressedCompetitorIDs = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name()); + if (dbSuppressedCompetitorIDs != null) { + for (Object competitorId : dbSuppressedCompetitorIDs) { + loadedLeaderboardCorrections.suppressCompetitorById((Serializable) competitorId); + } + } + return needsMigration; + } + + /** + * @param dbObject expects to find a field identified by field which holds a {@link BasicDBList} + */ + private ThresholdBasedResultDiscardingRule loadResultDiscardingRule(DBObject dbObject, FieldNames field) { + BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) dbObject.get(field.name()); + final ThresholdBasedResultDiscardingRule result; + if (dbDiscardIndexResultsStartingWithHowManyRaces == null) { + result = null; + } else { + int[] discardIndexResultsStartingWithHowManyRaces = new int[dbDiscardIndexResultsStartingWithHowManyRaces + .size()]; + int i = 0; + for (Object discardingThresholdAsObject : dbDiscardIndexResultsStartingWithHowManyRaces) { + discardIndexResultsStartingWithHowManyRaces[i++] = (Integer) discardingThresholdAsObject; + } + result = new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces); + } + return result; + } + + /** + * @return null if the regatta cannot be resolved; otherwise the leaderboard for the regatta specified + */ + private RegattaLeaderboard loadRegattaLeaderboard(String leaderboardName, String regattaName, DBObject dbLeaderboard, + ThresholdBasedResultDiscardingRule resultDiscardingRule, RegattaRegistry regattaRegistry) { + RegattaLeaderboard result = null; + Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName)); + if (regatta == null) { + logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard."); + } else { + result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); + result.setName(leaderboardName); + } + return result; + } + + private RaceLogStore getRaceLogStore() { + return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore( + new MongoObjectFactoryImpl(database, serviceFinderFactory), this); + } + + private RegattaLogStore getRegattaLogStore() { + return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore( + new MongoObjectFactoryImpl(database, serviceFinderFactory), this); + } + + private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard, + ThresholdBasedResultDiscardingRule resultDiscardingRule) { + final FlexibleLeaderboardImpl result; + BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name()); + if (dbRaceColumns == null) { + // this was probably an orphaned overall leaderboard + logger.warning("Probably found orphan overall leaderboard named " + + dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring."); + result = null; + } else { + final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard); + + Serializable courseAreaId = (Serializable) dbLeaderboard.get(FieldNames.COURSE_AREA_ID.name()); + CourseArea courseArea = null; + if (courseAreaId != null) { + UUID courseAreaUuid = UUID.fromString(courseAreaId.toString()); + courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid); + } + + result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(), + (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()), + resultDiscardingRule, scoringScheme, courseArea); + // For a FlexibleLeaderboard, there should be only the default fleet for any race column + for (Object dbRaceColumnAsObject : dbRaceColumns) { + BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject; + String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); + + RaceColumn raceColumn = result.addRaceColumn(columnName, + (Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name())); + + Map raceIdentifiers = loadRaceIdentifiers(dbRaceColumn); + RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(result.getFleet(null).getName()); + if (defaultFleetRaceIdentifier == null) { + // Backward compatibility + defaultFleetRaceIdentifier = raceIdentifiers.get(null); + } + if (defaultFleetRaceIdentifier != null) { + Fleet defaultFleet = result.getFleet(null); + if (defaultFleet != null) { + + raceColumn.setRaceIdentifier(defaultFleet, defaultFleetRaceIdentifier); + } else { + // leaderboard has no default fleet; don't know what to do with default RaceIdentifier + logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier + + " for default fleet for leaderboard " + result.getName() + + " because no default fleet was found in leaderboard"); + } + } + + } + } + return result; + } + + private ScoringScheme loadScoringScheme(DBObject dbLeaderboard) { + ScoringSchemeType scoringSchemeType = getScoringSchemeType(dbLeaderboard); + final ScoringScheme scoringScheme = baseDomainFactory.createScoringScheme(scoringSchemeType); + return scoringScheme; + } + + private boolean loadLeaderboardCorrections(DBObject dbLeaderboard, DelayedLeaderboardCorrections correctionsToUpdate, + SettableScoreCorrection scoreCorrectionToUpdate) { + boolean needsMigration = false; + DBObject carriedPoints = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_CARRIED_POINTS.name()); + if (carriedPoints != null) { + needsMigration = true; + for (String escapedCompetitorName : carriedPoints.keySet()) { + Double carriedPointsForCompetitor = ((Number) carriedPoints.get(escapedCompetitorName)).doubleValue(); + if (carriedPointsForCompetitor != null) { + correctionsToUpdate.setCarriedPointsByName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), carriedPointsForCompetitor); + } + } + } + BasicDBList carriedPointsById = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name()); + if (carriedPointsById != null) { + for (Object o : carriedPointsById) { + DBObject competitorIdAndCarriedPoints = (DBObject) o; + Serializable competitorId = (Serializable) competitorIdAndCarriedPoints.get(FieldNames.COMPETITOR_ID.name()); + Double carriedPointsForCompetitor = ((Number) competitorIdAndCarriedPoints + .get(FieldNames.LEADERBOARD_CARRIED_POINTS.name())).doubleValue(); + if (carriedPointsForCompetitor != null) { + correctionsToUpdate.setCarriedPointsByID(competitorId, carriedPointsForCompetitor); + } + } + } + DBObject dbScoreCorrection = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name()); + if (dbScoreCorrection.containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name())) { + scoreCorrectionToUpdate.setTimePointOfLastCorrectionsValidity( + new MillisecondsTimePoint((Long) dbScoreCorrection.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name()))); + dbScoreCorrection.removeField(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name()); + } + if (dbScoreCorrection.containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name())) { + scoreCorrectionToUpdate.setComment((String) dbScoreCorrection.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name())); + dbScoreCorrection.removeField(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name()); + } + for (String escapedRaceColumnName : dbScoreCorrection.keySet()) { + // deprecated style: a DBObject per race where the keys are the escaped competitor names + // new style: a BasicDBList per race where each entry is a DBObject with COMPETITOR_ID and + // LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON and LEADERBOARD_CORRECTED_SCORE fields each + DBObject dbScoreCorrectionForRace = (DBObject) dbScoreCorrection.get(escapedRaceColumnName); + final RaceColumn raceColumn = correctionsToUpdate.getLeaderboard().getRaceColumnByName(MongoUtils.unescapeDollarAndDot(escapedRaceColumnName)); + if (raceColumn != null) { + if (dbScoreCorrectionForRace instanceof BasicDBList) { + for (Object o : (BasicDBList) dbScoreCorrectionForRace) { + DBObject dbScoreCorrectionForCompetitorInRace = (DBObject) o; + Serializable competitorId = (Serializable) dbScoreCorrectionForCompetitorInRace + .get(FieldNames.COMPETITOR_ID.name()); + if (dbScoreCorrectionForCompetitorInRace + .containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())) { + correctionsToUpdate.setMaxPointsReasonByID(competitorId, raceColumn, MaxPointsReason + .valueOf((String) dbScoreCorrectionForCompetitorInRace + .get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name()))); + } + if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_CORRECTED_SCORE + .name())) { + final Double leaderboardCorrectedScore = ((Number) dbScoreCorrectionForCompetitorInRace + .get(FieldNames.LEADERBOARD_CORRECTED_SCORE.name())).doubleValue(); + correctionsToUpdate.correctScoreByID(competitorId, raceColumn, + (Double) leaderboardCorrectedScore); + } + } + } else { + needsMigration = true; + for (String competitorName : dbScoreCorrectionForRace.keySet()) { + DBObject dbScoreCorrectionForCompetitorInRace = (DBObject) dbScoreCorrectionForRace + .get(competitorName); + if (dbScoreCorrectionForCompetitorInRace + .containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())) { + correctionsToUpdate.setMaxPointsReasonByName(MongoUtils + .unescapeDollarAndDot(competitorName), raceColumn, MaxPointsReason + .valueOf((String) dbScoreCorrectionForCompetitorInRace + .get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name()))); + } + if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_CORRECTED_SCORE + .name())) { + final Double leaderboardCorrectedScore = ((Number) dbScoreCorrectionForCompetitorInRace + .get(FieldNames.LEADERBOARD_CORRECTED_SCORE.name())).doubleValue(); + correctionsToUpdate.correctScoreByName(MongoUtils.unescapeDollarAndDot(competitorName), + raceColumn, (Double) leaderboardCorrectedScore); + } + } + } + } else { + logger.warning("Couldn't find race column " + MongoUtils.unescapeDollarAndDot(escapedRaceColumnName) + + " in leaderboard " + correctionsToUpdate.getLeaderboard().getName()); + } + } + DBObject competitorDisplayNames = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name()); + // deprecated style: a DBObject whose keys are the escaped competitor names + // new style: a BasicDBList whose entries are DBObjects with COMPETITOR_ID and COMPETITOR_DISPLAY_NAME fields + if (competitorDisplayNames != null) { + if (competitorDisplayNames instanceof BasicDBList) { + for (Object o : (BasicDBList) competitorDisplayNames) { + DBObject competitorDisplayName = (DBObject) o; + final Serializable competitorId = (Serializable) competitorDisplayName.get(FieldNames.COMPETITOR_ID.name()); + final String displayName = (String) competitorDisplayName.get(FieldNames.COMPETITOR_DISPLAY_NAME.name()); + correctionsToUpdate.setDisplayNameByID(competitorId, displayName); + } + } else { + needsMigration = true; + for (String escapedCompetitorName : competitorDisplayNames.keySet()) { + correctionsToUpdate.setDisplayNameByName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), + (String) competitorDisplayNames.get(escapedCompetitorName)); + } + } + } + return needsMigration; + } + + /** + * Expects a DBObject under the key {@link FieldNames#RACE_IDENTIFIERS} whose keys are the fleet names and whose + * values are the race identifiers as DBObjects (see {@link #loadRaceIdentifier(DBObject)}). If legacy DB instances + * have a {@link RaceIdentifier} that is not associated with a fleet name, it may be stored directly in the + * dbRaceColumn. In this case, it is returned with null as the fleet name key. + * + * @return a map with fleet names as key and the corresponding fleet's race identifier as value; the special + * null key is used to identify a "default fleet" for backward compatibility with stored + * leaderboards which don't know about fleets yet; this key should be mapped to the leaderboard's default + * fleet. + */ + private Map loadRaceIdentifiers(DBObject dbRaceColumn) { + Map result = new HashMap(); + // try to load a deprecated single race identifier to associate with the default fleet: + RaceIdentifier singleLegacyRaceIdentifier = loadRaceIdentifier(dbRaceColumn); + if (singleLegacyRaceIdentifier != null) { + result.put(null, singleLegacyRaceIdentifier); + } + DBObject raceIdentifiersPerFleet = (DBObject) dbRaceColumn.get(FieldNames.RACE_IDENTIFIERS.name()); + if (raceIdentifiersPerFleet != null) { + for (String escapedFleetName : raceIdentifiersPerFleet.keySet()) { + String fleetName = MongoUtils.unescapeDollarAndDot(escapedFleetName); + result.put(fleetName, loadRaceIdentifier((DBObject) raceIdentifiersPerFleet.get(fleetName))); + } + } + return result; + } + + @Override + public LeaderboardGroup loadLeaderboardGroup(String name, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { + DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); + LeaderboardGroup leaderboardGroup = null; + try { + BasicDBObject query = new BasicDBObject(); + query.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), name); + leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry, leaderboardRegistry); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard group "+name+"."); + logger.log(Level.SEVERE, "loadLeaderboardGroup", e); + } + + return leaderboardGroup; + } + + @Override + public Iterable getAllLeaderboardGroups(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { + DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); + Set leaderboardGroups = new HashSet(); + try { + for (DBObject o : leaderboardGroupCollection.find()) { + boolean hasUUID = o.containsField(FieldNames.LEADERBOARD_GROUP_UUID.name()); + final LeaderboardGroup leaderboardGroup = loadLeaderboardGroup(o, regattaRegistry, leaderboardRegistry); + leaderboardGroups.add(leaderboardGroup); + if (!hasUUID) { + // in an effort to migrate leaderboard groups without ID to such that have a UUID as their ID, we need + // to write a leaderboard group to the database again after it just received a UUID for the first time: + logger.info("Existing LeaderboardGroup " + leaderboardGroup.getName() + + " received a UUID during migration; updating the leaderboard group in the database"); + new MongoObjectFactoryImpl(database).storeLeaderboardGroup(leaderboardGroup); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups."); + logger.log(Level.SEVERE, "loadLeaderboardGroup", e); + } + + return leaderboardGroups; + } + + private LeaderboardGroup loadLeaderboardGroup(DBObject o, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); + String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name()); + UUID uuid = (UUID) o.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); + if (uuid == null) { + uuid = UUID.randomUUID(); + logger.info("Leaderboard group "+name+" receives UUID "+uuid+" in a migration effort"); + // migration: leaderboard groups that don't yet have a UUID receive a random one + } + String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name()); + String displayName = (String) o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name()); + boolean displayGroupsInReverseOrder = false; // default value + Object displayGroupsInReverseOrderObj = o.get(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name()); + if (displayGroupsInReverseOrderObj != null) { + displayGroupsInReverseOrder = (Boolean) displayGroupsInReverseOrderObj; + } + ArrayList leaderboards = new ArrayList(); + BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name()); + for (Object object : dbLeaderboardIds) { + ObjectId dbLeaderboardId = (ObjectId) object; + DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId); + if (dbLeaderboard != null) { + final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry, + leaderboardRegistry, /* groupForMetaLeaderboard */null); + if (loadedLeaderboard != null) { + leaderboards.add(loadedLeaderboard); + } + } else { + logger.warning("couldn't find leaderboard with ID "+dbLeaderboardId+" referenced by leaderboard group "+name); + } + } + logger.info("loaded leaderboard group "+name); + LeaderboardGroupImpl result = new LeaderboardGroupImpl(uuid, name, description, displayName, displayGroupsInReverseOrder, leaderboards); + Object overallLeaderboardIdOrName = o.get(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name()); + if (overallLeaderboardIdOrName != null) { + final DBObject dbOverallLeaderboard; + if (overallLeaderboardIdOrName instanceof ObjectId) { + dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardIdOrName); + } else { + dbOverallLeaderboard = (DBObject) overallLeaderboardIdOrName; + } + if (dbOverallLeaderboard != null) { + // the loadLeaderboard call adds the overall leaderboard to the leaderboard registry and sets it as the + // overall leaderboard of the leaderboard group + loadLeaderboard(dbOverallLeaderboard, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ result); + } + } + return result; + } + + @Override + public Iterable getLeaderboardsNotInGroup(RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry) { + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); + Set result = new HashSet(); + try { + // For MongoDB 2.4 $where with refs to global objects no longer works + // http://docs.mongodb.org/manual/reference/operator/where/#op._S_where + // Also a single where leads to a table walk without using indexes. So avoid $where. + + // Don't change the query object, unless you know what you're doing. + // It queries all leaderboards not referenced to be part of a leaderboard group + // and in particular not being an overall leaderboard of a leaderboard group. + DBCursor allLeaderboards = leaderboardCollection.find(); + for (DBObject leaderboardFromDB : allLeaderboards) { + DBObject inLeaderboardGroupsQuery = new BasicDBObject(); + inLeaderboardGroupsQuery.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), ((ObjectId)leaderboardFromDB.get("_id")).toString()); + boolean inLeaderboardGroups = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupsQuery).size()>0; + + DBObject inLeaderboardGroupOverallQuery = new BasicDBObject(); + inLeaderboardGroupOverallQuery.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), ((ObjectId)leaderboardFromDB.get("_id")).toString()); + boolean inLeaderboardGroupOverall = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupOverallQuery).size()>0; + + DBObject inLeaderboardGroupOverallQueryName = new BasicDBObject(); + inLeaderboardGroupOverallQueryName.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), leaderboardFromDB.get(FieldNames.LEADERBOARD_NAME.name())); + boolean inLeaderboardGroupOverallName = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()).find(inLeaderboardGroupOverallQueryName).size()>0; + + if (!inLeaderboardGroups && !inLeaderboardGroupOverall && !inLeaderboardGroupOverallName) { + final Leaderboard loadedLeaderboard = loadLeaderboard(leaderboardFromDB, regattaRegistry, leaderboardRegistry, /* groupForMetaLeaderboard */ null); + if (loadedLeaderboard != null) { + result.add(loadedLeaderboard); + } + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards."); + logger.log(Level.SEVERE, "getAllLeaderboards", e); + } + return result; + } + + @Override + public WindTrack loadWindTrack(String regattaName, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage) { + final WindTrack result; + Map resultMap = loadWindTracks(regattaName, race, windSource, millisecondsOverWhichToAverage); + if (resultMap.containsKey(windSource)) { + result = resultMap.get(windSource); + } else { + // create an empty wind track as result if no fixes were found in store for the wind source requested + result = new WindTrackImpl(millisecondsOverWhichToAverage, windSource.getType().getBaseConfidence(), + windSource.getType().useSpeed(), + /* nameForReadWriteLock */ WindTrackImpl.class.getSimpleName()+" for source "+windSource.toString()); + } + return result; + } + + @Override + public Map loadWindTracks(String regattaName, RaceDefinition race, + long millisecondsOverWhichToAverageWind) { + Map result = loadWindTracks(regattaName, race, /* constrain wind source */ null, millisecondsOverWhichToAverageWind); + return result; + } + + /** + * @param constrainToWindSource + * if null, wind for all sources will be loaded; otherwise, only wind data for the wind + * source specified by this argument will be loaded + */ + private Map loadWindTracks(String regattaName, RaceDefinition race, + WindSource constrainToWindSource, long millisecondsOverWhichToAverageWind) { + Map result = new HashMap(); + try { + DBCollection windTracks = database.getCollection(CollectionNames.WIND_TRACKS.name()); + ensureIndicesOnWindTracks(windTracks); + BasicDBObject queryById = new BasicDBObject(); + // the default query is by the RACE_ID key: + queryById.put(FieldNames.RACE_ID.name(), race.getId()); + if (constrainToWindSource != null) { + queryById.put(FieldNames.WIND_SOURCE_NAME.name(), constrainToWindSource.name()); + } + for (DBObject dbWind : windTracks.find(queryById)) { + loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); + } + // Additionally check for legacy wind fixes stored with the old EVENT_NAME key; if any are found, migrate them + BasicDBObject queryByName = new BasicDBObject(); + queryByName.put(FieldNames.EVENT_NAME.name(), regattaName); + queryByName.put(FieldNames.RACE_NAME.name(), race.getName()); + if (constrainToWindSource != null) { + queryByName.put(FieldNames.WIND_SOURCE_NAME.name(), constrainToWindSource.name()); + } + final DBCursor windFixesFoundByName = windTracks.find(queryByName); + if (windFixesFoundByName.hasNext()) { + List windFixesToMigrate = new ArrayList(); + for (DBObject dbWind : windFixesFoundByName) { + Util.Pair wind = loadWindFix(result, dbWind, millisecondsOverWhichToAverageWind); + // write the wind fix with the new ID-based key and remove the legacy wind fix from the DB + windFixesToMigrate.add(new MongoObjectFactoryImpl(database).storeWindTrackEntry(race, regattaName, + wind.getB(), wind.getA())); + } + logger.info("Migrating "+windFixesFoundByName.size()+" wind fixes of regatta "+regattaName+ + " and race "+race.getName()+" to ID-based keys"); + windTracks.insert(windFixesToMigrate.toArray(new DBObject[windFixesToMigrate.size()])); + logger.info("Removing "+windFixesFoundByName.size()+" wind fixes that were keyed by the names of regatta "+regattaName+ + " and race "+race.getName()); + windTracks.remove(queryByName); + } + } catch (Exception e) { + // something went wrong during DB access; report, then use empty new wind track + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded wind data. Check MongoDB settings."); + logger.log(Level.SEVERE, "loadWindTrack", e); + } + return result; + } + + private Util.Pair loadWindFix(Map result, DBObject dbWind, long millisecondsOverWhichToAverageWind) { + Wind wind = loadWind((DBObject) dbWind.get(FieldNames.WIND.name())); + WindSourceType windSourceType = WindSourceType.valueOf((String) dbWind.get(FieldNames.WIND_SOURCE_NAME.name())); + WindSource windSource; + if (dbWind.containsField(FieldNames.WIND_SOURCE_ID.name())) { + windSource = new WindSourceWithAdditionalID(windSourceType, (String) dbWind.get(FieldNames.WIND_SOURCE_ID.name())); + } else { + windSource = new WindSourceImpl(windSourceType); + } + WindTrack track = result.get(windSource); + if (track == null) { + track = new WindTrackImpl(millisecondsOverWhichToAverageWind, windSource.getType().getBaseConfidence(), + windSource.getType().useSpeed(), + /* nameForReadWriteLock */ WindTrackImpl.class.getSimpleName()+" for source "+windSource.toString()); + result.put(windSource, track); + } + track.add(wind); + return new Util.Pair(wind, windSource); + } + + @Override + public void loadLeaderboardGroupLinksForEvents(EventResolver eventResolver, + LeaderboardGroupResolver leaderboardGroupResolver) { + DBCollection links = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); + for (Object o : links.find()) { + DBObject dbLink = (DBObject) o; + UUID eventId = (UUID) dbLink.get(FieldNames.EVENT_ID.name()); + Event event = eventResolver.getEvent(eventId); + if (event == null) { + logger.info("Found leaderboard group IDs for event with ID "+eventId+" but couldn't find that event."); + } else { + @SuppressWarnings("unchecked") + List leaderboardGroupIDs = (List) dbLink.get(FieldNames.LEADERBOARD_GROUP_UUID.name()); + for (UUID leaderboardGroupID : leaderboardGroupIDs) { + LeaderboardGroup leaderboardGroup = leaderboardGroupResolver.getLeaderboardGroupByID(leaderboardGroupID); + if (leaderboardGroup != null) { + event.addLeaderboardGroup(leaderboardGroup); + } + } + } + } + } + + @Override + public Event loadEvent(String name) { + Event result; + BasicDBObject query = new BasicDBObject(); + query.put(FieldNames.EVENT_NAME.name(), name); + DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); + DBObject eventDBObject = eventCollection.findOne(query); + if (eventDBObject != null) { + result = loadEvent(eventDBObject); + } else { + result = null; + } + return result; + } + + @Override + public Iterable> loadAllEvents() { + ArrayList> result = new ArrayList<>(); + DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); + + try { + for (DBObject object : eventCollection.find()) { + Event event = loadEvent(object); + Boolean requiresStoreAfterMigration = loadLegacyImageAndVideoURLs(event, object); + result.add(new Pair(event, requiresStoreAfterMigration)); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events."); + logger.log(Level.SEVERE, "loadAllEvents", e); + } + + return result; + } + + + private RemoteSailingServerReference loadSailingSever(DBObject serverDBObject) { + RemoteSailingServerReference result = null; + String name = (String) serverDBObject.get(FieldNames.SERVER_NAME.name()); + String urlAsString = (String) serverDBObject.get(FieldNames.SERVER_URL.name()); + try { + URL serverUrl = new URL(urlAsString); + result = new RemoteSailingServerReferenceImpl(name, serverUrl); + } catch (MalformedURLException e) { + logger.log(Level.SEVERE, "Can't load the sailing server with URL " + urlAsString, e); + } + return result; + } + + @Override + public Iterable loadAllRemoteSailingServerReferences() { + ArrayList result = new ArrayList(); + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); + try { + for (DBObject o : serverCollection.find()) { + if (loadSailingSever(o) != null) { + result.add(loadSailingSever(o)); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load sailing server instances URLs."); + logger.log(Level.SEVERE, "loadAllSailingServers", e); + } + return result; + } + + /** + * An event doesn't store its regattas; it's the regatta that stores a reference to its event; the regatta + * needs to add itself to the event when loaded or instantiated. + */ + private Event loadEvent(DBObject eventDBObject) { + String name = (String) eventDBObject.get(FieldNames.EVENT_NAME.name()); + String description = (String) eventDBObject.get(FieldNames.EVENT_DESCRIPTION.name()); + UUID id = (UUID) eventDBObject.get(FieldNames.EVENT_ID.name()); + TimePoint startDate = loadTimePoint(eventDBObject, FieldNames.EVENT_START_DATE); + TimePoint endDate = loadTimePoint(eventDBObject, FieldNames.EVENT_END_DATE); + boolean isPublic = eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) != null ? (Boolean) eventDBObject.get(FieldNames.EVENT_IS_PUBLIC.name()) : false; + Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name())); + Event result = new EventImpl(name, startDate, endDate, venue, isPublic, id); + result.setDescription(description); + String officialWebSiteURLAsString = (String) eventDBObject.get(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name()); + if (officialWebSiteURLAsString != null) { + try { + result.setOfficialWebsiteURL(new URL(officialWebSiteURLAsString)); + } catch (MalformedURLException e) { + logger.severe("Error parsing official website URL "+officialWebSiteURLAsString+" for event "+name+". Ignoring this URL."); + } + } + BasicDBList images = (BasicDBList) eventDBObject.get(FieldNames.EVENT_IMAGES.name()); + if (images != null) { + for (Object imageObject : images) { + ImageDescriptor image = loadImage((DBObject) imageObject); + if (image != null) { + result.addImage(image); + } + } + } + BasicDBList videos = (BasicDBList) eventDBObject.get(FieldNames.EVENT_VIDEOS.name()); + if (videos != null) { + for (Object videoObject : videos) { + VideoDescriptor video = loadVideo((DBObject) videoObject); + if (video != null) { + result.addVideo(video); + } + } + } + return result; + } + + private Venue loadVenue(DBObject dbObject) { + String name = (String) dbObject.get(FieldNames.VENUE_NAME.name()); + BasicDBList dbCourseAreas = (BasicDBList) dbObject.get(FieldNames.COURSE_AREAS.name()); + Venue result = new VenueImpl(name); + for (Object courseAreaDBObject : dbCourseAreas) { + CourseArea courseArea = loadCourseArea((DBObject) courseAreaDBObject); + result.addCourseArea(courseArea); + } + return result; + } + + private CourseArea loadCourseArea(DBObject courseAreaDBObject) { + String name = (String) courseAreaDBObject.get(FieldNames.COURSE_AREA_NAME.name()); + UUID id = (UUID) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name()); + return baseDomainFactory.getOrCreateCourseArea(id, name); + } + + @Override + public Iterable loadAllRegattas(TrackedRegattaRegistry trackedRegattaRegistry) { + List result = new ArrayList(); + DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name()); + for (DBObject dbRegatta : regattaCollection.find()) { + result.add(loadRegatta(dbRegatta, trackedRegattaRegistry)); + } + return result; + } + + @Override + public Regatta loadRegatta(String name, TrackedRegattaRegistry trackedRegattaRegistry) { + DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), name); + DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name()); + DBObject dbRegatta = regattaCollection.findOne(query); + Regatta result = loadRegatta(dbRegatta, trackedRegattaRegistry); + assert result == null || result.getName().equals(name); + return result; + } + + private Regatta loadRegatta(DBObject dbRegatta, TrackedRegattaRegistry trackedRegattaRegistry) { + Regatta result = null; + if (dbRegatta != null) { + String name = (String) dbRegatta.get(FieldNames.REGATTA_NAME.name()); + String boatClassName = (String) dbRegatta.get(FieldNames.BOAT_CLASS_NAME.name()); + TimePoint startDate = loadTimePoint(dbRegatta, FieldNames.REGATTA_START_DATE); + TimePoint endDate = loadTimePoint(dbRegatta, FieldNames.REGATTA_END_DATE); + Serializable id = (Serializable) dbRegatta.get(FieldNames.REGATTA_ID.name()); + if (id == null) { + id = name; + } + BoatClass boatClass = null; + if (boatClassName != null) { + boolean typicallyStartsUpwind = (Boolean) dbRegatta.get(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name()); + boatClass = baseDomainFactory.getOrCreateBoatClass(boatClassName, typicallyStartsUpwind); + } + BasicDBList dbSeries = (BasicDBList) dbRegatta.get(FieldNames.REGATTA_SERIES.name()); + Iterable series = loadSeries(dbSeries, trackedRegattaRegistry); + Serializable courseAreaId = (Serializable) dbRegatta.get(FieldNames.COURSE_AREA_ID.name()); + CourseArea courseArea = null; + if (courseAreaId != null) { + UUID courseAreaUuid = UUID.fromString(courseAreaId.toString()); + courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid); + } + RegattaConfiguration configuration = null; + if (dbRegatta.containsField(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())) { + try { + JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(dbRegatta.get(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())))); + configuration = RegattaConfigurationJsonDeserializer.create().deserialize(json); + } catch (JsonDeserializationException|ParseException e) { + logger.log(Level.WARNING, "Error loading racing procedure configration for regatta.", e); + } + } + final Boolean useStartTimeInference = (Boolean) dbRegatta.get(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name()); + final RankingMetricConstructor rankingMetricConstructor = loadRankingMetricConstructor(dbRegatta); + result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, boatClass, startDate, endDate, series, /* persistent */true, + loadScoringScheme(dbRegatta), id, courseArea, useStartTimeInference == null ? true + : useStartTimeInference, rankingMetricConstructor); + result.setRegattaConfiguration(configuration); + } + return result; + } + + private RankingMetricConstructor loadRankingMetricConstructor(DBObject dbRegatta) { + DBObject rankingMetricJson = (DBObject) dbRegatta.get(FieldNames.REGATTA_RANKING_METRIC.name()); + // default is OneDesignRankingMetric + final RankingMetricConstructor result; + if (rankingMetricJson == null) { + result = OneDesignRankingMetric::new; + } else { + final String rankingMetricTypeName = (String) rankingMetricJson.get(FieldNames.REGATTA_RANKING_METRIC_TYPE.name()); + result = RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.valueOf(rankingMetricTypeName)); + } + return result; + } + + private ScoringSchemeType getScoringSchemeType(DBObject dbObject) { + String scoringSchemeTypeName = (String) dbObject.get(FieldNames.SCORING_SCHEME_TYPE.name()); + ScoringSchemeType scoringSchemeType; + if (scoringSchemeTypeName == null) { + scoringSchemeType = ScoringSchemeType.LOW_POINT; // the default + } else { + try { + scoringSchemeType = ScoringSchemeType.valueOf(scoringSchemeTypeName); + } catch (IllegalArgumentException ila) { + // can happen that the database contains a scoring scheme that + // has not yet been implemented - fall back with a warning + scoringSchemeType = ScoringSchemeType.LOW_POINT; + logger.warning("Could not find scoring scheme " + scoringSchemeTypeName + "! Most probably this has not yet been implemented or even been removed."); + } + } + return scoringSchemeType; + } + + private Iterable loadSeries(BasicDBList dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) { + List result = new ArrayList(); + for (Object o : dbSeries) { + DBObject oneDBSeries = (DBObject) o; + Series series = loadSeries(oneDBSeries, trackedRegattaRegistry); + result.add(series); + } + return result; + } + + private Series loadSeries(DBObject dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) { + String name = (String) dbSeries.get(FieldNames.SERIES_NAME.name()); + boolean isMedal = (Boolean) dbSeries.get(FieldNames.SERIES_IS_MEDAL.name()); + Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name()); + Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name()); + Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name()); + final BasicDBList dbFleets = (BasicDBList) dbSeries.get(FieldNames.SERIES_FLEETS.name()); + List fleets = loadFleets(dbFleets); + BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name()); + Iterable raceColumnNames = loadRaceColumnNames(dbRaceColumns); + Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry); + if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) { + ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS); + series.setResultDiscardingRule(resultDiscardingRule); + } + if (startsWithZeroScore != null) { + series.setStartsWithZeroScore(startsWithZeroScore); + } + if (hasSplitFleetContiguousScoring != null) { + series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); + } + if (firstColumnIsNonDiscardableCarryForward != null) { + series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); + } + loadRaceColumnRaceLinks(dbRaceColumns, series); + return series; + } + + private Iterable loadRaceColumnNames(BasicDBList dbRaceColumns) { + List result = new ArrayList(); + for (Object o : dbRaceColumns) { + DBObject dbRaceColumn = (DBObject) o; + result.add((String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name())); + } + return result; + } + + private void loadRaceColumnRaceLinks(BasicDBList dbRaceColumns, Series series) { + for (Object o : dbRaceColumns) { + DBObject dbRaceColumn = (DBObject) o; + String name = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()); + Map raceIdentifiersPerFleetName = loadRaceIdentifiers(dbRaceColumn); + for (Map.Entry e : raceIdentifiersPerFleetName.entrySet()) { + // null key for "default" fleet is not acceptable here + if (e.getKey() == null) { + logger.warning("Ignoring null fleet name while loading RaceColumn " + name); + } else { + series.getRaceColumnByName(name).setRaceIdentifier(series.getFleetByName(e.getKey()), e.getValue()); + } + } + } + } + + private List loadFleets(BasicDBList dbFleets) { + List result = new ArrayList(); + for (Object o : dbFleets) { + DBObject dbFleet = (DBObject) o; + Fleet fleet = loadFleet(dbFleet); + result.add(fleet); + } + return result; + } + + private Fleet loadFleet(DBObject dbFleet) { + Fleet result; + String name = (String) dbFleet.get(FieldNames.FLEET_NAME.name()); + Integer ordering = (Integer) dbFleet.get(FieldNames.FLEET_ORDERING.name()); + if (ordering == null) { + ordering = 0; + } + Integer colorAsInt = (Integer) dbFleet.get(FieldNames.FLEET_COLOR.name()); + Color color = null; + if (colorAsInt != null) { + int r = colorAsInt % 256; + int g = (colorAsInt / 256 ) % 256; + int b = (colorAsInt / 256 / 256) % 256; + color = new RGBColor(r, g, b); + } + result = new FleetImpl(name, ordering, color); + return result; + } + + @Override + public Map loadRaceIDToRegattaAssociations(RegattaRegistry regattaRegistry) { + DBCollection raceIDToRegattaCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name()); + Map result = new HashMap(); + for (DBObject o : raceIDToRegattaCollection.find()) { + Regatta regatta = regattaRegistry.getRegattaByName((String) o.get(FieldNames.REGATTA_NAME.name())); + if (regatta != null) { + result.put((String) o.get(FieldNames.RACE_ID_AS_STRING.name()), regatta); + } else { + logger.warning("Couldn't find regatta " + o.get(FieldNames.REGATTA_NAME.name()) + + ". Cannot restore race associations with this regatta."); + } + } + return result; + } + + @Override + public RaceLog loadRaceLog(RaceLogIdentifier identifier) { + RaceLog result = new RaceLogImpl(RaceLogImpl.class.getSimpleName(), identifier.getIdentifier()); + try { + BasicDBObject query = new BasicDBObject(); + query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(identifier.getIdentifier())); + loadRaceLogEvents(result, query); + + // query for events with deprecated identifier format... + query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(identifier.getDeprecatedIdentifier())); + + List eventsToMigrate = loadRaceLogEvents(result, query); + if (!eventsToMigrate.isEmpty()) { + // ... migrate them... + MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database, serviceFinderFactory)); + for (RaceLogEvent event : eventsToMigrate) { + event.accept(storeVisitor); + } + // ... and delete the old ones... + database.getCollection(CollectionNames.RACE_LOGS.name()).remove(query); + } + } catch (Throwable t) { + // something went wrong during DB access; report, then use empty new race log + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings."); + logger.log(Level.SEVERE, "loadRaceLog", t); + } + return result; + } + + private List loadRaceLogEvents(RaceLog targetRaceLog, BasicDBObject query) { + List result = new ArrayList<>(); + DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name()); + for (DBObject o : raceLog.find(query)) { + try { + RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name())); + if (raceLogEvent != null) { + targetRaceLog.load(raceLogEvent); + result.add(raceLogEvent); + } + } catch (IllegalStateException e) { + logger.log(Level.SEVERE, "Couldn't load race log event "+o+": "+e.getMessage(), e); + } + } + return result; + } + + public RaceLogEvent loadRaceLogEvent(DBObject dbObject) { + TimePoint logicalTimePoint = loadTimePoint(dbObject); + TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT); + Serializable id = (Serializable) dbObject.get(FieldNames.RACE_LOG_EVENT_ID.name()); + Integer passId = (Integer) dbObject.get(FieldNames.RACE_LOG_EVENT_PASS_ID.name()); + BasicDBList dbCompetitors = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name()); + List competitors = loadCompetitorsForRaceLogEvent(dbCompetitors); + final AbstractLogEventAuthor author; + String authorName = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name()); + Number authorPriority = (Number) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name()); + if (authorName != null && authorPriority != null) { + author = new LogEventAuthorImpl(authorName, authorPriority.intValue()); + } else { + author = LogEventAuthorImpl.createCompatibilityAuthor(); + } + + String eventClass = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_CLASS.name()); + if (eventClass.equals(RaceLogStartTimeEvent.class.getSimpleName())) { + return loadRaceLogStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogRaceStatusEvent.class.getSimpleName())) { + return loadRaceLogRaceStatusEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogFlagEvent.class.getSimpleName())) { + return loadRaceLogFlagEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogPassChangeEvent.class.getSimpleName())) { + return loadRaceLogPassChangeEvent(createdAt, author, logicalTimePoint, id, passId, competitors); + } else if (eventClass.equals(RaceLogCourseAreaChangedEvent.class.getSimpleName())) { + return loadRaceLogCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogCourseDesignChangedEvent.class.getSimpleName())) { + return loadRaceLogCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogFinishPositioningListChangedEvent.class.getSimpleName())) { + return loadRaceLogFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogFinishPositioningConfirmedEvent.class.getSimpleName())) { + return loadRaceLogFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogPathfinderEvent.class.getSimpleName())) { + return loadRaceLogPathfinderEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogGateLineOpeningTimeEvent.class.getSimpleName())) { + return loadRaceLogGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogStartProcedureChangedEvent.class.getSimpleName())) { + return loadRaceLogStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogProtestStartTimeEvent.class.getSimpleName())) { + return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogWindFixEvent.class.getSimpleName())) { + return loadRaceLogRaceLogWindFixEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogDeviceCompetitorMappingEvent.class.getSimpleName())) { + return loadRaceLogDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogDeviceMarkMappingEvent.class.getSimpleName())) { + return loadRaceLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogDenoteForTrackingEvent.class.getSimpleName())) { + return loadRaceLogDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogStartTrackingEvent.class.getSimpleName())) { + return loadRaceLogStartEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogRevokeEvent.class.getSimpleName())) { + return loadRaceLogRevokeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogRegisterCompetitorEvent.class.getSimpleName())) { + return loadRaceLogRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogDefineMarkEvent.class.getSimpleName())) { + return loadRaceLogDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { + return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(RaceLogAdditionalScoringInformationEvent.class.getSimpleName())) { + return loadRaceLogAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(FixedMarkPassingEvent.class.getSimpleName())){ + return loadRaceLogFixedMarkPassingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(SuppressedMarkPassingsEvent.class.getSimpleName())){ + return loadRaceLogSuppressedMarkPassingsEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } + + throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); + } + + private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Wind wind = loadWind((DBObject) dbObject.get(FieldNames.WIND.name())); + return raceLogEventFactory.createWindFixEvent(createdAt, author, logicalTimePoint, id, competitors, passId, wind); + } + + private RaceLogEvent loadRaceLogDeviceCompetitorMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + Competitor mappedTo = baseDomainFactory.getExistingCompetitorById( + (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name())); + TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); + TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); + return raceLogEventFactory.createDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); + } + + private RaceLogEvent loadRaceLogDeviceMarkMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + //have to load complete mark, as no order is guaranteed for loading of racelog events + Mark mappedTo = loadMark((DBObject) dbObject.get(FieldNames.MARK.name())); + TimePoint from = loadTimePoint(dbObject, FieldNames.RACE_LOG_FROM); + TimePoint to = loadTimePoint(dbObject, FieldNames.RACE_LOG_TO); + return raceLogEventFactory.createDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, device, mappedTo, passId, from, to); + } + + private RaceLogEvent loadRaceLogDenoteForTrackingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name()); + BoatClass boatClass = baseDomainFactory.getOrCreateBoatClass((String) dbObject.get(FieldNames.BOAT_CLASS_NAME.name())); + Serializable raceId = (Serializable) dbObject.get(FieldNames.RACE_ID.name()); + return raceLogEventFactory.createDenoteForTrackingEvent(createdAt, author, logicalTimePoint, id, passId, raceName, boatClass, raceId); + } + + private RaceLogEvent loadRaceLogStartEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + return raceLogEventFactory.createStartTrackingEvent(createdAt, author, logicalTimePoint, id, passId); + } + + private RaceLogEvent loadRaceLogRevokeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable revokedEventId = Helpers.tryUuidConversion( + (Serializable) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name())); + String revokedEventType = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name()); + String revokedEventShortInfo = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name()); + String reason = (String) dbObject.get(FieldNames.RACE_LOG_REVOKED_REASON.name()); + return raceLogEventFactory.createRevokeEvent(createdAt, author, logicalTimePoint, id, passId, + revokedEventId, revokedEventType, revokedEventShortInfo, reason); + } + + private RaceLogEvent loadRaceLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable competitorId = (Serializable) dbObject.get(FieldNames.RACE_LOG_COMPETITOR_ID.name()); + Competitor comp = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); + return raceLogEventFactory.createRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, passId, comp); + } + + private RaceLogEvent loadRaceLogDefineMarkEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Mark mark = loadMark((DBObject) dbObject.get(FieldNames.RACE_LOG_MARK.name())); + return raceLogEventFactory.createDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, mark); + } + + private RaceLogEvent loadRaceLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name())); + TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); + return raceLogEventFactory.createCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, + deviceMappingEventId, closingTimePoint); + } + + private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Object additionalScoringInformationTypeInfo = dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()); + AdditionalScoringInformationType informationType = AdditionalScoringInformationType.UNKNOWN; + if (additionalScoringInformationTypeInfo != null) { + informationType = AdditionalScoringInformationType.valueOf(additionalScoringInformationTypeInfo.toString()); + } else { + logger.warning("Could not find additional scoring information attached to db log for " + dbObject.toString()); + } + return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId, informationType); + } + + private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME); + return raceLogEventFactory.createProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, protestStartTime); + } + + private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + RacingProcedureType type = RacingProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString()); + return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, type); + } + + private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Number gateLaunchStopTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name()); + Number golfDownTime = 0; + if (dbObject.containsField(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name())) { + golfDownTime = (Number) dbObject.get(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name()); + } + return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, competitors, + passId, gateLaunchStopTime == null ? null : gateLaunchStopTime.longValue(), golfDownTime.longValue()); + } + + private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + String pathfinderId = dbObject.get(FieldNames.RACE_LOG_PATHFINDER_ID.name()).toString(); + return raceLogEventFactory.createPathfinderEvent(createdAt, author, logicalTimePoint, id, competitors, passId, pathfinderId); + } + + private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); + CompetitorResults positionedCompetitors = null; + //When a confirmation event is loaded that does not contain the positioned competitors (this is the case for the ESS events in + //Singapore and Quingdao) then null should be set for the positionedCompetitors, which is evaluated later on. + if (dbPositionedCompetitorList != null) { + positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); + } + + return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); + } + + private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); + CompetitorResults positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); + + return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); + } + + private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors) { + return raceLogEventFactory.createPassChangeEvent(createdAt, author, logicalTimePoint, id, competitors, passId); + } + + private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name()); + CourseBase courseData = loadCourseData((BasicDBList) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()), courseName); + return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseData); + } + + private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + Serializable courseAreaId = (Serializable) dbObject.get(FieldNames.COURSE_AREA_ID.name()); + return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId); + } + + private RaceLogEvent loadRaceLogFixedMarkPassingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + TimePoint ofFixedPassing = loadTimePoint(dbObject, FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING); + Integer zeroBasedIndexOfWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_PASSED_WAYPOINT.name()); + return raceLogEventFactory.createFixedMarkPassingEvent(logicalTimePoint, author, id, competitors, passId, ofFixedPassing, zeroBasedIndexOfWaypoint); + } + + private RaceLogEvent loadRaceLogSuppressedMarkPassingsEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Integer zeroBasedIndexOfFirstSuppressedWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name()); + return raceLogEventFactory.createSuppressedMarkPassingsEvent(logicalTimePoint, author, id, competitors, passId, zeroBasedIndexOfFirstSuppressedWaypoint); + } + + private CompetitorResults loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) { + CompetitorResults positionedCompetitors = new CompetitorResultsImpl(); + for (Object object : dbPositionedCompetitorList) { + DBObject dbObject = (DBObject) object; + + Serializable competitorId = (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name()); + + String competitorName = (String) dbObject.get(FieldNames.COMPETITOR_DISPLAY_NAME.name()); + //The Competitor name is a new field in the list. Therefore the name might be null for existing events. In this case a standard name is set. + if (competitorName == null) { + competitorName = "loaded competitor"; + } + + //At this point we do not retrieve the competitor object since at any point in time, especially after a server restart, the DomainFactory and its competitor + //cache might be empty. But at this time the race log is loaded from database, so the competitor would be null. + //By not using the Competitor object retrieved from the DomainFactory we get completely independent from server restarts and the timepoint of loading + //competitors by tracking providers. + + MaxPointsReason maxPointsReason = MaxPointsReason.valueOf((String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())); + + Util.Triple positionedCompetitor = new Util.Triple(competitorId, competitorName, maxPointsReason); + positionedCompetitors.add(positionedCompetitor); + } + return positionedCompetitors; + } + + private List loadCompetitorsForRaceLogEvent(BasicDBList dbCompetitorList) { + List competitors = new ArrayList(); + for (Object object : dbCompetitorList) { + Serializable competitorId = (Serializable) object; + Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); + competitors.add(competitor); + } + return competitors; + } + + private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + Flags upperFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name())); + Flags lowerFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name())); + Boolean displayed = Boolean.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name())); + + if (upperFlag == null || lowerFlag == null || displayed == null) { + return null; + } + + return raceLogEventFactory.createFlagEvent(createdAt, author, logicalTimePoint, id, competitors, passId, upperFlag, lowerFlag, displayed); + } + + private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + TimePoint startTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_START_TIME); + return raceLogEventFactory.createStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, startTime); + } + + private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + RaceLogRaceStatus nextStatus = RaceLogRaceStatus.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name())); + return raceLogEventFactory.createRaceStatusEvent(createdAt, author, logicalTimePoint, id, competitors, passId, nextStatus); + } + + @Override + public RegattaLog loadRegattaLog(RegattaLikeIdentifier identifier) { + RegattaLog result = new RegattaLogImpl(RegattaLogImpl.class.getSimpleName(), identifier); + try { + BasicDBObject query = new BasicDBObject(); + query.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), identifier.getIdentifierType()); + query.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), identifier.getName()); + loadRegattaLogEvents(result, query, identifier); + } catch (Throwable t) { + // something went wrong during DB access; report, then use empty new regatta log + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data. Check MongoDB settings."); + logger.log(Level.SEVERE, "loadRegattaLog", t); + } + return result; + } + + private void loadRegattaLogEvents(RegattaLog targetRegattaLog, BasicDBObject query, RegattaLikeIdentifier regattaLogIdentifier) { + DBCollection collection = database.getCollection(CollectionNames.REGATTA_LOGS.name()); + for (DBObject o : collection.find(query)) { + try { + RegattaLogEvent event = loadRegattaLogEvent(o, regattaLogIdentifier); + if (event != null) { + targetRegattaLog.load(event); + } + } catch (IllegalStateException e) { + logger.log(Level.SEVERE, "Couldn't load regatta log event "+o+": "+e.getMessage(), e); + } + } + } + + private RegattaLogEvent loadRegattaLogEvent(DBObject o, RegattaLikeIdentifier regattaLogIdentifier) { + DBObject dbObject = (DBObject) o.get(FieldNames.REGATTA_LOG_EVENT.name()); + TimePoint logicalTimePoint = loadTimePoint(dbObject); + TimePoint createdAt = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_EVENT_CREATED_AT); + Serializable id = (Serializable) dbObject.get(FieldNames.REGATTA_LOG_EVENT_ID.name()); + final AbstractLogEventAuthor author; + String authorName = (String) dbObject.get(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name()); + Number authorPriority = (Number) dbObject.get(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name()); + author = new LogEventAuthorImpl(authorName, authorPriority.intValue()); + //CloseOpenEnded, DeviceCompMapping, DeviceMarkMapping, RegisterComp, Revoke + String eventClass = (String) dbObject.get(FieldNames.REGATTA_LOG_EVENT_CLASS.name()); + if (eventClass.equals(RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName())) { + return loadRegattaLogDeviceCompetitorMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); + } else if (eventClass.equals(RegattaLogDeviceMarkMappingEvent.class.getSimpleName())) { + return loadRegattaLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); + } else if (eventClass.equals(RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { + return loadRegattaLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, dbObject); + } else if (eventClass.equals(RegattaLogRegisterCompetitorEvent.class.getSimpleName())) { + return loadRegattaLogRegisterCompetitorEvent(createdAt, author, logicalTimePoint, id, dbObject); + } else if (eventClass.equals(RegattaLogSetCompetitorTimeOnTimeFactorEvent.class.getSimpleName())) { + return loadRegattaLogSetCompetitorTimeOnTimeFactorEvent(createdAt, author, logicalTimePoint, id, dbObject); + } else if (eventClass.equals(RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent.class.getSimpleName())) { + return loadRegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent(createdAt, author, logicalTimePoint, id, dbObject); + } else if (eventClass.equals(RegattaLogRevokeEvent.class.getSimpleName())) { + return loadRegattaLogRevokeEvent(createdAt, author, logicalTimePoint, id, dbObject); + } + throw new IllegalStateException(String.format("Unknown RegattaLogEvent type %s", eventClass)); + } + + private Competitor getCompetitorByID(DBObject dbObject) { + Serializable competitorId = (Serializable) dbObject.get(FieldNames.REGATTA_LOG_COMPETITOR_ID.name()); + Competitor comp = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); + return comp; + } + + private RegattaLogEvent loadRegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent(TimePoint createdAt, + AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { + final Competitor comp = getCompetitorByID(dbObject); + final Double timeOnTimeFactor = (Double) dbObject.get(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name()); + return new RegattaLogSetCompetitorTimeOnTimeFactorEventImpl(createdAt, author, logicalTimePoint, id, comp, timeOnTimeFactor); + } + + private RegattaLogEvent loadRegattaLogSetCompetitorTimeOnTimeFactorEvent(TimePoint createdAt, + AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { + final Competitor comp = getCompetitorByID(dbObject); + final Double timeOnDistanceSecondsAllowancePerNauticalMile = (Double) dbObject.get(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name()); + final Duration timeOnDistanceAllowancePerNauticalMile = timeOnDistanceSecondsAllowancePerNauticalMile == null ? null : + new MillisecondsDurationImpl((long) (timeOnDistanceSecondsAllowancePerNauticalMile*1000)); + return new RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEventImpl(createdAt, author, logicalTimePoint, id, comp, timeOnDistanceAllowancePerNauticalMile); + } + + private RegattaLogRevokeEvent loadRegattaLogRevokeEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { + Serializable revokedEventId = Helpers.tryUuidConversion( + (Serializable) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name())); + String revokedEventType = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name()); + String revokedEventShortInfo = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name()); + String reason = (String) dbObject.get(FieldNames.REGATTA_LOG_REVOKED_REASON.name()); + return new RegattaLogRevokeEventImpl(createdAt, author, logicalTimePoint, id, + revokedEventId, revokedEventType, revokedEventShortInfo, reason); + } + + private RegattaLogRegisterCompetitorEvent loadRegattaLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { + Competitor comp = getCompetitorByID(dbObject); + return new RegattaLogRegisterCompetitorEventImpl(createdAt, author, logicalTimePoint, id, comp); + } + + private RegattaLogCloseOpenEndedDeviceMappingEvent loadRegattaLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, + AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, DBObject dbObject) { + Serializable deviceMappingEventId = Helpers.tryUuidConversion((Serializable) dbObject.get( + FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name())); + TimePoint closingTimePoint = loadTimePoint(dbObject, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT); + return new RegattaLogCloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, id, + deviceMappingEventId, closingTimePoint); + } + + private RegattaLogDeviceMarkMappingEvent loadRegattaLogDeviceMarkMappingEvent(TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + //have to load complete mark, as no order is guaranteed for loading of racelog events + Mark mappedTo = loadMark((DBObject) dbObject.get(FieldNames.MARK.name())); + Triple times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, FieldNames.RACE_LOG_FROM, FieldNames.REGATTA_LOG_TO, FieldNames.RACE_LOG_TO); + final TimePoint from = times.getA(); + final TimePoint to = times.getB(); + final RegattaLogDeviceMarkMappingEventImpl result = new RegattaLogDeviceMarkMappingEventImpl(createdAt, author, logicalTimePoint, id, mappedTo, device, from, to); + final boolean needsMigration = times.getC(); + if (needsMigration) { + // remove old version of mapping event + WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject); + assert removeResult.getN() == 1; + // and then insert using the fixed storage implementation + new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result); + } + return result; + } + + /** + * Loads a from and a to time point from fromField and toField of dbObject. + * If the fromField is not found, the fromFieldDeprecated is attempted. If found, migration + * is deemed necessary, expressed by returning true in the {@link Triple#getC()} component of the result. + * Same for the to-field. + * + * @return the from-time in {@link Triple#getA()}, the to-time in {@link Triple#getB()} and whether or not migration is + * necessary because a value was only found in a deprecated field in {@link Triple#getC()}. + */ + private Triple loadFromToTimePoint(final DBObject dbObject, FieldNames fromField, FieldNames fromFieldDeprecated, + FieldNames toField, FieldNames toFieldDeprecated) { + boolean needsMigration = false; + TimePoint from = loadTimePoint(dbObject, fromField); + if (from == null) { + // see bug 2733: erroneously, some records before the fix were written using RACE_LOG_FROM instead of REGATTA_LOG_FROM + // If such a case is found here, migrate the record. + from = loadTimePoint(dbObject, fromFieldDeprecated); + if (from != null) { + needsMigration = true; + } + } + TimePoint to = loadTimePoint(dbObject, toField); + if (to == null) { + // see bug 2733: erroneously, some records before the fix were written using RACE_LOG_FROM instead of REGATTA_LOG_FROM + // If such a case is found here, migrate the record. + to = loadTimePoint(dbObject, toFieldDeprecated); + if (to != null) { + needsMigration = true; + } + } + return new Triple<>(from, to, needsMigration); + } + + private RegattaLogDeviceCompetitorMappingEvent loadRegattaLogDeviceCompetitorMappingEvent(TimePoint createdAt, + AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, final DBObject dbObject, + RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { + DeviceIdentifier device = null; + try { + device = loadDeviceId(deviceIdentifierServiceFinder, + (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e); + e.printStackTrace(); + } + Competitor mappedTo = baseDomainFactory.getExistingCompetitorById( + (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name())); + Triple times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, FieldNames.RACE_LOG_FROM, FieldNames.REGATTA_LOG_TO, FieldNames.RACE_LOG_TO); + final TimePoint from = times.getA(); + final TimePoint to = times.getB(); + final boolean needsMigration = times.getC(); + final RegattaLogDeviceCompetitorMappingEventImpl result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, author, logicalTimePoint, id, mappedTo, device, from, to); + if (needsMigration) { + // remove old version of mapping event + WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject); + assert removeResult.getN() == 1; + // and then insert using the fixed storage implementation + new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result); + } + return result; + } + + /** + * The old field name WAYPOINT_PASSINGSIDE has been replaced by WAYPOINT_PASSINGINSTRUCTIONS. If a race with the old + * field is loaded, the value of PASSINGSIDE is used and then migrated to PASSINGINSTRUCTION. If the first or last + * Waypoint has the PassingInstructions Gate, it is transfered to Line. + * + */ + @SuppressWarnings("deprecation") // Used to migrate from PASSINGSIDE to the new PASSINGINSTRUCTIONS + private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) { + if (courseName == null) { + courseName = "Course"; + } + CourseBase courseData = new CourseDataImpl(courseName); + int i = 0; + for (Object object : dbCourseList) { + DBObject dbObject = (DBObject) object; + Waypoint waypoint = null; + PassingInstruction passingInstructions = null; + String waypointPassingInstruction = (String) dbObject.get(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name()); + if (waypointPassingInstruction == null) { + waypointPassingInstruction = (String) dbObject.get(FieldNames.WAYPOINT_PASSINGSIDE.name()); + if(waypointPassingInstruction != null) { + logger.info("Migrating PassingInstruction "+waypointPassingInstruction+" to field name WAYPOINT_PASSINGINSTRUCTIONS"); + if((i==0||i==dbCourseList.size()-1)&&waypointPassingInstruction.toLowerCase().equals("gate")){ + logger.warning("Changing PassingInstructions of first or last Waypoint from Gate to Line."); + waypointPassingInstruction = "Line"; + } + dbObject.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), waypointPassingInstruction); + dbObject.removeField(FieldNames.WAYPOINT_PASSINGSIDE.name()); + } + } + if (waypointPassingInstruction != null) { + passingInstructions = PassingInstruction.valueOfIgnoringCase(waypointPassingInstruction); + } + ControlPoint controlPoint = loadControlPoint((DBObject) dbObject.get(FieldNames.CONTROLPOINT.name())); + if (passingInstructions == null) { + waypoint = new WaypointImpl(controlPoint); + } else { + waypoint = new WaypointImpl(controlPoint, passingInstructions); + } + courseData.addWaypoint(i++, waypoint); + } + return courseData; + } + + private ControlPoint loadControlPoint(DBObject dbObject) { + String controlPointClass = (String) dbObject.get(FieldNames.CONTROLPOINT_CLASS.name()); + ControlPoint controlPoint = null; + if (controlPointClass != null) { + if (controlPointClass.equals(Mark.class.getSimpleName())) { + Mark mark = loadMark((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); + controlPoint = mark; + } else if(controlPointClass.equals("Gate")) { + ControlPointWithTwoMarks cpwtm = loadControlPointWithTwoMarks((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); + dbObject.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName()); + controlPoint = cpwtm; + } else if (controlPointClass.equals(ControlPointWithTwoMarks.class.getSimpleName())) { + ControlPointWithTwoMarks cpwtm = loadControlPointWithTwoMarks((DBObject) dbObject.get(FieldNames.CONTROLPOINT_VALUE.name())); + controlPoint = cpwtm; + } + } + return controlPoint; + } + + /** + * Checks for the old GATE fields and migrates them to the new CONTROLPOINTWITHTWOMARKS fields. + */ + @SuppressWarnings("deprecation") // Used for migrating old races + private ControlPointWithTwoMarks loadControlPointWithTwoMarks(DBObject dbObject) { + String controlPointName = (String) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name()); + if (controlPointName == null) { + controlPointName = (String) dbObject.get(FieldNames.GATE_NAME.name()); + logger.info("Migrating name of ControlPointWithTwoMarks " + controlPointName + + " from GATE_NAME to new field CONTROLPOINTWITHTWOMARKS_NAME."); + dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), controlPointName); + dbObject.removeField(FieldNames.GATE_NAME.name()); + } + Serializable controlPointId = (Serializable) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name()); + if (controlPointId == null) { + controlPointId = (Serializable) dbObject.get(FieldNames.GATE_ID.name()); + logger.info("Migrating id of ControlPointWithTwoMarks " + controlPointName + + " from old field GATE_ID to CONTROLPOINTWITHTWOMARKS_ID."); + dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), controlPointId); + dbObject.removeField(FieldNames.GATE_ID.name()); + } + DBObject dbLeft = (DBObject) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name()); + if (dbLeft == null) { + dbLeft = (DBObject) dbObject.get(FieldNames.GATE_LEFT.name()); + logger.info("Migrating left Mark of ControlPointWithTwoMarks " + controlPointName + " from old field GATE_LEFT to CONTROLPOINTWITHTWOMARKS_LEFT"); + dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), dbLeft); + dbObject.removeField(FieldNames.GATE_LEFT.name()); + } + Mark leftMark = loadMark(dbLeft); + DBObject dbRight = (DBObject) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name()); + if (dbRight == null) { + dbRight = (DBObject) dbObject.get(FieldNames.GATE_RIGHT.name()); + logger.info("Migrating right Mark of ControlPointWithTwoMarks " + controlPointName + " from old field GATE_RIGHT to CONTROLPOINTWITHTWOMARKS_RIGHT"); + dbObject.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), dbRight); + dbObject.removeField(FieldNames.GATE_RIGHT.name()); + } + Mark rightMark = loadMark(dbRight); + ControlPointWithTwoMarks gate = baseDomainFactory.createControlPointWithTwoMarks(controlPointId, leftMark, rightMark, controlPointName); + return gate; + } + + private Mark loadMark(DBObject dbObject) { + Serializable markId = (Serializable) dbObject.get(FieldNames.MARK_ID.name()); + String markColor = (String) dbObject.get(FieldNames.MARK_COLOR.name()); + String markName = (String) dbObject.get(FieldNames.MARK_NAME.name()); + String markPattern = (String) dbObject.get(FieldNames.MARK_PATTERN.name()); + String markShape = (String) dbObject.get(FieldNames.MARK_SHAPE.name()); + Object markTypeRaw = dbObject.get(FieldNames.MARK_TYPE.name()); + MarkType markType = markTypeRaw == null ? null : MarkType.valueOf((String) markTypeRaw); + + Mark mark = baseDomainFactory.getOrCreateMark(markId, markName, markType, markColor, markShape, markPattern); + return mark; + } + + @Override + public Collection loadAllCompetitors() { + ArrayList result = new ArrayList(); + DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); + try { + for (DBObject o : collection.find()) { + JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(o))); + Competitor c = competitorDeserializer.deserialize(json); + result.add(c); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load competitors."); + logger.log(Level.SEVERE, "loadCompetitors", e); + } + return result; + } + + @Override + public Iterable> loadAllDeviceConfigurations() { + Map result = new HashMap<>(); + DBCollection configurationCollection = database.getCollection(CollectionNames.CONFIGURATIONS.name()); + + try { + for (DBObject dbObject : configurationCollection.find()) { + Util.Pair entry = loadConfigurationEntry(dbObject); + result.put(entry.getA(), entry.getB()); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load configurations."); + logger.log(Level.SEVERE, "loadAllDeviceConfigurations", e); + } + + return result.entrySet(); + } + + private Util.Pair loadConfigurationEntry(DBObject dbObject) { + DBObject matcherObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_MATCHER.name()); + DBObject configObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_CONFIG.name()); + return new Util.Pair(loadConfigurationMatcher(matcherObject), + loadConfiguration(configObject)); + } + + private DeviceConfigurationMatcher loadConfigurationMatcher(DBObject matcherObject) { + DeviceConfigurationMatcherType type = DeviceConfigurationMatcherType.valueOf( + matcherObject.get(FieldNames.CONFIGURATION_MATCHER_TYPE.name()).toString()); + List clientIdentifiers = new ArrayList(); + BasicDBList clientIdentifiersObject = (BasicDBList) matcherObject.get(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name()); + if (clientIdentifiersObject != null) { + for (Object clientIdentifier : clientIdentifiersObject) { + clientIdentifiers.add(clientIdentifier.toString()); + } + } + return baseDomainFactory.getOrCreateDeviceConfigurationMatcher(type, clientIdentifiers); + } + + private DeviceConfiguration loadConfiguration(DBObject configObject) { + DeviceConfiguration configuration = null; + try { + JsonDeserializer deserializer = DeviceConfigurationJsonDeserializer.create(); + JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(configObject))); + configuration = deserializer.deserialize(json); + } catch (JsonDeserializationException | ParseException e) { + logger.log(Level.SEVERE, "Error parsing configuration object from MongoDB, falling back to empty configuration."); + logger.log(Level.SEVERE, "loadConfiguration", e); + configuration = new DeviceConfigurationImpl(new RegattaConfigurationImpl()); + } + return configuration; + } + + private DeviceIdentifier loadDeviceId( + TypeBasedServiceFinder deviceIdentifierServiceFinder, DBObject deviceId) + throws TransformationException, NoCorrespondingServiceRegisteredException { + String deviceType = (String) deviceId.get(FieldNames.DEVICE_TYPE.name()); + Object deviceTypeId = deviceId.get(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name()); + String stringRepresentation = (String) deviceId.get(FieldNames.DEVICE_STRING_REPRESENTATION.name()); + + try { + return deviceIdentifierServiceFinder.findService(deviceType).deserialize(deviceTypeId, deviceType, stringRepresentation); + } catch (TransformationException e) { + return new PlaceHolderDeviceIdentifierSerializationHandler().deserialize( + stringRepresentation, deviceType, stringRepresentation); + } + } + + @Override + public Map> loadResultUrls() { + Map> resultUrls = new HashMap<>(); + DBCollection resultUrlCollection = database.getCollection(CollectionNames.RESULT_URLS.name()); + for (DBObject dbObject : resultUrlCollection.find()) { + String providerName = (String) dbObject.get(FieldNames.RESULT_PROVIDERNAME.name()); + String urlString = (String) dbObject.get(FieldNames.RESULT_URL.name()); + URL url; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + logger.log(Level.SEVERE, "Failed to parse result Url String: " + urlString + ". Did not load url!"); + continue; + } + if (!resultUrls.containsKey(providerName)) { + resultUrls.put(providerName, new HashSet()); + } + Set set = resultUrls.get(providerName); + set.add(url); + } + return resultUrls; + } + + private ImageDescriptor loadImage(DBObject dbObject) { + ImageDescriptor image = null; + URL imageURL = loadURL(dbObject, FieldNames.IMAGE_URL); + if (imageURL != null) { + String title = (String) dbObject.get(FieldNames.IMAGE_TITLE.name()); + String subtitle = (String) dbObject.get(FieldNames.IMAGE_SUBTITLE.name()); + String copyright = (String) dbObject.get(FieldNames.IMAGE_COPYRIGHT.name()); + String localeRaw = (String) dbObject.get(FieldNames.IMAGE_LOCALE.name()); + Locale locale = localeRaw != null ? Locale.forLanguageTag(localeRaw) : null; + Number imageWidth = (Number) dbObject.get(FieldNames.IMAGE_WIDTH_IN_PX.name()); + Number imageHeight = (Number) dbObject.get(FieldNames.IMAGE_HEIGHT_IN_PX.name()); + TimePoint createdAtDate = loadTimePoint(dbObject, FieldNames.IMAGE_CREATEDATDATE); + BasicDBList tags = (BasicDBList) dbObject.get(FieldNames.IMAGE_TAGS.name()); + List imageTags = new ArrayList(); + if (tags != null) { + for (Object tagObject : tags) { + imageTags.add((String) tagObject); + } + } + image = new ImageDescriptorImpl(imageURL, createdAtDate); + image.setCopyright(copyright); + image.setTitle(title); + image.setSubtitle(subtitle); + image.setLocale(locale); + image.setTags(imageTags); + if (imageWidth != null && imageHeight != null) { + image.setSize(imageWidth.intValue(), imageHeight.intValue()); + } + } + return image; + } + + private VideoDescriptor loadVideo(DBObject dbObject) { + VideoDescriptor video = null; + URL videoURL = loadURL(dbObject, FieldNames.VIDEO_URL); + if(videoURL != null) { + String title = (String) dbObject.get(FieldNames.VIDEO_TITLE.name()); + String subtitle = (String) dbObject.get(FieldNames.VIDEO_SUBTITLE.name()); + String copyright = (String) dbObject.get(FieldNames.VIDEO_COPYRIGHT.name()); + Object mimeTypeRaw = dbObject.get(FieldNames.VIDEO_MIMETYPE.name()); + MimeType mimeType = mimeTypeRaw == null ? null : MimeType.valueOf((String) mimeTypeRaw); + String localeRaw = (String) dbObject.get(FieldNames.VIDEO_LOCALE.name()); + Locale locale = localeRaw != null ? Locale.forLanguageTag(localeRaw) : null; + TimePoint createdAtDate = loadTimePoint(dbObject, FieldNames.VIDEO_CREATEDATDATE); + BasicDBList tags = (BasicDBList) dbObject.get(FieldNames.VIDEO_TAGS.name()); + Number lengthInSeconds = (Number) dbObject.get(FieldNames.VIDEO_LENGTH_IN_SECONDS.name()); + URL thumbnailURL = loadURL(dbObject, FieldNames.VIDEO_THUMBNAIL_URL); + List videoTags = new ArrayList(); + if (tags != null) { + for (Object tagObject : tags) { + videoTags.add((String) tagObject); + } + } + video = new VideoDescriptorImpl(videoURL, mimeType, createdAtDate); + video.setCopyright(copyright); + video.setTitle(title); + video.setSubtitle(subtitle); + video.setLocale(locale); + video.setTags(videoTags); + video.setLengthInSeconds(lengthInSeconds == null ? null : lengthInSeconds.intValue()); + video.setThumbnailURL(thumbnailURL); + } + return video; + } + + private URL loadURL(DBObject dbObject, FieldNames field) { + URL result = null; + String urlAsString = (String) dbObject.get(field.name()); + if(urlAsString != null) { + try { + result = new URL(urlAsString); + } catch (MalformedURLException e) { + logger.severe("Error parsing URL '"+urlAsString+"' in field "+field.name()+"."); + } + } + return result; + } + + /** + * Legacy code to support conversion of old image and video URLs + * @param event + * @param eventDBObject + */ + private boolean loadLegacyImageAndVideoURLs(Event event, DBObject eventDBObject) { + URL logoImageURL = null; + List imageURLs = new ArrayList(); + List sponsorImageURLs = new ArrayList(); + List videoURLs = new ArrayList(); + + String logoImageURLAsString = (String) eventDBObject.get(FieldNames.EVENT_LOGO_IMAGE_URL.name()); + if (logoImageURLAsString != null) { + try { + logoImageURL = new URL(logoImageURLAsString); + } catch (MalformedURLException e) { + logger.severe("Error parsing logo image URL "+logoImageURLAsString+" for event "+event.getName()+". Ignoring this URL."); + } + } + BasicDBList imageURLsJson = (BasicDBList) eventDBObject.get(FieldNames.EVENT_IMAGE_URLS.name()); + if (imageURLsJson != null) { + for (Object imageURL : imageURLsJson) { + try { + imageURLs.add(new URL((String) imageURL)); + } catch (MalformedURLException e) { + logger.severe("Error parsing image URL "+imageURL+" for event "+event.getName()+". Ignoring this image URL."); + } + } + } + BasicDBList videoURLsJson = (BasicDBList) eventDBObject.get(FieldNames.EVENT_VIDEO_URLS.name()); + if (videoURLsJson != null) { + for (Object videoURL : videoURLsJson) { + try { + videoURLs.add(new URL((String) videoURL)); + } catch (MalformedURLException e) { + logger.severe("Error parsing video URL "+videoURL+" for event "+event.getName()+". Ignoring this video URL."); + } + } + } + BasicDBList sponsorImageURLsJson = (BasicDBList) eventDBObject.get(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name()); + if (sponsorImageURLsJson != null) { + for (Object sponsorImageURL : sponsorImageURLsJson) { + try { + sponsorImageURLs.add(new URL((String) sponsorImageURL)); + } catch (MalformedURLException e) { + logger.severe("Error parsing sponsor image URL "+sponsorImageURL+" for event "+event.getName()+". Ignoring this sponsor image URL."); + } + } + } + return event.setMediaURLs(imageURLs, sponsorImageURLs, videoURLs, logoImageURL, Collections.emptyMap()); + } +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 6db7c33016c..fc0c675b804 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.persistence.impl; + public enum FieldNames { // general: TIME_AS_MILLIS, KNOT_SPEED, DEGREE_BEARING, LAT_DEG, LNG_DEG, @@ -18,6 +19,8 @@ public enum FieldNames { EVENT_IMAGE_URLS, EVENT_VIDEO_URLS, EVENT_SPONSOR_IMAGE_URLS, + EVENT_IMAGES, + EVENT_VIDEOS, EVENT_OFFICIAL_WEBSITE_URL, EVENT_LOGO_IMAGE_URL, @@ -128,5 +131,13 @@ public enum FieldNames { RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE, // Handicap factors - REGATTA_LOG_TIME_ON_TIME_FACTOR, REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE, REGATTA_RANKING_METRIC, REGATTA_RANKING_METRIC_TYPE + REGATTA_LOG_TIME_ON_TIME_FACTOR, REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE, REGATTA_RANKING_METRIC, REGATTA_RANKING_METRIC_TYPE, + + // image fields + IMAGE_URL, IMAGE_LOCALE, IMAGE_TITLE, IMAGE_SUBTITLE, IMAGE_MIMETYPE, IMAGE_COPYRIGHT, IMAGE_CREATEDATDATE, + IMAGE_WIDTH_IN_PX, IMAGE_HEIGHT_IN_PX, IMAGE_TAGS, + + // video fields + VIDEO_URL, VIDEO_LOCALE, VIDEO_TITLE, VIDEO_SUBTITLE, VIDEO_MIMETYPE, VIDEO_COPYRIGHT, VIDEO_CREATEDATDATE, + VIDEO_TAGS, VIDEO_THUMBNAIL_URL, VIDEO_LENGTH_IN_SECONDS; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index b5b2f52ee59..5ecab343599 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -112,6 +112,8 @@ import com.sap.sse.common.TypeBasedServiceFinder; import com.sap.sse.common.TypeBasedServiceFinderFactory; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class MongoObjectFactoryImpl implements MongoObjectFactory { private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); @@ -514,6 +516,10 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { serverCollection.remove(query); } + /** + * StoreEvent() uses some deprecated methods of event to keep backward compatibility. + */ + @SuppressWarnings("deprecation") @Override public void storeEvent(Event event) { DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); @@ -546,6 +552,18 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { sponsorImageURLs.add(sponsorImageURL.toString()); } eventDBObject.put(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name(), sponsorImageURLs); + BasicDBList images = new BasicDBList(); + for (ImageDescriptor image : event.getImages()) { + DBObject imageObject = createImageObject(image); + images.add(imageObject); + } + eventDBObject.put(FieldNames.EVENT_IMAGES.name(), images); + BasicDBList videos = new BasicDBList(); + for (VideoDescriptor video: event.getVideos()) { + DBObject videoObject = createVideoObject(video); + videos.add(videoObject); + } + eventDBObject.put(FieldNames.EVENT_VIDEOS.name(), videos); eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); // now store the links to the leaderboard groups DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); @@ -1208,7 +1226,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject entry = (DBObject) JSON.parse(json.toString()); collection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE); } - + @Override public void removeAllCompetitors() { logger.info("Removing all persistent competitor info"); @@ -1403,4 +1421,41 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { result.put(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name(), event.getTimeOnDistanceAllowancePerNauticalMile().asSeconds()); storeRegattaLogEvent(regattaLikeId, result); } + + private DBObject createImageObject(ImageDescriptor image) { + DBObject result = new BasicDBObject(); + result.put(FieldNames.IMAGE_URL.name(), image.getURL().toString()); + result.put(FieldNames.IMAGE_LOCALE.name(), image.getLocale() != null ? image.getLocale().toLanguageTag() : null); + result.put(FieldNames.IMAGE_TITLE.name(), image.getTitle()); + result.put(FieldNames.IMAGE_SUBTITLE.name(), image.getSubtitle()); + result.put(FieldNames.IMAGE_COPYRIGHT.name(), image.getCopyright()); + result.put(FieldNames.IMAGE_WIDTH_IN_PX.name(), image.getWidthInPx()); + result.put(FieldNames.IMAGE_HEIGHT_IN_PX.name(), image.getHeightInPx()); + storeTimePoint(image.getCreatedAtDate(), result, FieldNames.IMAGE_CREATEDATDATE); + BasicDBList tags = new BasicDBList(); + for (String tag : image.getTags()) { + tags.add(tag); + } + result.put(FieldNames.IMAGE_TAGS.name(), tags); + return result; + } + + private DBObject createVideoObject(VideoDescriptor video) { + DBObject result = new BasicDBObject(); + result.put(FieldNames.VIDEO_URL.name(), video.getURL().toString()); + result.put(FieldNames.VIDEO_LOCALE.name(), video.getLocale() != null ? video.getLocale().toLanguageTag() : null); + result.put(FieldNames.VIDEO_THUMBNAIL_URL.name(), video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null); + result.put(FieldNames.VIDEO_TITLE.name(), video.getTitle()); + result.put(FieldNames.VIDEO_SUBTITLE.name(), video.getSubtitle()); + result.put(FieldNames.VIDEO_MIMETYPE.name(), video.getMimeType() != null ? video.getMimeType().name() : null); + result.put(FieldNames.VIDEO_COPYRIGHT.name(), video.getCopyright()); + result.put(FieldNames.VIDEO_LENGTH_IN_SECONDS.name(), video.getLengthInSeconds()); + storeTimePoint(video.getCreatedAtDate(), result, FieldNames.VIDEO_CREATEDATDATE); + BasicDBList tags = new BasicDBList(); + for (String tag : video.getTags()) { + tags.add(tag); + } + result.put(FieldNames.VIDEO_TAGS.name(), tags); + return result; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java index ec8dd8ea3b1..36830a08816 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java @@ -1,19 +1,20 @@ package com.sap.sailing.domain.base; import java.net.URL; -import java.util.concurrent.ExecutionException; +import java.util.Map; import com.sap.sailing.domain.common.Renamable; import com.sap.sse.common.Named; import com.sap.sse.common.TimePoint; import com.sap.sse.common.WithID; import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.WithMedia; /** * Base interface for an Event consisting of all static information, which might be shared * by the server and an Android application. */ -public interface EventBase extends Named, WithDescription, Renamable, WithID { +public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia { void setDescription(String description); @@ -41,6 +42,7 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { void setPublic(boolean isPublic); /** + * @deprecated * Returns a non-null live but unmodifiable collection of URLs pointing to image resources that can be * used to represent the event, e.g., on a web page. * @@ -48,70 +50,29 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { */ Iterable getImageURLs(); - void addImageURL(URL imageURL); - - void removeImageURL(URL imageURL); - /** + * @deprecated * An event may have zero or more sponsors, each of which usually want to see their logo on the web page. * * @return the sponsors' logos; always non-null but possibly empty */ Iterable getSponsorImageURLs(); - - void addSponsorImageURL(URL sponsorImageURL); - - void removeSponsorImageURL(URL sponsorImageURL); - - /** - * Replaces the {@link #getSponsorImageURLs() current contents of the sponsorship image URL sequence} by the image URLs in - * sponsorImageURLs. - * - * @param sponsorImageURLs - * if null, the internal sequence of sponsorship image URLs is cleared but remains valid (non- - * null) - */ - void setSponsorImageURLs(Iterable sponsorImageURLs); - + /** + * @deprecated * An optional logo image; may return null. */ URL getLogoImageURL(); - - void setLogoImageURL(URL logoImageURL); - - /** - * Replaces the {@link #getImageURLs() current contents of the image URL sequence} by the image URLs in - * imageURLs. - * - * @param imageURLs - * if null, the internal sequence of image URLs is cleared but remains valid (non- - * null) - */ - void setImageURLs(Iterable imageURLs); /** + * @deprecated * Returns a non-null live but unmodifiable collection of URLs pointing to video resources that can be * used to represent the event, e.g., on a web page. * * @return a non-null value which may be empty */ Iterable getVideoURLs(); - - void addVideoURL(URL videoURL); - - void removeVideoURL(URL videoURL); - /** - * Replaces the {@link #getVideoURLs() current contents of the video URL sequence} by the video URLs in - * videoURLs. - * - * @param videoURLs - * if null, the internal sequence of image URLs is cleared but remains valid (non- - * null) - */ - void setVideoURLs(Iterable videoURLs); - /** * @return the URL of the event's official web site, or null if such a site does not exist or its URL * is not known @@ -122,9 +83,8 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { Iterable getLeaderboardGroups(); - /** - * For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()} - * determines the image dimensions. - */ - ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException; + /** + * Sets and converts all event images and videos from the old URL based format to the new richer format + * */ + boolean setMediaURLs(Iterable imageURLs, Iterable sponsorImageURLs, Iterable videoURLs, URL logoImageURL, Map imageSizes); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java index 3376fbcdbee..2ab810449d4 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java @@ -1,224 +1,352 @@ -package com.sap.sailing.domain.base.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.net.URL; -import java.util.Collections; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; - -import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.domain.base.Venue; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; - -public abstract class EventBaseImpl implements EventBase { - private static final long serialVersionUID = -5749964088848611074L; - - private String name; - private String description; - private final Venue venue; - private boolean isPublic; - private final UUID id; - private TimePoint startDate; - private TimePoint endDate; - private ConcurrentLinkedQueue imageURLs; - private ConcurrentLinkedQueue videoURLs; - private ConcurrentLinkedQueue sponsorImageURLs; - private URL logoImageURL; - private URL officialWebsiteURL; - - protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { - this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); - } - - /** - * @param venue must not be null - */ - protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { - assert venue != null; - this.id = id; - this.name = name; - this.startDate = startDate; - this.endDate = endDate; - this.venue = venue; - this.isPublic = isPublic; - this.imageURLs = new ConcurrentLinkedQueue(); - this.videoURLs = new ConcurrentLinkedQueue(); - this.sponsorImageURLs = new ConcurrentLinkedQueue(); - } - - private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - ois.defaultReadObject(); - if (imageURLs == null) { - imageURLs = new ConcurrentLinkedQueue(); - } - if (videoURLs == null) { - videoURLs = new ConcurrentLinkedQueue(); - } - if (sponsorImageURLs == null) { - sponsorImageURLs = new ConcurrentLinkedQueue(); - } - } - - @Override - public UUID getId() { - return id; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public void setDescription(String description) { - this.description = description; - } - - @Override - public Venue getVenue() { - return venue; - } - - @Override - public String getName() { - return name; - } - - /** - * @param newName must not be null - */ - public void setName(String newName) { - if (newName == null) { - throw new IllegalArgumentException("An event name must not be null"); - } - this.name = newName; - } - - @Override - public boolean isPublic() { - return isPublic; - } - - @Override - public void setPublic(boolean isPublic) { - this.isPublic = isPublic; - } - - @Override - public TimePoint getStartDate() { - return startDate; - } - - @Override - public void setStartDate(TimePoint startDate) { - this.startDate = startDate; - } - - @Override - public TimePoint getEndDate() { - return endDate; - } - - @Override - public void setEndDate(TimePoint endDate) { - this.endDate = endDate; - } - - @Override - public Iterable getImageURLs() { - return Collections.unmodifiableCollection(imageURLs); - } - - @Override - public void addImageURL(URL imageURL) { - if (!imageURLs.contains(imageURL)) { - imageURLs.add(imageURL); - } - } - - @Override - public void removeImageURL(URL imageURL) { - imageURLs.remove(imageURL); - } - - @Override - public void setImageURLs(Iterable imageURLs) { - this.imageURLs.clear(); - if (imageURLs != null) { - Util.addAll(imageURLs, this.imageURLs); - } - } - - @Override - public Iterable getVideoURLs() { - return Collections.unmodifiableCollection(videoURLs); - } - - @Override - public void addVideoURL(URL videoURL) { - if (!videoURLs.contains(videoURL)) { - videoURLs.add(videoURL); - } - } - - @Override - public void removeVideoURL(URL videoURL) { - videoURLs.remove(videoURL); - } - - @Override - public void setVideoURLs(Iterable videoURLs) { - this.videoURLs.clear(); - if (videoURLs != null) { - Util.addAll(videoURLs, this.videoURLs); - } - } - - @Override - public Iterable getSponsorImageURLs() { - return Collections.unmodifiableCollection(sponsorImageURLs); - } - - @Override - public void addSponsorImageURL(URL sponsorImageURL) { - if (!sponsorImageURLs.contains(sponsorImageURL)) { - sponsorImageURLs.add(sponsorImageURL); - } - } - - @Override - public void removeSponsorImageURL(URL sponsorImageURL) { - sponsorImageURLs.remove(sponsorImageURL); - } - - @Override - public void setSponsorImageURLs(Iterable sponsorImageURLs) { - this.sponsorImageURLs.clear(); - if (sponsorImageURLs != null) { - Util.addAll(sponsorImageURLs, this.sponsorImageURLs); - } - } - - @Override - public URL getLogoImageURL() { - return logoImageURL; - } - - @Override - public void setLogoImageURL(URL logoImageURL) { - this.logoImageURL = logoImageURL; - } - - @Override - public URL getOfficialWebsiteURL() { - return officialWebsiteURL; - } - - @Override - public void setOfficialWebsiteURL(URL officialWebsiteURL) { - this.officialWebsiteURL = officialWebsiteURL; - } - -} +package com.sap.sailing.domain.base.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; + +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.Venue; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.MediaDescriptor; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.common.media.MediaUtils; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; + +public abstract class EventBaseImpl implements EventBase { + private static final long serialVersionUID = -5749964088848611074L; + + private String name; + private String description; + private final Venue venue; + private boolean isPublic; + private final UUID id; + private TimePoint startDate; + private TimePoint endDate; + private URL officialWebsiteURL; + private ConcurrentLinkedQueue images; + private ConcurrentLinkedQueue videos; + + protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { + this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); + } + + /** + * @param venue must not be null + */ + protected EventBaseImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { + assert venue != null; + this.id = id; + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + this.venue = venue; + this.isPublic = isPublic; + this.images = new ConcurrentLinkedQueue(); + this.videos = new ConcurrentLinkedQueue(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + if (images == null) { + images = new ConcurrentLinkedQueue(); + } + if (videos == null) { + videos = new ConcurrentLinkedQueue(); + } + } + + @Override + public UUID getId() { + return id; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public Venue getVenue() { + return venue; + } + + @Override + public String getName() { + return name; + } + + /** + * @param newName must not be null + */ + public void setName(String newName) { + if (newName == null) { + throw new IllegalArgumentException("An event name must not be null"); + } + this.name = newName; + } + + @Override + public boolean isPublic() { + return isPublic; + } + + @Override + public void setPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + @Override + public TimePoint getStartDate() { + return startDate; + } + + @Override + public void setStartDate(TimePoint startDate) { + this.startDate = startDate; + } + + @Override + public TimePoint getEndDate() { + return endDate; + } + + @Override + public void setEndDate(TimePoint endDate) { + this.endDate = endDate; + } + + @Override + public Iterable getImageURLs() { + List result = new ArrayList(); + for (ImageDescriptor image : images) { + if (!image.hasTag(MediaTagConstants.SPONSOR) && !image.hasTag(MediaTagConstants.LOGO)) { + result.add(image.getURL()); + } + } + return result; + } + + @Override + public Iterable getVideoURLs() { + List result = new ArrayList(); + for(VideoDescriptor video: videos) { + result.add(video.getURL()); + } + return result; + } + + @Override + public Iterable getSponsorImageURLs() { + List result = new ArrayList(); + for(MediaDescriptor media : findMediaWithTag(images, MediaTagConstants.SPONSOR)) { + result.add(media.getURL()); + } + return result; + } + + @Override + public URL getLogoImageURL() { + URL result = null; + ImageDescriptor media = findImageWithTag(MediaTagConstants.LOGO); + if(media != null) { + result = media.getURL(); + } + return result; + } + + @Override + public URL getOfficialWebsiteURL() { + return officialWebsiteURL; + } + + @Override + public void setOfficialWebsiteURL(URL officialWebsiteURL) { + this.officialWebsiteURL = officialWebsiteURL; + } + + @Override + public Iterable getImages() { + return Collections.unmodifiableCollection(images); + } + + @Override + public void addImage(ImageDescriptor image) { + if (!images.contains(image)) { + images.add(image); + } + } + + @Override + public void removeImage(ImageDescriptor image) { + images.remove(image); + } + + @Override + public void setImages(Iterable images) { + this.images.clear(); + if (images != null) { + Util.addAll(images, this.images); + } + } + + @Override + public Iterable getVideos() { + return Collections.unmodifiableCollection(videos); + } + + @Override + public void addVideo(VideoDescriptor video) { + if (!videos.contains(video)) { + videos.add(video); + } + } + + @Override + public void removeVideo(VideoDescriptor video) { + videos.remove(video); + } + + @Override + public void setVideos(Iterable videos) { + this.videos.clear(); + if (videos != null) { + Util.addAll(videos, this.videos); + } + } + + @Override + public ImageDescriptor findImageWithTag(String tagName) { + ImageDescriptor result = null; + List mediaWithTag = findMediaWithTag(images, tagName); + if(mediaWithTag.size() > 0) { + result = mediaWithTag.get(0); + } + return result; + } + + @Override + public VideoDescriptor findVideoWithTag(String tagName) { + VideoDescriptor result = null; + List mediaWithTag = findMediaWithTag(videos, tagName); + if(mediaWithTag.size() > 0) { + result = mediaWithTag.get(0); + } + return result; + } + + @Override + public boolean hasImageWithTag(String tagName) { + List mediaWithTag = findMediaWithTag(images, tagName); + return mediaWithTag.size() > 0; + } + + @Override + public List findImagesWithTag(String tagName) { + return findMediaWithTag(images, tagName); + } + + @Override + public List findVideosWithTag(String tagName) { + return findMediaWithTag(videos, tagName); + } + + private List findMediaWithTag(Iterable media, String tagName) { + List result = new ArrayList<>(); + for (T mediaEntry : media) { + if(mediaEntry.hasTag(tagName)) { + result.add(mediaEntry); + } + } + return result; + } + + /** + * Sets and converts all event images and videos from the old URL based format to the new richer format + */ + public boolean setMediaURLs(Iterable imageURLs, Iterable sponsorImageURLs, Iterable videoURLs, + URL logoImageURL, Map imageSizes) { + boolean changed = false; + + for (URL url : imageURLs) { + if (!hasMedia(images, url)) { + ImageDescriptor image = migrateImageURLtoImage(url, getStartDate(), imageSizes.get(url)); + String urlAsString = url.toString(); + if (urlAsString.toLowerCase().indexOf("stage") > 0) { + image.addTag(MediaTagConstants.STAGE); + } else if (urlAsString.toLowerCase().indexOf("eventteaser") > 0) { + image.addTag(MediaTagConstants.TEASER); + } else { + image.addTag(MediaTagConstants.GALLERY); + } + addImage(image); + changed = true; + } + } + for (URL url : sponsorImageURLs) { + if (!hasMedia(images, url)) { + ImageDescriptor image = migrateImageURLtoImage(url, getStartDate(), imageSizes.get(url)); + image.addTag(MediaTagConstants.SPONSOR); + addImage(image); + changed = true; + } + } + + if (logoImageURL != null && !hasMedia(images, logoImageURL)) { + ImageDescriptor image = migrateImageURLtoImage(logoImageURL, getStartDate(), imageSizes.get(logoImageURL)); + image.addTag(MediaTagConstants.LOGO); + addImage(image); + changed = true; + } + + for (URL url : videoURLs) { + if (!hasMedia(videos, url)) { + MimeType mimeType = MediaUtils.detectMimeTypeFromUrl(url.toString()); + VideoDescriptor video = new VideoDescriptorImpl(url, mimeType, getStartDate()); + addVideo(video); + changed = true; + } + } + + return changed; + } + + private ImageDescriptor migrateImageURLtoImage(URL url, TimePoint createdAt, ImageSize imageSize) { + ImageDescriptorImpl image = new ImageDescriptorImpl(url, createdAt); + if (imageSize != null) { + image.setSize(imageSize.getWidth(), imageSize.getHeight()); + } else { + Pair imageDimensions = MediaUtils.getImageDimensions(url); + if (imageDimensions != null) { + image.setSize(imageDimensions); + } + } + return image; + } + + private boolean hasMedia(Iterable media, URL url) { + for (MediaDescriptor mediaEntry : media) { + if (url.equals(mediaEntry.getURL())) { + return true; + } + } + return false; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java index a5b88e9b506..fe683702679 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java @@ -1,16 +1,11 @@ package com.sap.sailing.domain.base.impl; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ExecutionException; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.media.ImageSize; /** * A simplified implementation of the {@link EventBase} interface which maintains an immutable collection of @@ -23,7 +18,6 @@ import com.sap.sse.common.media.ImageSize; public class StrippedEventImpl extends EventBaseImpl { private static final long serialVersionUID = 5608501747499933988L; private final Iterable leaderboardGroups; - private final Map imageSizes; public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id, Iterable leaderboardGroups) { @@ -34,20 +28,10 @@ public class StrippedEventImpl extends EventBaseImpl { boolean isPublic, UUID id, Iterable leaderboardGroups) { super(name, startDate, endDate, venue, isPublic, id); this.leaderboardGroups = leaderboardGroups; - this.imageSizes = new HashMap(); } @Override public Iterable getLeaderboardGroups() { return leaderboardGroups; } - - public void setImageSize(URL imageURL, ImageSize imageSize) { - imageSizes.put(imageURL, imageSize); - } - - @Override - public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException { - return imageSizes.get(imageURL); - } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java index 189d2d08dcb..4612ea26bf5 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java @@ -1,15 +1,11 @@ package com.sap.sailing.domain.test; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.Iterator; -import java.util.UUID; -import java.util.concurrent.ExecutionException; import javax.imageio.ImageIO; import javax.imageio.ImageReader; @@ -17,8 +13,6 @@ import javax.imageio.stream.ImageInputStream; import org.junit.Test; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.impl.EventImpl; import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sse.common.media.ImageSize; @@ -56,15 +50,4 @@ public class CalculateImageSizeFromUrlTest { } return null; } - - @Test - public void testEventImageSize() throws MalformedURLException, InterruptedException, ExecutionException { - Event e = new EventImpl("Event Name", /* startDate */ null, /* endDate */ null, "Kiel", /* isPublic */ true, UUID.randomUUID()); - int width = Math.max(10, (int) (200. * Math.random())); - int height = Math.max(10, (int) (100. * Math.random())); - URL imageURL = new URL("http://placekitten.com/" + width + "/" + height); - e.addImageURL(imageURL); - ImageSize expectedSize = new ImageSizeImpl(width, height); - assertEquals(expectedSize, e.getImageSize(imageURL)); - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java index fb25d1c77c3..aef4e907422 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java @@ -1,64 +1,49 @@ -package com.sap.sailing.domain.base; - -import java.net.URL; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sse.common.media.ImageSize; - -/** - * An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For - * example, Kiel Week 2011 is an event, and the International German Championship 2011 held, e.g., in Travem�nde, is an event, - * too. - * - * @author Axel Uhl (D043530) - * - */ -public interface Event extends EventBase { - Iterable getRegattas(); - - void addRegatta(Regatta regatta); - - void removeRegatta(Regatta regatta); - - /** - * For events, the ID is always a UUID. - */ - UUID getId(); - - /** - * Returns a non-null live but unmodifiable collection of leaderboard groups that were previously - * {@link #addLeaderboardGroup(LeaderboardGroup) added} to this event, in the order of their addition. Therefore, to - * change the iteration order, {@link #removeLeaderboardGroup(LeaderboardGroup)} and - * {@link #addLeaderboardGroup(LeaderboardGroup)} need to be used. - */ - @Override - Iterable getLeaderboardGroups(); - - void addLeaderboardGroup(LeaderboardGroup leaderboardGroup); - - /** - * @return true if and only if a leaderboard group equal to leaderboardGroup was part of - * {@link #getLeaderboardGroups()} and therefore was actually removed - */ - boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup); - - /** - * Replaces the {@link #getLeaderboardGroups() current contents of the leaderboard groups sequence} by the - * leaderboard groups in leaderboardGroups. - */ - void setLeaderboardGroups(Iterable leaderboardGroups); - - /** - * For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()} - * determines the image dimensions. The retrieval of the image size is started when the URL is added to this event, and the result is - * cached. Should the image change its size afterwards or change its availability status, this event won't notice unless the image is - * removed from this event and added again.

- * - * Note that exceptions may result should the image be unavailable. If the image URL leads to a document that is not an image, - * null will result. - */ - ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException; - -} +package com.sap.sailing.domain.base; + +import java.util.UUID; + +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; + +/** + * An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For + * example, Kiel Week 2011 is an event, and the International German Championship 2011 held, e.g., in Travem�nde, is an event, + * too. + * + * @author Axel Uhl (D043530) + * + */ +public interface Event extends EventBase { + Iterable getRegattas(); + + void addRegatta(Regatta regatta); + + void removeRegatta(Regatta regatta); + + /** + * For events, the ID is always a UUID. + */ + UUID getId(); + + /** + * Returns a non-null live but unmodifiable collection of leaderboard groups that were previously + * {@link #addLeaderboardGroup(LeaderboardGroup) added} to this event, in the order of their addition. Therefore, to + * change the iteration order, {@link #removeLeaderboardGroup(LeaderboardGroup)} and + * {@link #addLeaderboardGroup(LeaderboardGroup)} need to be used. + */ + @Override + Iterable getLeaderboardGroups(); + + void addLeaderboardGroup(LeaderboardGroup leaderboardGroup); + + /** + * @return true if and only if a leaderboard group equal to leaderboardGroup was part of + * {@link #getLeaderboardGroups()} and therefore was actually removed + */ + boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup); + + /** + * Replaces the {@link #getLeaderboardGroups() current contents of the leaderboard groups sequence} by the + * leaderboard groups in leaderboardGroups. + */ + void setLeaderboardGroups(Iterable leaderboardGroups); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java index 19d1fa73ce0..6921517df90 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java @@ -2,47 +2,26 @@ package com.sap.sailing.domain.base.impl; import java.io.IOException; import java.io.ObjectInputStream; -import java.net.URL; -import java.net.URLConnection; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; -import com.sap.sse.common.media.ImageSize; public class EventImpl extends EventBaseImpl implements Event { private static final long serialVersionUID = 855135446595485715L; - private static final Logger logger = Logger.getLogger(EventImpl.class.getName()); private final Set regattas; private ConcurrentLinkedQueue leaderboardGroups; - private transient ConcurrentHashMap> imageSizeFetchers; - private transient ExecutorService executor; - public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); } @@ -54,8 +33,6 @@ public class EventImpl extends EventBaseImpl implements Event { super(name, startDate, endDate, venue, isPublic, id); this.regattas = new HashSet(); this.leaderboardGroups = new ConcurrentLinkedQueue<>(); - this.imageSizeFetchers = new ConcurrentHashMap<>(); - this.executor = Executors.newCachedThreadPool(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { @@ -63,8 +40,6 @@ public class EventImpl extends EventBaseImpl implements Event { if (leaderboardGroups == null) { leaderboardGroups = new ConcurrentLinkedQueue<>(); } - this.imageSizeFetchers = new ConcurrentHashMap<>(); - this.executor = Executors.newCachedThreadPool(); } @Override @@ -106,110 +81,4 @@ public class EventImpl extends EventBaseImpl implements Event { public boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup) { return leaderboardGroups.remove(leaderboardGroup); } - - private Future getOrCreateImageSizeCalculator(final URL imageURL) { - Future imageSizeFetcher = imageSizeFetchers.get(imageURL); - if (imageSizeFetcher == null) { - imageSizeFetcher = executor.submit(new Callable() { - @Override - public ImageSize call() throws IOException { - ImageSize result = null; - ImageInputStream in = null; - try { - URLConnection conn = imageURL.openConnection(); - in = ImageIO.createImageInputStream(conn.getInputStream()); - final Iterator readers = ImageIO.getImageReaders(in); - if (readers.hasNext()) { - ImageReader reader = readers.next(); - try { - reader.setInput(in); - result = new ImageSizeImpl(reader.getWidth(0), reader.getHeight(0)); - } finally { - reader.dispose(); - } - } - } catch (IOException ioe) { - logger.log(Level.SEVERE, "Stale image link "+imageURL+" for event "+getName()+". Please double-check and replace or remove.", ioe); - throw ioe; - } finally { - if (in != null) { - in.close(); - } - } - return result; - } - }); - imageSizeFetchers.put(imageURL, imageSizeFetcher); - } - return imageSizeFetcher; - } - - @Override - public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException { - Future imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); - return imageSizeCalculator.get(); - } - - @Override - public void addImageURL(URL imageURL) { - super.addImageURL(imageURL); - refreshImageSizeFetcher(imageURL); - } - - private void refreshImageSizeFetcher(URL imageURL) { - if (imageURL != null) { - removeImageSizeFetcher(imageURL); - getOrCreateImageSizeCalculator(imageURL); - } - } - - @Override - public void removeImageURL(URL imageURL) { - super.removeImageURL(imageURL); - removeImageSizeFetcher(imageURL); - } - - private void removeImageSizeFetcher(URL imageURL) { - if (imageURL != null) { - imageSizeFetchers.remove(imageURL); - } - } - - @Override - public void setImageURLs(Iterable imageURLs) { - super.setImageURLs(imageURLs); - if (imageURLs != null) { - for (URL imageURL : imageURLs) { - refreshImageSizeFetcher(imageURL); - } - } - } - - @Override - public void addSponsorImageURL(URL sponsorImageURL) { - super.addSponsorImageURL(sponsorImageURL); - refreshImageSizeFetcher(sponsorImageURL); - } - - @Override - public void removeSponsorImageURL(URL sponsorImageURL) { - super.removeSponsorImageURL(sponsorImageURL); - removeImageSizeFetcher(sponsorImageURL); - } - - @Override - public void setSponsorImageURLs(Iterable sponsorImageURLs) { - super.setSponsorImageURLs(sponsorImageURLs); - if (sponsorImageURLs != null) { - for (URL imageURL : sponsorImageURLs) { - refreshImageSizeFetcher(imageURL); - } - } - } - - @Override - public void setLogoImageURL(URL logoImageURL) { - super.setLogoImageURL(logoImageURL); - refreshImageSizeFetcher(logoImageURL); - } } diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index 520588e980e..3680642bd97 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -47,6 +47,7 @@ + diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index 63b9d34a6fa..0a8d1d1bb38 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -54,6 +54,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + ex return new RegattaRacesPlace(contextForRegatta(regattaId)); } + public RegattaLeaderboardPlace getPlaceForRegattaLeaderboard(String regattaId) { + return new RegattaLeaderboardPlace(contextForRegatta(regattaId)); + } + protected EventContext contextForRegatta(String regattaId) { return new EventContext(ctx).withRegattaId(regattaId).withRegattaAnalyticsManager(null); } @@ -181,6 +186,11 @@ public abstract class AbstractEventActivity ex return homePlacesNavigator.getEventNavigation(getPlaceForRegatta(regattaId), null, false); } + @Override + public PlaceNavigation getRegattaLeaderboardNavigation(String regattaId) { + return homePlacesNavigator.getEventNavigation(getPlaceForRegattaLeaderboard(regattaId), null, false); + } + @Override public void ensureRegattaStructure(final AsyncCallback> callback) { if (ctx.getRaceGroups() != null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivityProxy.java index be0dc64d40e..d308d93ac64 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivityProxy.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventActivityProxy.java @@ -17,6 +17,7 @@ import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaLeaderboa import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaMediaPlace; import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaOverviewPlace; import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaRacesPlace; +import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventViewAction; import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO; import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO.EventType; import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; @@ -43,7 +44,7 @@ public class EventActivityProxy extends AbstractActivityProxy { } else { final UUID eventUUID = UUID.fromString(ctx.getEventId()); - clientFactory.getHomeService().getEventViewById(eventUUID, new AsyncCallback() { + clientFactory.getDispatch().execute(new GetEventViewAction(eventUUID), new AsyncCallback() { @Override public void onSuccess(final EventViewDTO event) { ctx.updateContext(event); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventContext.java index 6e5e81df66e..e9750708f07 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventContext.java @@ -77,7 +77,7 @@ public class EventContext { return regattaId; } if(eventDTO != null && (eventDTO.getType() == EventType.SINGLE_REGATTA || eventDTO.getType() == EventType.SERIES_EVENT)) { - return eventDTO.getRegattas().get(0).getDisplayName(); + return eventDTO.getRegattas().get(0).getId(); } return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java index b28ecb554bd..41628345267 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/EventView.java @@ -10,6 +10,7 @@ import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.common.client.controls.tabbar.TabView; import com.sap.sailing.gwt.home.client.app.PlaceNavigation; import com.sap.sailing.gwt.home.client.place.event.regatta.AbstractEventRegattaPlace; +import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaLeaderboardPlace; import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.RegattaRacesPlace; import com.sap.sailing.gwt.home.client.place.events.EventsPlace; import com.sap.sailing.gwt.home.client.place.fakeseries.SeriesDefaultPlace; @@ -56,6 +57,7 @@ public interface EventView getRegattaRacesNavigation(String regattaName); PlaceNavigation getRegattaNavigation(String regattaName); + PlaceNavigation getRegattaLeaderboardNavigation(String regattaName); void ensureRegattaStructure(AsyncCallback> callback); void ensureLeaderboardGroups(AsyncCallback> callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.java index b467fbaab88..5c6144acb4d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.java @@ -23,7 +23,6 @@ import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload.RefreshMa import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventOverviewStageAction; import com.sap.sailing.gwt.ui.shared.dispatch.event.GetLiveRacesForEventAction; import com.sap.sse.common.Util.Triple; @@ -62,7 +61,7 @@ public class MultiregattaOverviewTabView extends Composite implements Multiregat RefreshManager refreshManager = new RefreshManager(this, currentPresenter.getDispatch()); - refreshManager.add(stage, new GetEventOverviewStageAction(currentPresenter.getCtx().getEventDTO().getId())); + stage.setupRefresh(refreshManager); refreshManager.add(racesListLive, new GetLiveRacesForEventAction(currentPresenter.getCtx().getEventDTO().getId())); contentArea.setWidget(this); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.ui.xml index e959e50b54a..7e73a5c2923 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/multiregatta/tabs/MultiregattaOverviewTabView.ui.xml @@ -3,18 +3,13 @@ xmlns:r="urn:import:com.sap.sailing.gwt.home.client.place.event.partials.raceListLive" xmlns:o="urn:import:com.sap.sailing.gwt.home.client.place.event.overview"> - - .space-top { - margin-top: 2em; - } -

- +
- + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.java index b01755611e0..9bb68ed395a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.java @@ -1,30 +1,60 @@ package com.sap.sailing.gwt.home.client.place.event.overview; +import java.util.List; + import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.common.client.SharedResources; +import com.sap.sailing.gwt.common.client.SharedResources.MediaCss; import com.sap.sailing.gwt.home.client.place.event.EventView; import com.sap.sailing.gwt.home.client.place.event.partials.countdown.Countdown; import com.sap.sailing.gwt.home.client.place.event.partials.message.Message; +import com.sap.sailing.gwt.home.client.place.event.partials.updatesBox.UpdatesBox; import com.sap.sailing.gwt.home.client.place.event.partials.video.Video; +import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload.RefreshManager; import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload.RefreshableWidget; +import com.sap.sailing.gwt.ui.shared.dispatch.ListResult; import com.sap.sailing.gwt.ui.shared.dispatch.event.EventOverviewStageContentDTO; import com.sap.sailing.gwt.ui.shared.dispatch.event.EventOverviewStageDTO; import com.sap.sailing.gwt.ui.shared.dispatch.event.EventOverviewTickerStageDTO; import com.sap.sailing.gwt.ui.shared.dispatch.event.EventOverviewVideoStageDTO; +import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventOverviewNewsAction; +import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventOverviewStageAction; +import com.sap.sailing.gwt.ui.shared.dispatch.news.NewsEntryDTO; +import com.sap.sailing.gwt.ui.shared.general.EventState; -public class EventOverviewStage extends Composite implements RefreshableWidget { +public class EventOverviewStage extends Composite { + + private final RefreshableWidget refreshable = new RefreshableWidget() { + @Override + public void setData(EventOverviewStageDTO data, long nextUpdate, int updateNo) { + setStageData(data); + } + }; + private final RefreshableWidget> newsRefreshable = new RefreshableWidget>() { + @Override + public void setData(ListResult data, long nextUpdate, int updateNo) { + setNews(data.getValues()); + } + }; private static StageUiBinder uiBinder = GWT.create(StageUiBinder.class); interface StageUiBinder extends UiBinder { } + private final MediaCss mediaCss = SharedResources.INSTANCE.mediaCss(); + @UiField SimplePanel stage; @UiField Message message; + @UiField DivElement updatesWrapperUi; + @UiField(provided = true) UpdatesBox updatesUi; private Widget lastContent; @@ -32,11 +62,23 @@ public class EventOverviewStage extends Composite implements RefreshableWidget news) { + if(lastContent == null) { + setStageData(new EventOverviewStageDTO(null, new EventOverviewTickerStageDTO(null, null, null))); + } + + if(news.isEmpty()) { + updatesWrapperUi.getStyle().setDisplay(Display.NONE); + stage.removeStyleName(mediaCss.medium7()); + stage.removeStyleName(mediaCss.large8()); + } else { + updatesWrapperUi.getStyle().clearDisplay(); + updatesUi.setData(news); + stage.addStyleName(mediaCss.medium7()); + stage.addStyleName(mediaCss.large8()); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.ui.xml index 20710c7bc91..7902ce2899d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/overview/EventOverviewStage.ui.xml @@ -1,5 +1,5 @@ - + @@ -8,6 +8,9 @@
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.gss index 8569a5ccab4..e4ceefcb450 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.gss @@ -1,52 +1,20 @@ -.countdown { - font-size: 1rem; -} -.countdown_content { - font-size: inherit; -} -.countdown_content_panewrapper { - font-size: 0; -} -.countdown_content_pane { - border: 0.2em solid #fff; - background: #008fcc; - color: #fff; - text-align: center; - display: inline-block; - margin-right: 0.333333333333333em; - padding: 0.666666666666667em 0.333333333333333em; - border-radius: 0.666666666666667em; - width: 4.666666666666667em; - height: 4.666666666666667em; - text-shadow: none; - box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); -} -.countdown_content_panefirst, -.countdown_content_panesecond, -.countdown_content_panethird, -.countdown_content_panefourth { - font-size: 1rem; - text-transform: uppercase; -} -.countdown_content_pane_count { - font-weight: bold; - font-size: 2em; - line-height: 1em; -} -.countdown_content_pane_unit { - font-size: 0.8em; -} .countdown { font-size: 1rem; position: relative; height: 28.333333333333332em; - margin-top: 1.333333333333333em; } @media (max-width: 976px) { .countdown { height: 22em; } } +.countdown_image { + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-image: url("images/home/countdown-thumbnail.png"); +} .countdown_content { position: absolute; top: 50%; @@ -63,12 +31,6 @@ font-size: 1.466666666666667em; color: #f6f9fc; } -.countdown_image { - width: 100%; - height: 100%; - background-size: cover; - background-position: center; -} .countdown_info { position: absolute; bottom: 0; @@ -95,7 +57,6 @@ margin-top: 0.181818181818182em; } .countdown_info_lable { - width: 3.333333333333333em; display: inline-block; padding-left: 0.4em; margin-left: 1.333333333333333em; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.java index 02c1753f7d5..4f534a531f5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/Countdown.java @@ -21,6 +21,7 @@ import com.sap.sailing.gwt.home.client.app.PlaceNavigation; import com.sap.sailing.gwt.home.client.place.event.EventView; import com.sap.sailing.gwt.home.client.place.event.EventView.Presenter; import com.sap.sailing.gwt.home.client.place.event.partials.countdown.CountdownResources.LocalCss; +import com.sap.sailing.gwt.home.client.place.event.partials.countdownTimer.CountdownTimer; import com.sap.sailing.gwt.home.client.shared.stage.StageResources; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.dispatch.event.EventOverviewRaceTickerStageDTO; @@ -72,7 +73,7 @@ public class Countdown extends Composite { } if(data.getStartTime() != null) { - this.tickerContainer.setWidget(new CountdownTicker(data.getStartTime())); + this.tickerContainer.setWidget(new CountdownTimer(data.getStartTime())); } else { this.tickerContainer.setWidget(null); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownResources.java index 91aafa381fe..8ddafac0f0c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownResources.java @@ -12,17 +12,9 @@ public interface CountdownResources extends ClientBundle { public interface LocalCss extends CssResource { String countdown(); - String countdown_content(); - String countdown_content_panewrapper(); - String countdown_content_pane(); - String countdown_content_panefirst(); - String countdown_content_panesecond(); - String countdown_content_panethird(); - String countdown_content_panefourth(); - String countdown_content_pane_count(); - String countdown_content_pane_unit(); - String countdown_content_title(); String countdown_image(); + String countdown_content(); + String countdown_content_title(); String countdown_info(); String countdown_info_text(); String countdown_info_text_title(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.ui.xml deleted file mode 100644 index 6caed0e8386..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.ui.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - -
-
-
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.gss new file mode 100644 index 00000000000..f5d17429c39 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.gss @@ -0,0 +1,41 @@ +/** + * Styles for countdown on the stage + */ +.countdowntimer { + font-size: 1rem; +} +.countdowntimer_content { + font-size: inherit; +} +.countdowntimer_content_panewrapper { + font-size: 0; +} +.countdowntimer_content_pane { + border: 0.2em solid #fff; + background: #008fcc; + color: #fff; + text-align: center; + display: inline-block; + margin-right: 0.333333333333333em; + padding: 0.666666666666667em 0.333333333333333em; + border-radius: 0.666666666666667em; + width: 4.666666666666667em; + height: 4.666666666666667em; + text-shadow: none; + box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); +} +.countdowntimer_content_panefirst, +.countdowntimer_content_panesecond, +.countdowntimer_content_panethird, +.countdowntimer_content_panefourth { + font-size: 1rem; + text-transform: uppercase; +} +.countdowntimer_content_pane_count { + font-weight: bold; + font-size: 2em; + line-height: 1em; +} +.countdowntimer_content_pane_unit { + font-size: 0.8em; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.java similarity index 84% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.java index 5680938e0b8..87701774a8b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdown/CountdownTicker.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.java @@ -1,24 +1,24 @@ -package com.sap.sailing.gwt.home.client.place.event.partials.countdown; +package com.sap.sailing.gwt.home.client.place.event.partials.countdownTimer; import java.util.Date; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.client.shared.Countdown; import com.sap.sailing.gwt.home.client.shared.Countdown.CountdownListener; import com.sap.sailing.gwt.home.client.shared.Countdown.RemainingTime; import com.sap.sse.common.impl.MillisecondsTimePoint; -public class CountdownTicker extends Composite { +public class CountdownTimer extends Widget { private static CountdownTickerUiBinder uiBinder = GWT.create(CountdownTickerUiBinder.class); - interface CountdownTickerUiBinder extends UiBinder { + interface CountdownTickerUiBinder extends UiBinder { } @UiField DivElement majorContainer, majorValue, majorUnit; @@ -26,8 +26,9 @@ public class CountdownTicker extends Composite { private final Countdown countdown; - public CountdownTicker(Date startTime) { - initWidget(uiBinder.createAndBindUi(this)); + public CountdownTimer(Date startTime) { + CountdownTimerResources.INSTANCE.css().ensureInjected(); + setElement(uiBinder.createAndBindUi(this)); this.countdown = new Countdown(new MillisecondsTimePoint(startTime), new CountdownTickerListener()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.ui.xml new file mode 100644 index 00000000000..eca537d5e0a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimer.ui.xml @@ -0,0 +1,16 @@ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimerResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimerResources.java new file mode 100644 index 00000000000..bc5c3020998 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/countdownTimer/CountdownTimerResources.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.home.client.place.event.partials.countdownTimer; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface CountdownTimerResources extends ClientBundle { + public static final CountdownTimerResources INSTANCE = GWT.create(CountdownTimerResources.class); + + @Source("CountdownTimer.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String countdowntimer(); + String countdowntimer_content(); + String countdowntimer_content_panewrapper(); + String countdowntimer_content_pane(); + String countdowntimer_content_panefirst(); + String countdowntimer_content_panesecond(); + String countdowntimer_content_panethird(); + String countdowntimer_content_panefourth(); + String countdowntimer_content_pane_count(); + String countdowntimer_content_pane_unit(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/header/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/header/EventHeader.java index 88863dccfa5..7762dae18ef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/header/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/header/EventHeader.java @@ -103,14 +103,21 @@ public class EventHeader extends Composite { } private void initFields() { - String logoUrl = event.getLogoImageURL() != null ? event.getLogoImageURL() : EventHeaderResources.INSTANCE.defaultEventLogoImage().getSafeUri().asString(); + String logoUrl = event.getLogoImage() != null ? event.getLogoImage().getSourceRef() : EventHeaderResources.INSTANCE.defaultEventLogoImage().getSafeUri().asString(); eventLogo.getStyle().setBackgroundImage("url(" + logoUrl + ")"); - eventLogo.setTitle(event.getDisplayName()); + + String eventDisplayName = event.getDisplayName(); + eventLogo.setTitle(eventDisplayName); String nameToShow; if(presenter.showRegattaMetadata()) { HasRegattaMetadata regattaMetadata = presenter.getRegattaMetadata(); - nameToShow = regattaMetadata.getDisplayName(); + String regattaDisplayName = regattaMetadata.getDisplayName(); + if(regattaDisplayName.toLowerCase().contains(eventDisplayName.toLowerCase())) { + nameToShow = regattaDisplayName; + } else { + nameToShow = eventDisplayName + " - " + regattaDisplayName; + } if(regattaMetadata.getCompetitorsCount() > 0) { competitors.setInnerText((i18n.competitorsCount(regattaMetadata.getCompetitorsCount()))); @@ -138,7 +145,7 @@ public class EventHeader extends Composite { hide(eventVenueContainer, eventLink); } else { - nameToShow = event.getDisplayName(); + nameToShow = eventDisplayName; eventDate.setInnerHTML(EventDatesFormatterUtil.formatDateRangeWithYear(event.getStartDate(), event.getEndDate())); String venue = event.getLocationAndVenue(); if(event.getVenueCountry() != null && !event.getVenueCountry().isEmpty()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.gss new file mode 100644 index 00000000000..308dd6624eb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.gss @@ -0,0 +1,114 @@ +.box { + font-size: 1rem; + border: 1px solid #dedede; + border-radius: 0.333333333333333em; + overflow: hidden; +} +.box_header { + color: #333; + font-size: 1.2em; + font-weight: 700; + background: #f2f2f2; + padding: 1.111111111111111em; + border-bottom: 1px solid #dedede; +} +.box_content { + list-style: none; + margin: 0; + padding: 0; + max-height: 23.933333333333334em; + overflow-y: auto; +} +@media (max-width: 976px) { + .box_content { + height: 18em; + } +} +@media (max-width: 719px) { + .box_content { + height: 12em; + } +} +.box_content_item { + border-bottom: 1px solid #dedede; +} +.box_content_item:last-child { + border-bottom: none; +} +.updatesbox { + font-size: 1rem; +} +.updatesbox_item { + display: block; + height: 6em; + padding: 1.333333333333333em 2em 1.333333333333333em 1em; + position: relative; + color: #0a0a0a; +} +.updatesbox_itemlink { + background-image: url("images/home/arrow-grey-right.png"); + background-repeat: no-repeat; + background-size: 9px 16px; + background-position: 95% 50%; +} +.updatesbox_itemlink:hover { + text-decoration: none; +} +.updatesbox_item_icon { + width: 3.333333333333333em; + height: 3.333333333333333em; + position: absolute; + top: 50%; + left: 2.666666666666667em; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} +@media (max-width: 1190px) { + .updatesbox_item_icon { + display: none; + } +} +@media (max-width: 720px) { + .updatesbox_item_icon { + display: inherit; + } +} +.updatesbox_item_content { + margin-left: 4em; + padding-left: 0.8em; + border-left: 1px solid #dedede; + line-height: 1.3; +} +@media (max-width: 1190px) { + .updatesbox_item_content { + margin: 0; + border: none; + } +} +@media (max-width: 720px) { + .updatesbox_item_content { + margin-left: 4em; + border-left: 1px solid #dedede; + } +} +.updatesbox_item_content_text { + font-size: 1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.updatesbox_item_content_text_boatclass { + display: none; +} +@media (max-width: 1190px) { + .updatesbox_item_content_text_boatclass { + display: inline; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.java new file mode 100644 index 00000000000..06f611225ad --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.java @@ -0,0 +1,39 @@ +package com.sap.sailing.gwt.home.client.place.event.partials.updatesBox; + +import java.util.Collection; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.UListElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.client.place.event.EventView; +import com.sap.sailing.gwt.home.client.place.event.EventView.Presenter; +import com.sap.sailing.gwt.ui.shared.dispatch.news.NewsEntryDTO; + +public class UpdatesBox extends Composite { + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + interface MyUiBinder extends UiBinder { + } + + @UiField(provided = true) FlowPanel entries = new FlowPanel(UListElement.TAG); + private Presenter presenter; + + public UpdatesBox(EventView.Presenter presenter) { + this.presenter = presenter; + + UpdatesBoxResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setData(Collection newsEntries) { + entries.clear(); + for (NewsEntryDTO newsEntryDTO : newsEntries) { + entries.add(new UpdatesBoxItem(newsEntryDTO, presenter)); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.ui.xml new file mode 100644 index 00000000000..a26b60b8604 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBox.ui.xml @@ -0,0 +1,12 @@ + + + + + + +
+ +
+ +
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.java new file mode 100644 index 00000000000..37b0b613925 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.java @@ -0,0 +1,103 @@ +package com.sap.sailing.gwt.home.client.place.event.partials.updatesBox; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.common.client.BoatClassImageResolver; +import com.sap.sailing.gwt.common.client.LinkUtil; +import com.sap.sailing.gwt.home.client.app.PlaceNavigation; +import com.sap.sailing.gwt.home.client.place.event.EventView; +import com.sap.sailing.gwt.ui.shared.dispatch.news.AbstractRaceNewsEntryDTO; +import com.sap.sailing.gwt.ui.shared.dispatch.news.LeaderboardNewsEntryDTO; +import com.sap.sailing.gwt.ui.shared.dispatch.news.NewsEntryDTO; + +public class UpdatesBoxItem extends Widget { + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + interface MyUiBinder extends UiBinder { + } + + @UiField AnchorElement link; + @UiField DivElement icon; + @UiField SpanElement titleUi; + @UiField SpanElement boatClassUi; + @UiField DivElement messageUi; + @UiField DivElement timestampUi; + + public UpdatesBoxItem(NewsEntryDTO entry, EventView.Presenter presenter) { + setElement(uiBinder.createAndBindUi(this)); + + titleUi.setInnerText(entry.getTitle()); + messageUi.setInnerText(entry.getMessage()); + + String boatClass = entry.getBoatClass(); + if(boatClass != null && !boatClass.isEmpty()) { + icon.getStyle().setBackgroundImage("url(\"" + BoatClassImageResolver.getBoatClassIconResource(boatClass).getSafeUri().asString() + "\")"); + boatClassUi.setInnerText(" / " + boatClass); + } else { + boatClassUi.removeFromParent(); + } + if(boatClass != null && !boatClass.isEmpty()) { + + } + + Date timestamp = entry.getTimestamp(); + if(timestamp == null) { + timestampUi.removeFromParent(); + } else { + timestampUi.setInnerText(DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_MEDIUM).format(timestamp)); + } + + String directLink = entry.getExternalURL(); + if(directLink != null) { + link.setHref(directLink); + link.setTarget("_blank"); + } else { + PlaceNavigation placeNavigation = null; + if(entry instanceof LeaderboardNewsEntryDTO) { + directLink = ((LeaderboardNewsEntryDTO) entry).getExternalURL(); + placeNavigation = presenter.getRegattaLeaderboardNavigation(((LeaderboardNewsEntryDTO) entry).getLeaderboardName()); + } else if(entry instanceof AbstractRaceNewsEntryDTO) { + AbstractRaceNewsEntryDTO raceEntry = (AbstractRaceNewsEntryDTO) entry; + if(raceEntry.getTrackedRaceName() != null) { + directLink = presenter.getRaceViewerURL(raceEntry.getRegattaName(), raceEntry.getTrackedRaceName()); + } + } + if(placeNavigation != null) { + link.setHref(placeNavigation.getTargetUrl()); + Event.sinkEvents(link, Event.ONCLICK); + final PlaceNavigation pn = placeNavigation; + Event.setEventListener(link, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + if(LinkUtil.handleLinkClick(event)) { + event.preventDefault(); + pn.goToPlace(); + } + } + }); + } else { + link.removeFromParent(); + while(link.hasChildNodes()) { + Node firstChild = link.getChild(0); + firstChild.removeFromParent(); + getElement().appendChild(firstChild); + } + addStyleName(UpdatesBoxResources.INSTANCE.css().updatesbox_item()); + } + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.ui.xml new file mode 100644 index 00000000000..2c2e02a9800 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxItem.ui.xml @@ -0,0 +1,20 @@ + + + + +
  • + +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
  • +
    \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxResources.java new file mode 100644 index 00000000000..f0cf86a841c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/partials/updatesBox/UpdatesBoxResources.java @@ -0,0 +1,26 @@ +package com.sap.sailing.gwt.home.client.place.event.partials.updatesBox; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface UpdatesBoxResources extends ClientBundle { + public static final UpdatesBoxResources INSTANCE = GWT.create(UpdatesBoxResources.class); + + @Source("UpdatesBox.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String box(); + String box_header(); + String box_content(); + String box_content_item(); + String updatesbox(); + String updatesbox_item(); + String updatesbox_itemlink(); + String updatesbox_item_icon(); + String updatesbox_item_content(); + String updatesbox_item_content_text(); + String updatesbox_item_content_text_boatclass(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.java index 9422074fdc5..f639c97ab56 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.java @@ -13,7 +13,6 @@ import com.sap.sailing.gwt.home.client.place.event.regatta.EventRegattaView; import com.sap.sailing.gwt.home.client.place.event.regatta.EventRegattaView.Presenter; import com.sap.sailing.gwt.home.client.place.event.regatta.RegattaTabView; import com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload.RefreshManager; -import com.sap.sailing.gwt.ui.shared.dispatch.event.GetEventOverviewStageAction; import com.sap.sailing.gwt.ui.shared.dispatch.event.GetLiveRacesForRegattaAction; /** @@ -46,11 +45,9 @@ public class RegattaOverviewTabView extends Composite implements RegattaTabView< initWidget(ourUiBinder.createAndBindUi(this)); - // TODO CF RefreshManager refreshManager = new RefreshManager(this, currentPresenter.getDispatch()); - // TODO regatta specific depending on event type - refreshManager.add(stage, new GetEventOverviewStageAction(currentPresenter.getCtx().getEventDTO().getId())); + stage.setupRefresh(refreshManager); refreshManager.add(racesListLive, new GetLiveRacesForRegattaAction(currentPresenter.getCtx().getEventDTO() .getId(), currentPresenter.getCtx().getRegattaId())); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.ui.xml index 2d12ca41cf3..a2d8a30b0f2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/RegattaOverviewTabView.ui.xml @@ -7,7 +7,7 @@
    - +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/reload/RefreshableWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/reload/RefreshableWidget.java index d1d6e0b5844..2186f477d10 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/reload/RefreshableWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regatta/tabs/reload/RefreshableWidget.java @@ -1,8 +1,7 @@ package com.sap.sailing.gwt.home.client.place.event.regatta.tabs.reload; -import com.google.gwt.user.client.ui.IsWidget; import com.sap.sailing.gwt.ui.shared.dispatch.DTO; -public interface RefreshableWidget extends IsWidget { +public interface RefreshableWidget { void setData(D data, long nextUpdate, int updateNo); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/fakeseries/partials/header/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/fakeseries/partials/header/SeriesHeader.java index 75454a9f3df..8c8eae244e7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/fakeseries/partials/header/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/fakeseries/partials/header/SeriesHeader.java @@ -73,7 +73,7 @@ public class SeriesHeader extends Composite { } private void initFields() { - String logoUrl = series.getLogoImageURL() != null ? series.getLogoImageURL() : SeriesHeaderResources.INSTANCE.defaultEventLogoImage().getSafeUri().asString(); + String logoUrl = series.getLogoImage() != null ? series.getLogoImage().getSourceRef() : SeriesHeaderResources.INSTANCE.defaultEventLogoImage().getSafeUri().asString(); eventLogo.getStyle().setBackgroundImage("url(" + logoUrl + ")"); eventLogo.setTitle(series.getDisplayName()); eventName.setInnerText(series.getDisplayName()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceImpl.java index 0db0d71e9cf..37453d84ec8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceImpl.java @@ -14,39 +14,27 @@ import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RemoteSailingServerReference; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.gwt.ui.client.HomeService; import com.sap.sailing.gwt.ui.server.Activator; import com.sap.sailing.gwt.ui.server.ProxiedRemoteServiceServlet; import com.sap.sailing.gwt.ui.shared.eventlist.EventListEventDTO; import com.sap.sailing.gwt.ui.shared.eventlist.EventListViewDTO; -import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO; -import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO.EventType; -import com.sap.sailing.gwt.ui.shared.eventview.RegattaMetadataDTO; import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO; import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO.EventSeriesState; import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO; import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO; -import com.sap.sailing.gwt.ui.shared.general.EventState; +import com.sap.sailing.gwt.ui.shared.media.MediaConstants; import com.sap.sailing.gwt.ui.shared.media.MediaDTO; -import com.sap.sailing.gwt.ui.shared.media.MediaUtils; import com.sap.sailing.gwt.ui.shared.media.SailingImageDTO; import com.sap.sailing.gwt.ui.shared.media.SailingVideoDTO; -import com.sap.sailing.gwt.ui.shared.start.EventStageDTO; import com.sap.sailing.gwt.ui.shared.start.StageEventType; import com.sap.sailing.gwt.ui.shared.start.StartViewDTO; import com.sap.sailing.server.RacingEventService; @@ -57,18 +45,38 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; -import com.sap.sse.common.media.ImageSize; -import com.sap.sse.common.media.MediaType; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; import com.sap.sse.util.ServiceTrackerFactory; /** * The server side implementation of the RPC service. */ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements HomeService { + private static final long serialVersionUID = 3947782997746039939L; + + private static final int MAX_STAGE_EVENTS = 5; + private static final int MAX_RECENT_EVENTS = 3; + private static final int MAX_VIDEO_COUNT = 3; + + private final ServiceTracker racingEventServiceTracker; + + public HomeServiceImpl() { + BundleContext context = Activator.getDefault(); + + racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + } + + protected RacingEventService getService() { + return racingEventServiceTracker.getService(); + } + private interface EventVisitor { void visit(EventBase event, boolean onRemoteServer, URL baseURL); } + private static class EventHolder { EventBase event; boolean onRemoteServer; @@ -92,25 +100,6 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home } } - private static final long serialVersionUID = 3947782997746039939L; - private static final Logger logger = Logger.getLogger(HomeServiceImpl.class.getName()); - - private static final int MAX_STAGE_EVENTS = 5; - private static final int MAX_RECENT_EVENTS = 3; - private static final int MAX_VIDEO_COUNT = 3; - - private final ServiceTracker racingEventServiceTracker; - - public HomeServiceImpl() { - BundleContext context = Activator.getDefault(); - - racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); - } - - protected RacingEventService getService() { - return racingEventServiceTracker.getService(); // grab the service - } - public void forAllPublicEvents(EventVisitor visitor) throws MalformedURLException { URL requestedBaseURL = getRequestBaseURL(); for (Event event : getService().getAllEvents()) { @@ -179,18 +168,18 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home Pair pair = featuredEvents.get(i); StageEventType stageType = pair.getA(); EventHolder holder = pair.getB(); - result.addStageEvent(convertToEventStageDTO(holder.event, holder.baseURL, holder.onRemoteServer, stageType)); + result.addStageEvent(HomeServiceUtil.convertToEventStageDTO(holder.event, holder.baseURL, holder.onRemoteServer, stageType, getService())); EventReferenceDTO eventRef = new EventReferenceDTO(holder.event); - Iterable videosOfEvent = holder.event.getVideoURLs(); + Iterable videosOfEvent = holder.event.getVideos(); if (!Util.isEmpty(videosOfEvent) && result.getVideos().size() < MAX_VIDEO_COUNT) { - URL youTubeRandomUrl = HomeServiceUtil.getRandomURL(videosOfEvent); + VideoDescriptor youTubeRandomUrl = HomeServiceUtil.getRandomVideo(videosOfEvent); - MimeType type = MediaUtils.detectMimeTypeFromUrl(youTubeRandomUrl.toString()); - if (type.mediaType == MediaType.video) { - SailingVideoDTO candidate = new SailingVideoDTO(eventRef, youTubeRandomUrl.toString(), type, // - holder.event.getEndDate().asDate() // FIXME: using event enddate for now + MimeType type = youTubeRandomUrl.getMimeType(); + if (MediaConstants.SUPPORTED_VIDEO_TYPES.contains(type)) { + SailingVideoDTO candidate = new SailingVideoDTO(eventRef, youTubeRandomUrl.getURL().toString(), type, // + youTubeRandomUrl.getCreatedAtDate().asDate() ); candidate.setTitle(holder.event.getName()); result.addVideo(candidate); @@ -211,28 +200,24 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home for(EventHolder holder : recentEventsOfLast12Month) { if(result.getRecentEvents().size() < MAX_RECENT_EVENTS) { - result.addRecentEvent(convertToEventListDTO(holder.event, holder.baseURL, holder.onRemoteServer)); + result.addRecentEvent(HomeServiceUtil.convertToEventListDTO(holder.event, holder.baseURL, holder.onRemoteServer, getService())); } EventBase event = holder.event; EventReferenceDTO eventRef = new EventReferenceDTO(holder.event); - for (URL url : HomeServiceUtil.getSailingLovesPhotographyImages(event)) { - try { - ImageSize imageSize = event.getImageSize(url); - if(imageSize != null) { - // TODO: do we habe a title? - photoGalleryUrls.add(new SailingImageDTO(eventRef, url.toString(), imageSize, null)); - } - } catch (Exception e) { + for (ImageDescriptor url : HomeServiceUtil.getSailingLovesPhotographyImages(event)) { + if(url.hasSize()) { + SailingImageDTO sailingImageDTO = new SailingImageDTO(eventRef, url.getURL().toString(), null); + sailingImageDTO.setSizeInPx(url.getWidthInPx(), url.getHeightInPx()); + photoGalleryUrls.add(sailingImageDTO); } } - for (URL videoUrl : event.getVideoURLs()) { - - MimeType type = MediaUtils.detectMimeTypeFromUrl(videoUrl.toString()); - if (type.mediaType == MediaType.video) { - SailingVideoDTO candidate = new SailingVideoDTO(eventRef, videoUrl.toString(), type, // - holder.event.getEndDate().asDate() // FIXME: using event enddate for now + for (VideoDescriptor videoUrl : event.getVideos()) { + MimeType type = videoUrl.getMimeType(); + if (MediaConstants.SUPPORTED_VIDEO_TYPES.contains(type)) { + SailingVideoDTO candidate = new SailingVideoDTO(eventRef, videoUrl.getURL().toString(), type, // + videoUrl.getCreatedAtDate().asDate() ); candidate.setTitle(holder.event.getName()); videoCandidates.add(candidate); @@ -270,99 +255,6 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home // TODO media return result; } - - @Override - public EventViewDTO getEventViewById(UUID id) { - Event event = getService().getEvent(id); - if (event == null) { - throw new RuntimeException("Event not found"); - } - - EventViewDTO dto = new EventViewDTO(); - mapToMetadataDTO(event, dto); - - dto.setLogoImageURL(event.getLogoImageURL() == null ? null : event.getLogoImageURL().toString()); - dto.setOfficialWebsiteURL(event.getOfficialWebsiteURL() == null ? null : event.getOfficialWebsiteURL().toString()); - - dto.setHasMedia(HomeServiceUtil.hasMedia(event)); - dto.setState(HomeServiceUtil.calculateEventState(event)); - dto.setHasAnalytics(EventState.RUNNING.compareTo(dto.getState()) <= 0); - - boolean isFakeSeries = HomeServiceUtil.isFakeSeries(event); - - for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) { - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { - if(leaderboard instanceof RegattaLeaderboard) { - Regatta regatta = getService().getRegattaByName(leaderboard.getName()); - if(isFakeSeries && !HomeServiceUtil.isPartOfEvent(event, regatta)) { - continue; - } - - RegattaMetadataDTO regattaDTO = createRegattaMetadataDTO(leaderboardGroup, leaderboard); - regattaDTO.setStartDate(regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null); - regattaDTO.setEndDate(regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null); - regattaDTO.setState(HomeServiceUtil.calculateRegattaState(regattaDTO)); - dto.getRegattas().add(regattaDTO); - - } else if(leaderboard instanceof FlexibleLeaderboard) { - RegattaMetadataDTO regattaDTO = createRegattaMetadataDTO(leaderboardGroup, leaderboard); - - regattaDTO.setStartDate(null); - regattaDTO.setEndDate(null); - regattaDTO.setState(HomeServiceUtil.calculateRegattaState(regattaDTO)); - dto.getRegattas().add(regattaDTO); - } - } - } - - if (isFakeSeries) { - dto.setType(EventType.SERIES_EVENT); - - LeaderboardGroup overallLeaderboardGroup = event.getLeaderboardGroups().iterator().next(); - dto.setSeriesName(overallLeaderboardGroup.getDisplayName() != null ? overallLeaderboardGroup.getDisplayName() :overallLeaderboardGroup.getName()); - List fakeSeriesEvents = new ArrayList(); - - for (Event eventOfSeries : getService().getAllEvents()) { - for (LeaderboardGroup leaderboardGroup : eventOfSeries.getLeaderboardGroups()) { - if (overallLeaderboardGroup.equals(leaderboardGroup)) { - fakeSeriesEvents.add(eventOfSeries); - } - } - } - Collections.sort(fakeSeriesEvents, new Comparator() { - public int compare(Event e1, Event e2) { - return e1.getStartDate().compareTo(e2.getEndDate()); - } - }); - for(Event eventInSeries: fakeSeriesEvents) { - String displayName = getLocation(eventInSeries); - if(displayName == null) { - displayName = eventInSeries.getName(); - } - dto.getEventsOfSeries().add(new EventReferenceDTO(eventInSeries.getId(), displayName)); - } - } else { - dto.setType(dto.getRegattas().size() == 1 ? EventType.SINGLE_REGATTA: EventType.MULTI_REGATTA); - } - - // TODO fmittag: put in correct event ID - if("406cffc2-b491-4f7e-b78e-a21abd97006a".equals(event.getId().toString())) { - dto.setSailorsInfoURL("http://sailorsinfo.kieler-woche.de/"); - } - - return dto; - } - - private RegattaMetadataDTO createRegattaMetadataDTO(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard) { - RegattaMetadataDTO regattaDTO = new RegattaMetadataDTO(leaderboard.getName(), leaderboard.getName()); - regattaDTO.setBoatCategory(leaderboardGroup.getDisplayName() != null ? leaderboardGroup.getDisplayName() : leaderboardGroup.getName()); - regattaDTO.setCompetitorsCount(HomeServiceUtil.calculateCompetitorsCount(leaderboard)); - regattaDTO.setRaceCount(HomeServiceUtil.calculateRaceCount(leaderboard)); - regattaDTO.setTrackedRacesCount(HomeServiceUtil.calculateTrackedRaceCount(leaderboard)); - regattaDTO.setBoatClass(HomeServiceUtil.calculateBoatClass(leaderboard)); - - return regattaDTO; - } @Override public EventSeriesViewDTO getEventSeriesViewById(UUID id) { @@ -373,7 +265,8 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home EventSeriesViewDTO dto = new EventSeriesViewDTO(); dto.setId(id); - dto.setLogoImageURL(o.getLogoImageURL() == null ? null : o.getLogoImageURL().toString()); + ImageDescriptor logoImage = o.findImageWithTag(MediaTagConstants.LOGO); + dto.setLogoImage(logoImage != null ? HomeServiceUtil.convertToImageDTO(logoImage) : null); // TODO implement correctly. We currently do not show media for series. dto.setHasMedia(false); @@ -402,7 +295,7 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home } }); for(Event eventInSeries: fakeSeriesEvents) { - EventMetadataDTO eventOfSeries = convertToMetadataDTO(eventInSeries); + EventMetadataDTO eventOfSeries = HomeServiceUtil.convertToMetadataDTO(eventInSeries, getService()); dto.addEvent(eventOfSeries); oneEventStarted |= eventOfSeries.isStarted(); @@ -430,25 +323,20 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home EventReferenceDTO eventRef = new EventReferenceDTO(event); String eventName = event.getName(); - // TODO implement correctly and fill metadata MediaDTO media = new MediaDTO(); - for(URL url : HomeServiceUtil.getPhotoGalleryImageURLs(event)) { - ImageSize imageSize = null; - try { - imageSize = event.getImageSize(url); - } catch (InterruptedException | ExecutionException e) { - logger.log(Level.FINE, "Was unable to obtain image size for "+url+" earlier.", e); - } - SailingImageDTO entry = new SailingImageDTO(eventRef, url.toString(), imageSize, // - event.getEndDate().asDate() // FIXME: using the event end date for now, we need a better solution for migration - ); - entry.setTitle(eventName); + for(ImageDescriptor image : HomeServiceUtil.getPhotoGalleryImages(event)) { + SailingImageDTO entry = new SailingImageDTO(eventRef, image.getURL().toString(), image.getCreatedAtDate().asDate()); + entry.setSizeInPx(image.getWidthInPx(), image.getHeightInPx()); + entry.setTitle(image.getTitle() != null ? image.getTitle(): eventName); + entry.setSubtitle(image.getSubtitle()); + entry.setTags(image.getTags()); + entry.setCopyright(image.getCopyright()); media.addPhoto(entry); } - for(URL url : event.getVideoURLs()) { - MimeType type = MediaUtils.detectMimeTypeFromUrl(url.toString()); - if (type.mediaType == MediaType.video) { - SailingVideoDTO candidate = new SailingVideoDTO(eventRef, url.toString(), type, null); + for(VideoDescriptor url : event.getVideos()) { + MimeType type = url.getMimeType(); + if (MediaConstants.SUPPORTED_VIDEO_TYPES.contains(type)) { + SailingVideoDTO candidate = new SailingVideoDTO(eventRef, url.getURL().toString(), type, null); media.addVideo(candidate); } } @@ -468,7 +356,7 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home forAllPublicEvents(new EventVisitor() { @Override public void visit(EventBase event, boolean onRemoteServer, URL baseURL) { - EventListEventDTO eventDTO = convertToEventListDTO(event, baseURL, onRemoteServer); + EventListEventDTO eventDTO = HomeServiceUtil.convertToEventListDTO(event, baseURL, onRemoteServer, getService()); result.addEvent(eventDTO, getYear(eventDTO.getStartDate())); } }); @@ -480,58 +368,4 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home cal.setTime(date); return cal.get(Calendar.YEAR); } - - private EventStageDTO convertToEventStageDTO(EventBase event, URL baseURL, boolean onRemoteServer, StageEventType stageType) { - EventStageDTO dto = new EventStageDTO(); - mapToMetadataDTO(event, dto); - dto.setBaseURL(baseURL.toString()); - dto.setOnRemoteServer(onRemoteServer); - dto.setStageType(stageType); - dto.setStageImageURL(HomeServiceUtil.getStageImageURLAsString(event)); - return dto; - } - - private EventListEventDTO convertToEventListDTO(EventBase event, URL baseURL, boolean onRemoteServer) { - EventListEventDTO dto = new EventListEventDTO(); - mapToMetadataDTO(event, dto); - dto.setBaseURL(baseURL.toString()); - dto.setOnRemoteServer(onRemoteServer); - return dto; - } - - private EventMetadataDTO convertToMetadataDTO(EventBase event) { - EventMetadataDTO dto = new EventMetadataDTO(); - mapToMetadataDTO(event, dto); - return dto; - } - - private void mapToMetadataDTO(EventBase event, EventMetadataDTO dto) { - dto.setId((UUID) event.getId()); - dto.setDisplayName(event.getName()); - dto.setStartDate(event.getStartDate().asDate()); - dto.setEndDate(event.getEndDate().asDate()); - dto.setState(HomeServiceUtil.calculateEventState(event)); - dto.setVenue(event.getVenue().getName()); - if(HomeServiceUtil.isFakeSeries(event)) { - dto.setLocation(getLocation(event)); - } - dto.setThumbnailImageURL(HomeServiceUtil.findEventThumbnailImageUrlAsString(event)); - } - - public String getLocation(EventBase eventBase) { - if(!(eventBase instanceof Event)) { - return null; - } - Event event = (Event) eventBase; - for (Leaderboard leaderboard : event.getLeaderboardGroups().iterator().next().getLeaderboards()) { - if(leaderboard instanceof RegattaLeaderboard) { - Regatta regattaEntity = getService().getRegattaByName(leaderboard.getName()); - if(!HomeServiceUtil.isPartOfEvent(event, regattaEntity)) { - continue; - } - } - return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : leaderboard.getName(); - } - return null; - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java index b5c9696e94b..854f145cd51 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java @@ -2,14 +2,14 @@ package com.sap.sailing.gwt.server; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Random; -import java.util.concurrent.ExecutionException; +import java.util.UUID; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; @@ -21,24 +21,34 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.ui.shared.eventlist.EventListEventDTO; import com.sap.sailing.gwt.ui.shared.eventview.HasRegattaMetadata.RegattaState; import com.sap.sailing.gwt.ui.shared.eventview.RegattaMetadataDTO; +import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO; import com.sap.sailing.gwt.ui.shared.general.EventState; +import com.sap.sailing.gwt.ui.shared.media.MediaConstants; +import com.sap.sailing.gwt.ui.shared.start.EventStageDTO; +import com.sap.sailing.gwt.ui.shared.start.StageEventType; +import com.sap.sailing.server.RacingEventService; import com.sap.sse.common.Util; -import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.MediaDescriptor; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.gwt.client.media.ImageDTO; public final class HomeServiceUtil { + private HomeServiceUtil() { } private static final int MINIMUM_IMAGE_HEIGHT_FOR_SAILING_PHOTOGRAPHY_IN_PIXELS = 500; - private static final String STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "stage"; - private static final String THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "eventteaser"; public static String findEventThumbnailImageUrlAsString(EventBase event) { - URL url = findEventThumbnailImageUrl(event); - return url == null ? null : url.toString(); + ImageDescriptor url = findEventThumbnailImage(event); + return url == null ? null : url.getURL().toString(); } public static boolean isFakeSeries(EventBase event) { @@ -100,242 +110,37 @@ public final class HomeServiceUtil { return EventState.RUNNING; } - private static URL findEventThumbnailImageUrl(EventBase event) { - - final class ImageHolder { - - final int PERFECT_HEIGHT = 240; - final int PERFECT_WIDTH = 370; - final double PERFECT_RATIO = PERFECT_HEIGHT / (double) PERFECT_WIDTH; - final URL url; - final int height; - final int width; - final int size; - final double ratio; - - ImageHolder() { - this(null, null); - } - - ImageHolder(URL url, ImageSize imageSize) { - this(url, imageSize == null ? -1 : imageSize.getWidth(), imageSize == null ? -1 : imageSize.getHeight()); - } - - ImageHolder(URL url, int width, int height) { - this.url = url; - this.height = height; - this.width = width; - if(width == -1 || height == -1) { - this.size = -1; - this.ratio = -1; - } else { - this.size = height * width; - this.ratio = height / (double) width; - } - } - - boolean isBetterWorstcaseThan(ImageHolder otherImageHolder) { - - if (isNull()) { - return false; - } - - if (this.url != null && url.toString().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { - return false; - } - - if (otherImageHolder.isNull()) { - return true; - } - - if (!isBigEnough() && otherImageHolder.isBigEnough()) { - return true; - } - if (this.fitsRatio() && otherImageHolder.fitsRatio() && otherImageHolder.isBigEnough() - && otherImageHolder.isSmallerThan(this)) { - return true; - } - return false; - } - - boolean isSmallerThan(ImageHolder otherImageHolder) { - return size < otherImageHolder.size; - } - - boolean isBigEnough() { - return height >= PERFECT_HEIGHT && width >= PERFECT_WIDTH; - } - - boolean fitsRatio() { - return ratio == PERFECT_RATIO; - } - - boolean isPerfectFit() { - return (height == PERFECT_HEIGHT && width == PERFECT_WIDTH); - } - - boolean isNull() { - return size == -1; - } - } - - // search for name pattern - for (URL imageUrl : event.getImageURLs()) { - if (imageUrl.toString().toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { - return imageUrl; - } - } - - ImageHolder actualWorstcase = new ImageHolder(); - ImageHolder bestFit = new ImageHolder(); - - for (URL candidateImageUrl : getPhotoGalleryImageURLs(event)) { - ImageHolder candidate; - try { - candidate = new ImageHolder(candidateImageUrl, event.getImageSize(candidateImageUrl)); - } catch (Exception e) { - candidate = new ImageHolder(candidateImageUrl, -1, -1); - } - - if (candidate.isPerfectFit()) { - return candidate.url; - } - - if (candidate.fitsRatio() && candidate.isBigEnough()) { - if (candidate.isSmallerThan(bestFit)) { - bestFit = candidate; - } - } - - if (candidate.isBetterWorstcaseThan(actualWorstcase)) { - actualWorstcase = candidate; - } - - } - - if (!bestFit.isNull()) { - return bestFit.url; - } - if (!actualWorstcase.isNull()) { - return actualWorstcase.url; - } - return null; + private static ImageDescriptor findEventThumbnailImage(EventBase event) { + return event.findImageWithTag(MediaTagConstants.TEASER); } - /** - * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back - * rules: - *
      - *
    1. If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.
    2. - *
    3. If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered - * candidates.
    4. - *
    5. From all candidates, the one with the biggest known size (determined by the product of width and height) is - * chosen.
    6. - *
    7. If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.
    8. - *
    - */ public static String getStageImageURLAsString(final EventBase event) { - URL url = getStageImageURL(event); - return url == null ? null : url.toString(); + ImageDescriptor image = getStageImage(event); + return image == null ? null : image.getURL().toString(); } - /** - * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back - * rules: - *
      - *
    1. If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.
    2. - *
    3. If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered - * candidates.
    4. - *
    5. From all candidates, the one with the biggest known size (determined by the product of width and height) is - * chosen.
    6. - *
    7. If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.
    8. - *
    - */ - public static URL getStageImageURL(final EventBase event) { - final URL result; - if (Util.isEmpty(event.getImageURLs())) { - result = null; - } else { - Comparator stageImageComparator = new Comparator() { - @Override - public int compare(URL url1, URL url2) { - String o1 = url1.toString(); - String o2 = url2.toString(); - int result; - int o1NameHack = o1.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE) ? 1 : - o1.toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE) ? -1 : 0; - int o2NameHack = o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE) ? 1 : - o2.toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE) ? -1 : 0; - final int preResultBasedOnNameHack = o1NameHack - o2NameHack; - if (preResultBasedOnNameHack == 0) { - try { - result = compareBySize(url1, url2); - } catch (Exception e) { - result = 0; - } - } else { - result = preResultBasedOnNameHack; - } - return result; - } + public static ImageDescriptor getStageImage(final EventBase event) { + return event.findImageWithTag(MediaTagConstants.STAGE); + } - private int compareBySize(URL o1, URL o2) throws InterruptedException, ExecutionException { - final int result; - final ImageSize o1Size = event.getImageSize(o1); - final ImageSize o2Size = event.getImageSize(o2); - result = (o1Size == null ? 0 : (o1Size.getWidth() * o1Size.getHeight())) - - (o2Size == null ? 0 : (o2Size.getWidth() * o2Size.getHeight())); - return result; - } - }; - List sortedImageURLs = new ArrayList<>(); - for (URL url : event.getImageURLs()) { - sortedImageURLs.add(url); - } - Collections.sort(sortedImageURLs, stageImageComparator); - result = sortedImageURLs.get(sortedImageURLs.size() - 1); - } - return result; - } - public static List getPhotoGalleryImageURLsAsString(EventBase event) { - List urls = getPhotoGalleryImageURLs(event); + List urls = getPhotoGalleryImages(event); List result = new ArrayList(urls.size()); - for (URL url : urls) { - result.add(url.toString()); + for (ImageDescriptor url : urls) { + result.add(url.getURL().toString()); } return result; } - public static List getPhotoGalleryImageURLs(EventBase event) { - URL stageImageURL = getStageImageURL(event); // if set, exclude stage image from photo gallery - List result = new ArrayList<>(); - Iterable imageURLs = event.getImageURLs(); - - boolean first = true; - for (Iterator iter = imageURLs.iterator(); iter.hasNext(); ) { - URL imageUrl = iter.next(); - if(imageUrl.toString().toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { - continue; - } - if ((first && !iter.hasNext()) || !Util.equalsWithNull(imageUrl, stageImageURL)) { - result.add(imageUrl); - } - first = false; - } - return result; + public static List getPhotoGalleryImages(EventBase event) { + return event.findImagesWithTag(MediaTagConstants.GALLERY); } - public static List getSailingLovesPhotographyImages(EventBase event) { - final List acceptedImages = new LinkedList<>(); - for (URL candidateImageUrl : event.getImageURLs()) { - try { - ImageSize imageSize = event.getImageSize(candidateImageUrl); - if (imageSize != null && imageSize.getHeight() > MINIMUM_IMAGE_HEIGHT_FOR_SAILING_PHOTOGRAPHY_IN_PIXELS) { - acceptedImages.add(candidateImageUrl); - } - } catch (Exception e) { - // TODO what should we do here? + public static List getSailingLovesPhotographyImages(EventBase event) { + final List acceptedImages = new LinkedList<>(); + for (ImageDescriptor candidateImageUrl : event.getImages()) { + if (candidateImageUrl.hasSize() && candidateImageUrl.getHeightInPx() > MINIMUM_IMAGE_HEIGHT_FOR_SAILING_PHOTOGRAPHY_IN_PIXELS) { + acceptedImages.add(candidateImageUrl); } } return acceptedImages; @@ -389,12 +194,11 @@ public final class HomeServiceUtil { } public static boolean hasPhotos(Event event) { - // FIXME filter teaser images - return Util.size(event.getImageURLs()) > 1; + return event.hasImageWithTag(MediaTagConstants.GALLERY); } public static boolean hasVideos(Event event) { - return !Util.isEmpty(event.getVideoURLs()); + return !Util.isEmpty(event.getVideos()); } public static boolean isPartOfEvent(Event event, Regatta regattaEntity) { @@ -406,11 +210,189 @@ public final class HomeServiceUtil { return false; } - public static URL getRandomURL(Iterable urls) { + public static VideoDescriptor getRandomVideo(Iterable urls) { if(Util.isEmpty(urls)) { return null; } int size = Util.size(urls); return Util.get(urls, new Random(size).nextInt(size)); } + + public static VideoDescriptor getStageVideo(Event event, Locale locale, Collection rankedTags, boolean acceptOtherTags) { + VideoDescriptor bestMatch = null; + + for (VideoDescriptor videoCandidate : event.getVideos()) { + if(!MediaConstants.SUPPORTED_VIDEO_TYPES.contains(videoCandidate.getMimeType())) { + continue; + } + + if(!acceptOtherTags && !hasOneTag(videoCandidate, rankedTags)) { + continue; + } + + LocaleMatch localeMatch = matchLocale(videoCandidate, locale); + if(localeMatch == LocaleMatch.NO_MATCH) { + continue; + } + + if(bestMatch == null) { + bestMatch = videoCandidate; + continue; + } + + int compareByTag = compareByTag(videoCandidate, bestMatch, rankedTags); + if(compareByTag > 0 || (compareByTag == 0 && isBetter(videoCandidate, bestMatch, locale))) { + bestMatch = videoCandidate; + continue; + } + } + return bestMatch; + } + + private static int compareByTag(VideoDescriptor videoCandidate, VideoDescriptor bestMatch, + Collection rankedTags) { + for(String rankedTag : rankedTags) { + boolean hasTag = hasTag(videoCandidate, rankedTag); + boolean hasTagBestMatch = hasTag(bestMatch, rankedTag); + if(hasTag != hasTagBestMatch) { + return hasTag ? 1 : -1; + } + } + return 0; + } + + private static boolean isBetter(VideoDescriptor candidate, VideoDescriptor reference, Locale locale) { + LocaleMatch localeMatch = matchLocale(candidate, locale); + LocaleMatch localeMatchRef = matchLocale(reference, locale); + if(localeMatch != localeMatchRef) { + return localeMatch.compareTo(localeMatchRef) < 0 ? true : false; + } + + // TODO filter by length + + return candidate.getCreatedAtDate().compareTo(reference.getCreatedAtDate()) > 0; + } + + private static boolean hasTag(MediaDescriptor videoCandidate, String tag) { + return Util.contains(videoCandidate.getTags(), tag); + } + + private static boolean hasOneTag(MediaDescriptor videoCandidate, Collection acceptedTags) { + for(String tag : videoCandidate.getTags()) { + if(acceptedTags.contains(tag)) { + return true; + } + } + return false; + } + + private enum LocaleMatch { + PERFECT, NOT_TAGGED, EN_FALLBACK, NO_MATCH + } + + private static LocaleMatch matchLocale(VideoDescriptor videoCandidate, Locale locale) { + Locale localeOfCandidate = videoCandidate.getLocale(); + if(localeOfCandidate == null) { + return LocaleMatch.NOT_TAGGED; + } + if(videoCandidate.getLocale().equals(locale)) { + return LocaleMatch.PERFECT; + } + if(videoCandidate.getLocale().equals(Locale.ENGLISH)) { + return LocaleMatch.EN_FALLBACK; + } + return LocaleMatch.NO_MATCH; + } + + public static EventStageDTO convertToEventStageDTO(EventBase event, URL baseURL, boolean onRemoteServer, StageEventType stageType, RacingEventService service) { + EventStageDTO dto = new EventStageDTO(); + mapToMetadataDTO(event, dto, service); + dto.setBaseURL(baseURL.toString()); + dto.setOnRemoteServer(onRemoteServer); + dto.setStageType(stageType); + dto.setStageImageURL(HomeServiceUtil.getStageImageURLAsString(event)); + return dto; + } + + public static EventListEventDTO convertToEventListDTO(EventBase event, URL baseURL, boolean onRemoteServer, RacingEventService service) { + EventListEventDTO dto = new EventListEventDTO(); + mapToMetadataDTO(event, dto, service); + dto.setBaseURL(baseURL.toString()); + dto.setOnRemoteServer(onRemoteServer); + return dto; + } + + public static EventMetadataDTO convertToMetadataDTO(EventBase event, RacingEventService service) { + EventMetadataDTO dto = new EventMetadataDTO(); + mapToMetadataDTO(event, dto, service); + return dto; + } + + public static void mapToMetadataDTO(EventBase event, EventMetadataDTO dto, RacingEventService service) { + dto.setId((UUID) event.getId()); + dto.setDisplayName(getEventDisplayName(event, service)); + dto.setStartDate(event.getStartDate().asDate()); + dto.setEndDate(event.getEndDate().asDate()); + dto.setState(HomeServiceUtil.calculateEventState(event)); + dto.setVenue(event.getVenue().getName()); + if(HomeServiceUtil.isFakeSeries(event)) { + dto.setLocation(getLocation(event, service)); + } + dto.setThumbnailImageURL(HomeServiceUtil.findEventThumbnailImageUrlAsString(event)); + } + + public static String getEventDisplayName(EventBase event, RacingEventService service) { + if(isFakeSeries(event)) { + String seriesName = getSeriesName(event); + if(seriesName != null) { + String location = getLocation(event, service); + if(location != null) { + return seriesName + " - " + location; + } + } + } + return event.getName(); + } + + public static String getSeriesName(EventBase event) { + LeaderboardGroupBase overallLeaderboardGroup = event.getLeaderboardGroups().iterator().next(); + return getLeaderboardDisplayName(overallLeaderboardGroup); + } + + public static String getLeaderboardDisplayName(LeaderboardGroupBase overallLeaderboardGroup) { + return overallLeaderboardGroup.getDisplayName() != null ? overallLeaderboardGroup.getDisplayName() : overallLeaderboardGroup.getName(); + } + + public static String getLocation(EventBase eventBase, RacingEventService service) { + if(!(eventBase instanceof Event)) { + return null; + } + Event event = (Event) eventBase; + for (Leaderboard leaderboard : event.getLeaderboardGroups().iterator().next().getLeaderboards()) { + if(leaderboard instanceof RegattaLeaderboard) { + Regatta regattaEntity = service.getRegattaByName(leaderboard.getName()); + if(!HomeServiceUtil.isPartOfEvent(event, regattaEntity)) { + continue; + } + } + return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : leaderboard.getName(); + } + return null; + } + + public static ImageDTO convertToImageDTO(ImageDescriptor image) { + ImageDTO result = new ImageDTO(image.getURL().toString(), image.getCreatedAtDate() != null ? image.getCreatedAtDate().asDate() : null); + result.setCopyright(image.getCopyright()); + result.setTitle(image.getTitle()); + result.setSubtitle(image.getSubtitle()); + result.setMimeType(image.getMimeType()); + result.setSizeInPx(image.getWidthInPx(), image.getHeightInPx()); + result.setLocale(image.getLocale() != null ? image.getLocale().toString() : null); + List tags = new ArrayList(); + for(String tag: image.getTags()) { + tags.add(tag); + } + result.setTags(tags); + return result; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java index fe79a48ff9b..3945c293834 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java @@ -8,6 +8,8 @@ import java.util.List; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; public class EventCreateDialog extends EventDialog { @@ -29,9 +31,9 @@ public class EventCreateDialog extends EventDialog { endDateBox.setFormat("dd/mm/yyyy hh:ii"); officialWebsiteURLEntryField = createTextBox(null); officialWebsiteURLEntryField.setVisibleLength(50); - logoImageURLEntryField = createTextBox(null); - logoImageURLEntryField.setVisibleLength(50); isPublicCheckBox = createCheckbox(""); isPublicCheckBox.setValue(false); + imagesListComposite.fillImages(Collections.emptyList()); + videosListComposite.fillVideos(Collections.emptyList()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java index a779a8b589c..d6f5334575c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java @@ -23,8 +23,9 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; public class EventDetailsComposite extends Composite { private EventDTO event; @@ -38,11 +39,9 @@ public class EventDetailsComposite extends Composite { private final Label isPublic; private final Anchor officialWebsiteURL; private final Anchor eventOverviewURL; - private final Anchor logoImageURL; private final SimpleStringListComposite courseAreaNamesList; private final SimpleStringListComposite imageURLList; private final SimpleStringListComposite videoURLList; - private final SimpleStringListComposite sponsorImageURLList; private final SimpleStringListComposite leaderboardGroupList; private final CaptionPanel mainPanel; @@ -70,11 +69,9 @@ public class EventDetailsComposite extends Composite { isPublic = createLabelAndValueWidget(grid, currentRow++, stringMessages.isPublic(), "IsPublicLabel"); officialWebsiteURL = createAnchorAndValueWidget(grid, currentRow++, stringMessages.eventOfficialWebsiteURL(), "OfficialWebsiteURLLabel"); eventOverviewURL = createAnchorAndValueWidget(grid, currentRow++, stringMessages.eventOverviewURL(), "EventOverviewURLLabel"); - logoImageURL = createAnchorAndValueWidget(grid, currentRow++, stringMessages.eventLogoImageURL(), "LogoImageURLLabel"); courseAreaNamesList = createLableAndValueListWidget(grid, currentRow++, stringMessages.courseAreas(), "CourseAreaValueList"); - imageURLList = createLableAndValueListWidget(grid, currentRow++, stringMessages.imageURLs(), "ImageURLValueList"); - videoURLList = createLableAndValueListWidget(grid, currentRow++, stringMessages.videoURLs(), "VideoURLValueList"); - sponsorImageURLList = createLableAndValueListWidget(grid, currentRow++, stringMessages.sponsorImageURLs(), "SponsorImageURLValueList"); + imageURLList = createLableAndValueListWidget(grid, currentRow++, stringMessages.images(), "ImageURLValueList"); + videoURLList = createLableAndValueListWidget(grid, currentRow++, stringMessages.videos(), "VideoURLValueList"); leaderboardGroupList = createLableAndValueListWidget(grid, currentRow++, stringMessages.leaderboardGroups(), "LeaderboardGroupValueList"); for(int i=0; i < rows; i++) { @@ -136,8 +133,6 @@ public class EventDetailsComposite extends Composite { String regattaOverviewLink = EntryPointLinkFactory.createRegattaOverviewLink(regattaOverviewURLParameters); eventOverviewURL.setText(regattaOverviewLink); eventOverviewURL.setHref(regattaOverviewLink); - logoImageURL.setText(event.getLogoImageURL()); - logoImageURL.setHref(event.getLogoImageURL()); List courseAreaNames = new ArrayList<>(); if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) { @@ -148,14 +143,15 @@ public class EventDetailsComposite extends Composite { courseAreaNamesList.setValues(courseAreaNames); List imageURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getImageURLs(), imageURLStringsAsList); + for(ImageDTO image: event.getImages()) { + imageURLStringsAsList.add(image.getSourceRef()); + } imageURLList.setValues(imageURLStringsAsList); List videoURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getVideoURLs(), videoURLStringsAsList); + for(VideoDTO video: event.getVideos()) { + videoURLStringsAsList.add(video.getSourceRef()); + } videoURLList.setValues(videoURLStringsAsList); - List sponsorImageURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getSponsorImageURLs(), sponsorImageURLStringsAsList); - sponsorImageURLList.setValues(sponsorImageURLStringsAsList); List leaderboardGroupNamesAsList = new ArrayList<>(); for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { leaderboardGroupNamesAsList.add(leaderboardGroupDTO.getName()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index 74d0956b7bc..428762287a4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -1,7 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -30,6 +29,8 @@ import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; public abstract class EventDialog extends DataEntryDialogWithBootstrap { protected StringMessages stringMessages; @@ -41,14 +42,12 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap protected CheckBox isPublicCheckBox; protected UUID id; protected TextBox officialWebsiteURLEntryField; - protected TextBox logoImageURLEntryField; protected StringListInlineEditorComposite courseAreaNameList; - protected StringListInlineEditorComposite imageURLList; - protected StringListInlineEditorComposite videoURLList; - protected StringListInlineEditorComposite sponsorImageURLList; protected StringConstantsListEditorComposite leaderboardGroupList; protected List availableLeaderboardGroups; - + protected ImagesListComposite imagesListComposite; + protected VideosListComposite videosListComposite; + protected static class EventParameterValidator implements Validator { private StringMessages stringMessages; @@ -133,21 +132,6 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50)); courseAreaNameList.addValueChangeHandler(valueChangeHandler); - final List imageSuggestionURLs = Arrays.asList(new String[] { "http://", "https://", "http://www.", "https://www" }); - imageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), - /* suggestValues */ imageSuggestionURLs, stringMessages.enterImageURL(), 80)); - imageURLList.addValueChangeHandler(valueChangeHandler); - List videoURLSuggestions = new ArrayList<>(imageSuggestionURLs); - videoURLSuggestions.add("http://www.youtube.com/watch?v="); - videoURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), - /* suggestValues */ videoURLSuggestions, stringMessages.enterVideoURL(), 80)); - videoURLList.addValueChangeHandler(valueChangeHandler); - sponsorImageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), - /* suggestValues */ imageSuggestionURLs, stringMessages.enterSponsorImageURL(), 80)); - sponsorImageURLList.addValueChangeHandler(valueChangeHandler); List leaderboardGroupNames = new ArrayList<>(); for(LeaderboardGroupDTO leaderboardGroupDTO: availableLeaderboardGroups) { leaderboardGroupNames.add(leaderboardGroupDTO.getName()); @@ -156,6 +140,9 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap new StringConstantsListEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), leaderboardGroupNames, "Select a leaderboard group...")); leaderboardGroupList.addValueChangeHandler(valueChangeHandler); + + imagesListComposite = new ImagesListComposite(stringMessages); + videosListComposite = new VideosListComposite(stringMessages); } @Override @@ -170,7 +157,6 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap result.setName(nameEntryField.getText()); result.setDescription(descriptionEntryField.getText()); result.setOfficialWebsiteURL(officialWebsiteURLEntryField.getText().trim().isEmpty() ? null : officialWebsiteURLEntryField.getText().trim()); - result.setLogoImageURL(logoImageURLEntryField.getText().trim().isEmpty() ? null : logoImageURLEntryField.getText().trim()); result.startDate = startDateBox.getValue(); result.endDate = endDateBox.getValue(); result.isPublic = isPublicCheckBox.getValue(); @@ -182,14 +168,11 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap courseAreaDTO.setName(courseAreaName); courseAreas.add(courseAreaDTO); } - for (String imageURL : imageURLList.getValue()) { - result.addImageURL(imageURL); + for(ImageDTO image: imagesListComposite.getAllImages()) { + result.addImage(image); } - for (String videoURL : videoURLList.getValue()) { - result.addVideoURL(videoURL); - } - for (String sponsorImageURL : sponsorImageURLList.getValue()) { - result.addSponsorImageURL(sponsorImageURL); + for(VideoDTO video: videosListComposite.getAllVideos()) { + result.addVideo(video); } result.venue = new VenueDTO(venueEntryField.getText(), courseAreas); return result; @@ -204,7 +187,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap panel.add(additionalWidget); } - Grid formGrid = new Grid(8, 2); + Grid formGrid = new Grid(7, 2); panel.add(formGrid); formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":")); @@ -221,17 +204,14 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap formGrid.setWidget(5, 1, isPublicCheckBox); formGrid.setWidget(6, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); formGrid.setWidget(6, 1, officialWebsiteURLEntryField); - formGrid.setWidget(7, 0, new Label(stringMessages.eventLogoImageURL() + ":")); - formGrid.setWidget(7, 1, logoImageURLEntryField); TabLayoutPanel tabPanel = new TabLayoutPanel(30, Unit.PX); tabPanel.setHeight("250px"); panel.add(tabPanel); tabPanel.add(new ScrollPanel(leaderboardGroupList), stringMessages.leaderboardGroups()); tabPanel.add(new ScrollPanel(courseAreaNameList), stringMessages.courseAreas()); - tabPanel.add(new ScrollPanel(imageURLList), stringMessages.imageURLs()); - tabPanel.add(new ScrollPanel(videoURLList), stringMessages.videoURLs()); - tabPanel.add(new ScrollPanel(sponsorImageURLList), stringMessages.sponsorImageURLs()); + tabPanel.add(new ScrollPanel(imagesListComposite), "Images"); + tabPanel.add(new ScrollPanel(videosListComposite), "Videos"); return panel; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java index 97a25711eab..ec9b7a269ed 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java @@ -8,7 +8,6 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sse.common.Util; public class EventEditDialog extends EventDialog { public EventEditDialog(EventDTO event, Collection otherExistingEvents, List availableLeaderboardGroups, StringMessages stringMessages, DialogCallback callback) { @@ -37,22 +36,13 @@ public class EventEditDialog extends EventDialog { } officialWebsiteURLEntryField = createTextBox(event.getOfficialWebsiteURL()); officialWebsiteURLEntryField.setVisibleLength(50); - logoImageURLEntryField = createTextBox(event.getLogoImageURL()); - logoImageURLEntryField.setVisibleLength(50); courseAreaNameList.setValue(courseAreaNames); - List imageURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getImageURLs(), imageURLStringsAsList); - imageURLList.setValue(imageURLStringsAsList); - List videoURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getVideoURLs(), videoURLStringsAsList); - videoURLList.setValue(videoURLStringsAsList); - List sponsorImageURLStringsAsList = new ArrayList<>(); - Util.addAll(event.getSponsorImageURLs(), sponsorImageURLStringsAsList); - sponsorImageURLList.setValue(sponsorImageURLStringsAsList); List leaderboardGroupNames = new ArrayList<>(); for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { leaderboardGroupNames.add(leaderboardGroupDTO.getName()); } leaderboardGroupList.setValue(leaderboardGroupNames); + imagesListComposite.fillImages(event.getImages()); + videosListComposite.fillVideos(event.getVideos()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java index f1c6cc289bf..082ea76cc48 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java @@ -283,12 +283,12 @@ public class EventListComposite extends Composite implements EventsRefresher, Le return builder.toSafeHtml(); } }; - - TextColumn imageURLsColumn = new TextColumn() { + + TextColumn imagesColumn = new TextColumn() { @Override public String getValue(EventDTO event) { String result = ""; - int imageCount = Util.size(event.getImageURLs()); + int imageCount = Util.size(event.getImages()); if(imageCount > 0) { result = imageCount + " image(s)"; } @@ -296,11 +296,11 @@ public class EventListComposite extends Composite implements EventsRefresher, Le } }; - TextColumn videoURLsColumn = new TextColumn() { + TextColumn videosColumn = new TextColumn() { @Override public String getValue(EventDTO event) { String result = ""; - int videoCount = Util.size(event.getVideoURLs()); + int videoCount = Util.size(event.getVideos()); if(videoCount > 0) { result = videoCount + " video(s)"; } @@ -308,18 +308,6 @@ public class EventListComposite extends Composite implements EventsRefresher, Le } }; - TextColumn sponsorImageURLsColumn = new TextColumn() { - @Override - public String getValue(EventDTO event) { - String result = ""; - int sponsorImageCount = Util.size(event.getSponsorImageURLs()); - if(sponsorImageCount > 0) { - result = sponsorImageCount + " sponsor image(s)"; - } - return result; - } - }; - SafeHtmlCell associatedRegattasCell = new SafeHtmlCell(); Column associatedRegattasColumn = new Column(associatedRegattasCell) { @Override @@ -358,9 +346,6 @@ public class EventListComposite extends Composite implements EventsRefresher, Le isPublicColumn.setSortable(true); startEndDateColumn.setSortable(true); courseAreasColumn.setSortable(true); - imageURLsColumn.setSortable(true); - videoURLsColumn.setSortable(true); - sponsorImageURLsColumn.setSortable(true); leaderboardGroupsColumn.setSortable(true); table.addColumn(eventSelectionCheckboxColumn, eventSelectionCheckboxColumn.getHeader()); @@ -370,9 +355,8 @@ public class EventListComposite extends Composite implements EventsRefresher, Le table.addColumn(isPublicColumn, stringMessages.isPublic()); table.addColumn(courseAreasColumn, stringMessages.courseAreas()); table.addColumn(leaderboardGroupsColumn, stringMessages.leaderboardGroups()); - table.addColumn(imageURLsColumn, stringMessages.imageURLs()); - table.addColumn(videoURLsColumn, stringMessages.videoURLs()); - table.addColumn(sponsorImageURLsColumn, stringMessages.sponsorImageURLs()); + table.addColumn(imagesColumn, stringMessages.images()); + table.addColumn(videosColumn, stringMessages.videos()); table.addColumn(associatedRegattasColumn, stringMessages.regattas()); table.addColumn(eventActionColumn, stringMessages.actions()); table.setSelectionModel(eventSelectionCheckboxColumn.getSelectionModel(), eventSelectionCheckboxColumn.getSelectionManager()); @@ -507,8 +491,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le sailingService.updateEvent(oldEvent.id, oldEvent.getName(), updatedEvent.getDescription(), updatedEvent.startDate, updatedEvent.endDate, updatedEvent.venue, updatedEvent.isPublic, updatedEventLeaderboardGroupIds, - updatedEvent.getOfficialWebsiteURL(), updatedEvent.getLogoImageURL(), - updatedEvent.getImageURLs(), updatedEvent.getVideoURLs(), updatedEvent.getSponsorImageURLs(), + updatedEvent.getOfficialWebsiteURL(), updatedEvent.getImages(), updatedEvent.getVideos(), new AsyncCallback() { @Override public void onFailure(Throwable t) { @@ -579,9 +562,8 @@ public class EventListComposite extends Composite implements EventsRefresher, Le courseAreaNames.add(courseAreaDTO.getName()); } sailingService.createEvent(newEvent.getName(), newEvent.getDescription(), newEvent.startDate, newEvent.endDate, - newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), - newEvent.getVideoURLs(), newEvent.getSponsorImageURLs(), newEvent.getLogoImageURL(), - newEvent.getOfficialWebsiteURL(), new AsyncCallback() { + newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getOfficialWebsiteURL(), + newEvent.getImages(), newEvent.getVideos(), new AsyncCallback() { @Override public void onFailure(Throwable t) { errorReporter.reportError("Error trying to create new event " + newEvent.getName() + ": " + t.getMessage()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageConfigImagesBarCell.java new file mode 100644 index 00000000000..be162a0fd4e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageConfigImagesBarCell.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.text.shared.SafeHtmlRenderer; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; + +public class ImageConfigImagesBarCell extends ImagesBarCell { + static final String ACTION_REMOVE = "ACTION_REMOVE"; + static final String ACTION_EDIT = "ACTION_EDIT"; + private final StringMessages stringMessages; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + public ImageConfigImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + public ImageConfigImagesBarCell(SafeHtmlRenderer renderer, StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + return Arrays.asList( + new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())) + ); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageCreateDialog.java new file mode 100644 index 00000000000..c3304669cc6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageCreateDialog.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.media.ImageDTO; + +public class ImageCreateDialog extends ImageDialog { + + public ImageCreateDialog(String initialTag, StringMessages stringMessages, DialogCallback callback) { + super(new ImageParameterValidator(stringMessages), stringMessages, callback); + createdAtLabel = new Label(creationDate.toString()); + titleTextBox = createTextBox(null); + titleTextBox.setVisibleLength(50); + subtitleTextBox = createTextBox(null); + subtitleTextBox.setVisibleLength(50); + copyrightTextBox = createTextBox(null); + copyrightTextBox.setVisibleLength(50); + widthInPxBox = createIntegerBox(null, 10); + heightInPxBox = createIntegerBox(null, 10); + List tags = new ArrayList<>(); + if(initialTag != null && !initialTag.isEmpty()) { + tags.add(initialTag); + } + tagsListEditor.setValue(tags); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java new file mode 100644 index 00000000000..e934b453208 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java @@ -0,0 +1,155 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Image; +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.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.media.MediaConstants; +import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload; +import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.controls.IntegerBox; +import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.gwt.client.media.ImageDTO; + +public abstract class ImageDialog extends DataEntryDialog { + protected final StringMessages stringMessages; + protected final URLFieldWithFileUpload imageURLAndUploadComposite; + protected final Date creationDate; + protected Label createdAtLabel; + protected TextBox titleTextBox; + protected TextBox subtitleTextBox; + protected TextBox copyrightTextBox; + protected IntegerBox widthInPxBox; + protected IntegerBox heightInPxBox; + protected StringListInlineEditorComposite tagsListEditor; + + final Image image; + + protected static class ImageParameterValidator implements Validator { + private StringMessages stringMessages; + + public ImageParameterValidator(StringMessages stringMessages) { + this.stringMessages = stringMessages; + } + + @Override + public String getErrorMessage(ImageDTO imageToValidate) { + String errorMessage = null; + + if(imageToValidate.getSourceRef() == null || imageToValidate.getSourceRef().isEmpty()) { + errorMessage = stringMessages.pleaseEnterNonEmptyUrl(); + } + return errorMessage; + } + } + + public ImageDialog(ImageParameterValidator validator, StringMessages stringMessages, DialogCallback callback) { + super(stringMessages.image(), null, stringMessages.ok(), stringMessages.cancel(), validator, + callback); + this.stringMessages = stringMessages; + this.creationDate = new Date(); + getDialogBox().getWidget().setWidth("730px"); + + image = new Image(); + image.addLoadHandler(new LoadHandler() { + @Override + public void onLoad(LoadEvent event) { + int width = image.getOffsetWidth(); + int height = image.getOffsetHeight(); + + if(width > 0 && height > 0) { + widthInPxBox.setValue(width); + heightInPxBox.setValue(height); + } + } + }); + image.getElement().getStyle().setPosition(Position.ABSOLUTE); + image.getElement().getStyle().setVisibility(Visibility.HIDDEN); + + imageURLAndUploadComposite = new URLFieldWithFileUpload(stringMessages); + imageURLAndUploadComposite.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + image.setUrl(event.getValue()); + } + }); + + + tagsListEditor = new StringListInlineEditorComposite(Collections. emptyList(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ + MediaConstants.imageTagSuggestions, "Enter tags for the image", 50)); + } + + @Override + protected ImageDTO getResult() { + ImageDTO result = new ImageDTO(imageURLAndUploadComposite.getURL(), creationDate); + result.setTitle(titleTextBox.getValue()); + result.setSubtitle(subtitleTextBox.getValue()); + result.setCopyright(copyrightTextBox.getValue()); + if(widthInPxBox.getValue() != null && heightInPxBox.getValue() != null) { + result.setSizeInPx(widthInPxBox.getValue(), heightInPxBox.getValue()); + } + List tags = new ArrayList(); + for (String tag: tagsListEditor.getValue()) { + tags.add(tag); + } + result.setTags(tags); + return result; + } + + @Override + protected Widget getAdditionalWidget() { + final VerticalPanel panel = new VerticalPanel(); + panel.setWidth("100%"); + Widget additionalWidget = super.getAdditionalWidget(); + if (additionalWidget != null) { + panel.add(additionalWidget); + } + + Grid formGrid = new Grid(8, 2); + panel.add(formGrid); + + formGrid.setWidget(0, 0, new Label("Created at:")); + formGrid.setWidget(0, 1, createdAtLabel); + formGrid.setWidget(1, 0, new Label("Image URL:")); + formGrid.setWidget(1, 1, imageURLAndUploadComposite); + formGrid.setWidget(2, 0, new Label(stringMessages.title() + ":")); + formGrid.setWidget(2, 1, titleTextBox); + formGrid.setWidget(3, 0, new Label("Subtitle:")); + formGrid.setWidget(3, 1, subtitleTextBox); + formGrid.setWidget(4, 0, new Label("Copyright:")); + formGrid.setWidget(4, 1, copyrightTextBox); + formGrid.setWidget(5, 0, new Label("Width in px:")); + formGrid.setWidget(5, 1, widthInPxBox); + formGrid.setWidget(6, 0, new Label("Height in px:")); + formGrid.setWidget(6, 1, heightInPxBox); + formGrid.setWidget(7, 0, new Label("Tags:")); + formGrid.setWidget(7, 1, tagsListEditor); + + panel.add(image); + + return panel; + } + + @Override + public void show() { + super.show(); + imageURLAndUploadComposite.setFocus(true); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageEditDialog.java new file mode 100644 index 00000000000..45fb0194bc7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageEditDialog.java @@ -0,0 +1,27 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.media.ImageDTO; + +public class ImageEditDialog extends ImageDialog { + public ImageEditDialog(ImageDTO image, StringMessages stringMessages, DialogCallback callback) { + super(new ImageParameterValidator(stringMessages), stringMessages, callback); + createdAtLabel = new Label(image.getCreatedAtDate().toString()); + imageURLAndUploadComposite.setURL(image.getSourceRef()); + titleTextBox = createTextBox(image.getTitle()); + titleTextBox.setVisibleLength(50); + subtitleTextBox = createTextBox(image.getSubtitle()); + subtitleTextBox.setVisibleLength(50); + copyrightTextBox = createTextBox(image.getCopyright()); + copyrightTextBox.setVisibleLength(50); + widthInPxBox = createIntegerBox(image.getWidthInPx(), 10); + heightInPxBox = createIntegerBox(image.getHeightInPx(), 10); + List tags = new ArrayList(); + tags.addAll(image.getTags()); + tagsListEditor.setValue(tags); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImagesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImagesListComposite.java new file mode 100644 index 00000000000..19c34d9bc26 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImagesListComposite.java @@ -0,0 +1,313 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.client.SafeHtmlTemplates; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.media.ImageDTO; + +/** + * /** A composite showing the list of media images + * + * @author Frank Mittag (C5163974) + */ +public class ImagesListComposite extends Composite { + private final StringMessages stringMessages; + + private CellTable imageTable; + private SingleSelectionModel imageSelectionModel; + private ListDataProvider imageListDataProvider; + private final Label noImagesLabel; + + private final SimplePanel mainPanel; + private final VerticalPanel panel; + + public static class AnchorCell extends AbstractCell { + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { + sb.append(safeHtml); + } + } + + interface AnchorTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{1}") + SafeHtml cell(String url, String displayName); + } + + private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + + public ImagesListComposite(final StringMessages stringMessages) { + this.stringMessages = stringMessages; + + mainPanel = new SimplePanel(); + panel = new VerticalPanel(); + mainPanel.setWidget(panel); + + HorizontalPanel imagesControlsPanel = new HorizontalPanel(); + imagesControlsPanel.setSpacing(5); + panel.add(imagesControlsPanel); + + Button addPhotoBtn = new Button("Add gallery photo"); + addPhotoBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateImageDialog(MediaTagConstants.GALLERY); + } + }); + imagesControlsPanel.add(addPhotoBtn); + + Button addStateImageBtn = new Button("Add stage image"); + addStateImageBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateImageDialog(MediaTagConstants.STAGE); + } + }); + imagesControlsPanel.add(addStateImageBtn); + + Button addEventTeaseImageBtn = new Button("Add teaser image"); + addEventTeaseImageBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateImageDialog(MediaTagConstants.TEASER); + } + }); + imagesControlsPanel.add(addEventTeaseImageBtn); + + Button addLogoImageBtn = new Button("Add event logo"); + addLogoImageBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateImageDialog(MediaTagConstants.LOGO); + } + }); + imagesControlsPanel.add(addLogoImageBtn); + + imageSelectionModel = new SingleSelectionModel(); + imageListDataProvider = new ListDataProvider(); + imageTable = createImagesTable(); + imageTable.ensureDebugId("ImagesCellTable"); + imageTable.setVisible(false); + + imageSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + } + }); + + panel.add(imageTable); + + noImagesLabel = new Label("No images defined yet."); + noImagesLabel.ensureDebugId("NoImagesLabel"); + noImagesLabel.setWordWrap(false); + panel.add(noImagesLabel); + + initWidget(mainPanel); + } + + private CellTable createImagesTable() { + CellTable table = new CellTable(/* pageSize */10000, tableRes); + imageListDataProvider.addDataDisplay(table); + table.setWidth("100%"); + + AnchorCell anchorCell = new AnchorCell(); + Column shortImageNameColumn = new Column(anchorCell) { + @Override + public SafeHtml getValue(ImageDTO image) { + String linkName = ""; + String link = image.getSourceRef(); + if(link.endsWith("/")) { + link = link.substring(0, link.length() - 1); + } + int index = link.lastIndexOf("/"); + if(index > 0) { + linkName = link.substring(index+1, link.length()); + } + return ANCHORTEMPLATE.cell(image.getSourceRef(), linkName); + } + }; + + TextColumn titleColumn = new TextColumn() { + @Override + public String getValue(ImageDTO image) { + return image.getTitle() != null ? image.getTitle() : ""; + } + }; + TextColumn sizeColumn = new TextColumn() { + @Override + public String getValue(ImageDTO image) { + String imageSize = ""; + if (image.getWidthInPx() != null && image.getHeightInPx() != null) { + imageSize = image.getWidthInPx() + " x " + image.getHeightInPx(); + } + return imageSize; + } + }; + + TextColumn createdAtDateColumn = new TextColumn() { + @Override + public String getValue(ImageDTO image) { + return DateAndTimeFormatterUtil.formatDateAndTime(image.getCreatedAtDate()); + } + }; + + SafeHtmlCell tagsCell = new SafeHtmlCell(); + Column tagsColumn = new Column(tagsCell) { + @Override + public SafeHtml getValue(ImageDTO image) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + int tagsCount = image.getTags().size(); + int i = 1; + for (String tag : image.getTags()) { + builder.appendEscaped(tag); + if (i < tagsCount) { + builder.appendHtmlConstant(", "); + // not more than 4 tags per line + if (i % 4 == 0) { + builder.appendHtmlConstant("
    "); + } + } + i++; + } + return builder.toSafeHtml(); + } + }; + + ImagesBarColumn imageActionColumn = new ImagesBarColumn( + new ImageConfigImagesBarCell(stringMessages)); + imageActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, ImageDTO image, String value) { + if (ImageConfigImagesBarCell.ACTION_REMOVE.equals(value)) { + imageListDataProvider.getList().remove(image); + updateTableVisisbilty(); + } else if (ImageConfigImagesBarCell.ACTION_EDIT.equals(value)) { + openEditImageDialog(image); + } + } + }); + + titleColumn.setSortable(true); + createdAtDateColumn.setSortable(true); + + table.addColumn(shortImageNameColumn, "Short name:"); + table.addColumn(titleColumn, stringMessages.title()); + table.addColumn(createdAtDateColumn, "Created At"); + table.addColumn(sizeColumn, "Size"); + table.addColumn(tagsColumn, "Tags"); + table.addColumn(imageActionColumn, stringMessages.actions()); + table.addColumnSortHandler(getImageTableColumnSortHandler(imageListDataProvider.getList(), titleColumn, createdAtDateColumn)); + table.getColumnSortList().push(createdAtDateColumn); + + return table; + } + + private ListHandler getImageTableColumnSortHandler(List imageRecords, + TextColumn titleColumn, TextColumn createdAtDateColumn) { + ListHandler result = new ListHandler(imageRecords); + result.setComparator(titleColumn, new Comparator() { + @Override + public int compare(ImageDTO i1, ImageDTO i2) { + return new NaturalComparator().compare(i1.getTitle(), i2.getTitle()); + } + }); + result.setComparator(createdAtDateColumn, new Comparator() { + @Override + public int compare(ImageDTO e1, ImageDTO e2) { + int result; + if (e1.getCreatedAtDate() != null && e2.getCreatedAtDate() != null) { + result = e2.getCreatedAtDate().compareTo(e1.getCreatedAtDate()); + } else if (e1.getCreatedAtDate() == null && e2.getCreatedAtDate() != null) { + result = 1; + } else if (e1.getCreatedAtDate() != null && e2.getCreatedAtDate() == null) { + result = -1; + } else { + result = 0; + } + return result; + } + }); + return result; + } + + private void openCreateImageDialog(String initialTag) { + ImageCreateDialog dialog = new ImageCreateDialog(initialTag, stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(ImageDTO newImage) { + imageListDataProvider.getList().add(newImage); + updateTableVisisbilty(); + } + }); + dialog.show(); + } + + private void openEditImageDialog(final ImageDTO selectedImage) { + ImageEditDialog dialog = new ImageEditDialog(selectedImage, stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(ImageDTO updatedImage) { + imageListDataProvider.getList().remove(selectedImage); + imageListDataProvider.getList().add(updatedImage); + updateTableVisisbilty(); + } + }); + dialog.show(); + } + + private void updateTableVisisbilty() { + if (imageListDataProvider.getList().isEmpty()) { + imageTable.setVisible(false); + noImagesLabel.setVisible(true); + } else { + imageTable.setVisible(true); + noImagesLabel.setVisible(false); + } + } + + public void fillImages(List images) { + imageSelectionModel.clear(); + imageListDataProvider.getList().clear(); + imageListDataProvider.getList().addAll(images); + + updateTableVisisbilty(); + } + + public List getAllImages() { + return imageListDataProvider.getList(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaImportDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaImportDialog.java index 30b093196d8..8a5c64fdc6a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaImportDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaImportDialog.java @@ -30,14 +30,11 @@ public class RegattaImportDialog extends DataEntryDialog{ protected CheckBox isPublicCheckBox; protected UUID id; protected TextBox officialWebsiteURLEntryField; - protected TextBox logoImageURLEntryField; protected StringListInlineEditorComposite courseAreaNameList; protected StringListInlineEditorComposite imageURLList; protected StringListInlineEditorComposite videoURLList; protected StringListInlineEditorComposite sponsorImageURLList; - - public RegattaImportDialog(StringMessages stringMessages, String message, com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator validator, com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback callback) { @@ -53,7 +50,7 @@ public class RegattaImportDialog extends DataEntryDialog{ panel.add(additionalWidget); } - Grid formGrid = new Grid(8, 2); + Grid formGrid = new Grid(7, 2); panel.add(formGrid); formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":")); @@ -70,8 +67,6 @@ public class RegattaImportDialog extends DataEntryDialog{ formGrid.setWidget(5, 1, isPublicCheckBox); formGrid.setWidget(6, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); formGrid.setWidget(6, 1, officialWebsiteURLEntryField); - formGrid.setWidget(7, 0, new Label(stringMessages.eventLogoImageURL() + ":")); - formGrid.setWidget(7, 1, logoImageURLEntryField); panel.add(createHeadlineLabel(stringMessages.courseAreas())); panel.add(courseAreaNameList); @@ -89,7 +84,6 @@ public class RegattaImportDialog extends DataEntryDialog{ result.setName(nameEntryField.getText()); result.setDescription(descriptionEntryField.getText()); result.setOfficialWebsiteURL(officialWebsiteURLEntryField.getText().trim().isEmpty() ? null : officialWebsiteURLEntryField.getText().trim()); - result.setLogoImageURL(logoImageURLEntryField.getText().trim().isEmpty() ? null : logoImageURLEntryField.getText().trim()); result.startDate = startDateBox.getValue(); result.endDate = endDateBox.getValue(); result.isPublic = isPublicCheckBox.getValue(); @@ -101,15 +95,6 @@ public class RegattaImportDialog extends DataEntryDialog{ courseAreaDTO.setName(courseAreaName); courseAreas.add(courseAreaDTO); } - for (String imageURL : imageURLList.getValue()) { - result.addImageURL(imageURL); - } - for (String videoURL : videoURLList.getValue()) { - result.addVideoURL(videoURL); - } - for (String sponsorImageURL : sponsorImageURLList.getValue()) { - result.addSponsorImageURL(sponsorImageURL); - } result.venue = new VenueDTO(venueEntryField.getText(), courseAreas); return "OK"; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 65d0a11e75c..2dfebccfcfc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -246,9 +246,9 @@ public class StructureImportManagementPanel extends FlowPanel implements Regatta courseAreaNames.add(courseAreaDTO.getName()); } sailingService.createEvent(newEvent.getName(), newEvent.getDescription(), newEvent.startDate, newEvent.endDate, - newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), - newEvent.getVideoURLs(), newEvent.getSponsorImageURLs(), newEvent.getLogoImageURL(), - newEvent.getOfficialWebsiteURL(), new AsyncCallback() { + newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getOfficialWebsiteURL(), + newEvent.getImages(), newEvent.getVideos(), + new AsyncCallback() { @Override public void onFailure(Throwable t) { errorReporter.reportError(stringMessages.errorTryingToCreateNewEvent(newEvent.getName(), t.getMessage())); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoConfigImagesBarCell.java new file mode 100644 index 00000000000..ba8665c8697 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoConfigImagesBarCell.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.text.shared.SafeHtmlRenderer; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; + +public class VideoConfigImagesBarCell extends ImagesBarCell { + static final String ACTION_REMOVE = "ACTION_REMOVE"; + static final String ACTION_EDIT = "ACTION_EDIT"; + private final StringMessages stringMessages; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + public VideoConfigImagesBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + public VideoConfigImagesBarCell(SafeHtmlRenderer renderer, StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + return Arrays.asList( + new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())) + ); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoCreateDialog.java new file mode 100644 index 00000000000..ef66b5843d4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoCreateDialog.java @@ -0,0 +1,25 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collections; + +import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.gwt.client.media.VideoDTO; + +public class VideoCreateDialog extends VideoDialog { + + public VideoCreateDialog(StringMessages stringMessages, DialogCallback callback) { + super(new VideoParameterValidator(stringMessages), stringMessages, callback); + createdAtLabel = new Label(creationDate.toString()); + titleTextBox = createTextBox(null); + titleTextBox.setVisibleLength(50); + subtitleTextBox = createTextBox(null); + subtitleTextBox.setVisibleLength(50); + copyrightTextBox = createTextBox(null); + copyrightTextBox.setVisibleLength(50); + lengthIntegerBox = createIntegerBox(null, 10); + tagsListEditor.setValue(Collections.emptyList()); + setSelectedMimeType(MimeType.unknown); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java new file mode 100644 index 00000000000..5682a23d45a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java @@ -0,0 +1,193 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.google.gwt.core.shared.GWT; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; +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.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.media.MediaConstants; +import com.sap.sse.common.Util; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload; +import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.controls.IntegerBox; +import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.gwt.client.media.VideoDTO; + +public abstract class VideoDialog extends DataEntryDialog { + private static final String NO_LOCALE_TEXT = "---"; + protected final StringMessages stringMessages; + protected final URLFieldWithFileUpload videoURLAndUploadComposite; + protected final Date creationDate; + protected Label createdAtLabel; + protected TextBox titleTextBox; + protected TextBox subtitleTextBox; + protected TextBox copyrightTextBox; + protected final ListBox mimeTypeListBox; + protected final ListBox localeListBox; + protected IntegerBox lengthIntegerBox; + protected final URLFieldWithFileUpload thumbnailURLAndUploadComposite; + protected StringListInlineEditorComposite tagsListEditor; + + protected static class VideoParameterValidator implements Validator { + private StringMessages stringMessages; + + public VideoParameterValidator(StringMessages stringMessages) { + this.stringMessages = stringMessages; + } + + @Override + public String getErrorMessage(VideoDTO videoToValidate) { + String errorMessage = null; + + if(videoToValidate.getSourceRef() == null || videoToValidate.getSourceRef().isEmpty()) { + errorMessage = stringMessages.pleaseEnterNonEmptyUrl(); + } else if (videoToValidate.getMimeType() == null) { + errorMessage = "You must select the mime type for the video."; + } + return errorMessage; + } + } + + public VideoDialog(VideoParameterValidator validator, StringMessages stringMessages, DialogCallback callback) { + super(stringMessages.video(), null, stringMessages.ok(), stringMessages.cancel(), validator, + callback); + this.stringMessages = stringMessages; + this.creationDate = new Date(); + getDialogBox().getWidget().setWidth("730px"); + + mimeTypeListBox = createListBox(false); + mimeTypeListBox.addItem(MimeType.unknown.name()); + mimeTypeListBox.addItem(MimeType.aac.name()); + mimeTypeListBox.addItem(MimeType.mp4.name()); + mimeTypeListBox.addItem(MimeType.ogg.name()); + mimeTypeListBox.addItem(MimeType.ogv.name()); + mimeTypeListBox.addItem(MimeType.qt.name()); + mimeTypeListBox.addItem(MimeType.youtube.name()); + mimeTypeListBox.addItem(MimeType.vimeo.name()); + + GWT.debugger(); + localeListBox = createListBox(false); + localeListBox.addItem(NO_LOCALE_TEXT, ""); + localeListBox.addItem("English", "en"); + localeListBox.addItem("German", "de"); + localeListBox.addItem("Russian", "ru"); + localeListBox.addItem("Chinese", "zh"); + + videoURLAndUploadComposite = new URLFieldWithFileUpload(stringMessages); + videoURLAndUploadComposite.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + thumbnailURLAndUploadComposite = new URLFieldWithFileUpload(stringMessages); + + tagsListEditor = new StringListInlineEditorComposite(Collections. emptyList(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ + MediaConstants.videoTagSuggestions, "Enter tags for the video", 50)); + } + + @Override + protected VideoDTO getResult() { + VideoDTO result = new VideoDTO(videoURLAndUploadComposite.getURL(), getSelectedMimeType(), creationDate); + result.setTitle(titleTextBox.getValue()); + result.setSubtitle(subtitleTextBox.getValue()); + result.setCopyright(copyrightTextBox.getValue()); + result.setLocale(getSelectedLocale()); + List tags = new ArrayList(); + for (String tag: tagsListEditor.getValue()) { + tags.add(tag); + } + result.setTags(tags); + return result; + } + + protected void setSelectedMimeType(MimeType mimeType) { + for(int i = 0; i < mimeTypeListBox.getItemCount(); i++) { + if(mimeTypeListBox.getItemText(i).equals(mimeType.name())) { + mimeTypeListBox.setSelectedIndex(i); + break; + } + } + } + + private MimeType getSelectedMimeType() { + MimeType result = null; + int selectedIndex = mimeTypeListBox.getSelectedIndex(); + if(selectedIndex >= 0) { + result = MimeType.valueOf(mimeTypeListBox.getSelectedValue()); + } + return result; + } + + protected void setSelectedLocale(String locale) { + for(int i = 0; i < localeListBox.getItemCount(); i++) { + if(Util.equalsWithNull(localeListBox.getValue(i), locale)) { + localeListBox.setSelectedIndex(i); + return; + } + } + localeListBox.setSelectedIndex(0); + } + + private String getSelectedLocale() { + return localeListBox.getSelectedValue(); + } + + @Override + protected Widget getAdditionalWidget() { + final VerticalPanel panel = new VerticalPanel(); + panel.setWidth("100%"); + Widget additionalWidget = super.getAdditionalWidget(); + if (additionalWidget != null) { + panel.add(additionalWidget); + } + + Grid formGrid = new Grid(10, 2); + panel.add(formGrid); + + formGrid.setWidget(0, 0, new Label("Created at:")); + formGrid.setWidget(0, 1, createdAtLabel); + formGrid.setWidget(1, 0, new Label("Video URL:")); + formGrid.setWidget(1, 1, videoURLAndUploadComposite); + formGrid.setWidget(2, 0, new Label("Mime Type:")); + formGrid.setWidget(2, 1, mimeTypeListBox); + + + formGrid.setWidget(3, 0, new Label(stringMessages.title() + ":")); + formGrid.setWidget(3, 1, titleTextBox); + formGrid.setWidget(4, 0, new Label("Subtitle:")); + formGrid.setWidget(4, 1, subtitleTextBox); + formGrid.setWidget(5, 0, new Label("Copyright:")); + formGrid.setWidget(5, 1, copyrightTextBox); + formGrid.setWidget(6, 0, new Label("Language:")); + formGrid.setWidget(6, 1, localeListBox); + formGrid.setWidget(7, 0, new Label("Tags:")); + formGrid.setWidget(7, 1, tagsListEditor); + + formGrid.setWidget(8, 0, new Label("Video-Length (s):")); + formGrid.setWidget(8, 1, lengthIntegerBox); + formGrid.setWidget(9, 0, new Label("Thumbnail-URL:")); + formGrid.setWidget(9, 1, thumbnailURLAndUploadComposite); + + return panel; + } + + @Override + public void show() { + super.show(); + videoURLAndUploadComposite.setFocus(true); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoEditDialog.java new file mode 100644 index 00000000000..c8c2f10f5a4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoEditDialog.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.media.VideoDTO; + +public class VideoEditDialog extends VideoDialog { + public VideoEditDialog(VideoDTO video, StringMessages stringMessages, DialogCallback callback) { + super(new VideoParameterValidator(stringMessages), stringMessages, callback); + createdAtLabel = new Label(video.getCreatedAtDate().toString()); + videoURLAndUploadComposite.setURL(video.getSourceRef()); + titleTextBox = createTextBox(video.getTitle()); + titleTextBox.setVisibleLength(50); + subtitleTextBox = createTextBox(video.getSubtitle()); + subtitleTextBox.setVisibleLength(50); + copyrightTextBox = createTextBox(video.getCopyright()); + copyrightTextBox.setVisibleLength(50); + lengthIntegerBox = createIntegerBox(video.getLengthInSeconds(), 10); + List tags = new ArrayList(); + tags.addAll(video.getTags()); + tagsListEditor.setValue(tags); + setSelectedMimeType(video.getMimeType()); + setSelectedLocale(video.getLocale()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideosListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideosListComposite.java new file mode 100644 index 00000000000..ee17228b92c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideosListComposite.java @@ -0,0 +1,286 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.client.SafeHtmlTemplates; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; +import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; +import com.sap.sse.gwt.client.media.VideoDTO; + +/** + * /** A composite showing the list of media videos + * + * @author Frank Mittag (C5163974) + */ +public class VideosListComposite extends Composite { + private final StringMessages stringMessages; + + private CellTable videoTable; + private SingleSelectionModel videoSelectionModel; + private ListDataProvider videoListDataProvider; + private final Label noVideosLabel; + + private final SimplePanel mainPanel; + private final VerticalPanel panel; + + public static class AnchorCell extends AbstractCell { + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { + sb.append(safeHtml); + } + } + + interface AnchorTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{1}") + SafeHtml cell(String url, String displayName); + } + + private static AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + + public VideosListComposite(final StringMessages stringMessages) { + this.stringMessages = stringMessages; + + mainPanel = new SimplePanel(); + panel = new VerticalPanel(); + mainPanel.setWidget(panel); + + HorizontalPanel videosControlsPanel = new HorizontalPanel(); + videosControlsPanel.setSpacing(5); + panel.add(videosControlsPanel); + + Button createVideoBtn = new Button("Add video"); + createVideoBtn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + openCreateVideoDialog(); + } + }); + videosControlsPanel.add(createVideoBtn); + + videoSelectionModel = new SingleSelectionModel(); + videoListDataProvider = new ListDataProvider(); + videoTable = createVideosTable(); + videoTable.ensureDebugId("VideosCellTable"); + videoTable.setVisible(false); + + videoSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + } + }); + + panel.add(videoTable); + + noVideosLabel = new Label("No videos defined yet."); + noVideosLabel.ensureDebugId("NoVideosLabel"); + noVideosLabel.setWordWrap(false); + panel.add(noVideosLabel); + + initWidget(mainPanel); + } + + private CellTable createVideosTable() { + CellTable table = new CellTable(/* pageSize */10000, tableRes); + videoListDataProvider.addDataDisplay(table); + table.setWidth("100%"); + + AnchorCell anchorCell = new AnchorCell(); + Column shortVideoNameColumn = new Column(anchorCell) { + @Override + public SafeHtml getValue(VideoDTO video) { + String linkName = ""; + int index = video.getSourceRef().lastIndexOf("/"); + if(index > 0) { + linkName = video.getSourceRef().substring(index+1, video.getSourceRef().length()); + } + return ANCHORTEMPLATE.cell(video.getSourceRef(), linkName); + } + }; + + TextColumn titleColumn = new TextColumn() { + @Override + public String getValue(VideoDTO image) { + return image.getTitle() != null ? image.getTitle() : ""; + } + }; + + TextColumn createdAtDateColumn = new TextColumn() { + @Override + public String getValue(VideoDTO video) { + return DateAndTimeFormatterUtil.formatDateAndTime(video.getCreatedAtDate()); + } + }; + + TextColumn mimeTypeColumn = new TextColumn() { + @Override + public String getValue(VideoDTO video) { + return video.getMimeType() != null ? video.getMimeType().name() : ""; + } + }; + + TextColumn localeColumn = new TextColumn() { + @Override + public String getValue(VideoDTO video) { + return video.getLocale() != null ? video.getLocale() : ""; + } + }; + + SafeHtmlCell tagsCell = new SafeHtmlCell(); + Column tagsColumn = new Column(tagsCell) { + @Override + public SafeHtml getValue(VideoDTO video) { + SafeHtmlBuilder builder = new SafeHtmlBuilder(); + int tagsCount = video.getTags().size(); + int i = 1; + for (String tag : video.getTags()) { + builder.appendEscaped(tag); + if (i < tagsCount) { + builder.appendHtmlConstant(", "); + // not more than 4 tags per line + if (i % 4 == 0) { + builder.appendHtmlConstant("
    "); + } + } + i++; + } + return builder.toSafeHtml(); + } + }; + + ImagesBarColumn videoActionColumn = new ImagesBarColumn( + new VideoConfigImagesBarCell(stringMessages)); + videoActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, VideoDTO video, String value) { + if (ImageConfigImagesBarCell.ACTION_REMOVE.equals(value)) { + videoListDataProvider.getList().remove(video); + updateTableVisisbilty(); + } else if (ImageConfigImagesBarCell.ACTION_EDIT.equals(value)) { + openEditVideoDialog(video); + } + } + }); + + titleColumn.setSortable(true); + createdAtDateColumn.setSortable(true); + + table.addColumn(shortVideoNameColumn, "Short name"); + table.addColumn(titleColumn, stringMessages.title()); + table.addColumn(createdAtDateColumn, "Created At"); + table.addColumn(mimeTypeColumn, stringMessages.mimeType()); + table.addColumn(localeColumn, "Lang"); + table.addColumn(tagsColumn, "Tags"); + table.addColumn(videoActionColumn, stringMessages.actions()); + table.addColumnSortHandler(getVideoTableColumnSortHandler(videoListDataProvider.getList(), titleColumn, createdAtDateColumn)); + table.getColumnSortList().push(createdAtDateColumn); + + return table; + } + + private ListHandler getVideoTableColumnSortHandler(List videoRecords, + TextColumn titleColumn, TextColumn createdAtDateColumn) { + ListHandler result = new ListHandler(videoRecords); + result.setComparator(titleColumn, new Comparator() { + @Override + public int compare(VideoDTO i1, VideoDTO i2) { + return new NaturalComparator().compare(i1.getTitle(), i2.getTitle()); + } + }); + result.setComparator(createdAtDateColumn, new Comparator() { + @Override + public int compare(VideoDTO e1, VideoDTO e2) { + int result; + if (e1.getCreatedAtDate() != null && e2.getCreatedAtDate() != null) { + result = e2.getCreatedAtDate().compareTo(e1.getCreatedAtDate()); + } else if (e1.getCreatedAtDate() == null && e2.getCreatedAtDate() != null) { + result = 1; + } else if (e1.getCreatedAtDate() != null && e2.getCreatedAtDate() == null) { + result = -1; + } else { + result = 0; + } + return result; + } + }); + return result; + } + + private void openCreateVideoDialog() { + VideoCreateDialog dialog = new VideoCreateDialog(stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(VideoDTO newVideo) { + videoListDataProvider.getList().add(newVideo); + updateTableVisisbilty(); + } + }); + dialog.show(); + } + + private void openEditVideoDialog(final VideoDTO selectedVideo) { + VideoEditDialog dialog = new VideoEditDialog(selectedVideo, stringMessages, new DialogCallback() { + @Override + public void cancel() { + } + + @Override + public void ok(VideoDTO updatedVideo) { + videoListDataProvider.getList().remove(selectedVideo); + videoListDataProvider.getList().add(updatedVideo); + updateTableVisisbilty(); + } + }); + dialog.show(); + } + + private void updateTableVisisbilty() { + if (videoListDataProvider.getList().isEmpty()) { + videoTable.setVisible(false); + noVideosLabel.setVisible(true); + } else { + videoTable.setVisible(true); + noVideosLabel.setVisible(false); + } + } + + public void fillVideos(List videos) { + videoSelectionModel.clear(); + videoListDataProvider.getList().clear(); + videoListDataProvider.getList().addAll(videos); + + updateTableVisisbilty(); + } + + public List getAllVideos() { + return videoListDataProvider.getList(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeService.java index 304c9d0d1d4..429621a4b60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeService.java @@ -4,20 +4,17 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.RemoteService; import com.sap.sailing.gwt.ui.shared.eventlist.EventListViewDTO; -import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO; import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO; import com.sap.sailing.gwt.ui.shared.media.MediaDTO; import com.sap.sailing.gwt.ui.shared.start.StartViewDTO; public interface HomeService extends RemoteService { - EventViewDTO getEventViewById(UUID id); - EventSeriesViewDTO getEventSeriesViewById(UUID id); MediaDTO getMediaForEvent(UUID eventId); MediaDTO getMediaForEventSeries(UUID seriesId); - + EventListViewDTO getEventListView() throws Exception; StartViewDTO getStartView() throws Exception; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeServiceAsync.java index 7198af22700..01042a528bd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HomeServiceAsync.java @@ -4,7 +4,6 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.gwt.ui.shared.eventlist.EventListViewDTO; -import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO; import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO; import com.sap.sailing.gwt.ui.shared.media.MediaDTO; import com.sap.sailing.gwt.ui.shared.start.StartViewDTO; @@ -14,10 +13,8 @@ import com.sap.sailing.gwt.ui.shared.start.StartViewDTO; * The async counterpart of {@link HomeService} */ public interface HomeServiceAsync { - void getEventViewById(UUID id, AsyncCallback callback); - void getEventSeriesViewById(UUID id, AsyncCallback asyncCallback); - + void getMediaForEvent(UUID eventId, AsyncCallback callback); void getMediaForEventSeries(UUID seriesId, AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index d41a0374f8b..4963289df96 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -94,6 +94,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.mail.MailException; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.gwt.client.filestorage.FileStorageManagementGwtService; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; /** * The client side stub for the RPC service. Usually, when a null date is passed to @@ -294,12 +296,10 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS EventDTO updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, - String logoImageURL, Iterable imageURLs, Iterable videoURLs, - Iterable sponsorImageURLs) throws Exception; + Iterable images, Iterable videos) throws Exception; EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, - boolean isPublic, List courseAreaNames, Iterable imageURLs, - Iterable videoURLs, Iterable sponsorImageURLs, String logoImageURL, String officialWebsiteURL) throws Exception; + boolean isPublic, List courseAreaNames, String officialWebsiteURL, Iterable images, Iterable videos) throws Exception; void removeEvent(UUID eventId); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 17b7ada94e8..3048991ff99 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -83,6 +83,8 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.gwt.client.BuildVersionRetriever; import com.sap.sse.gwt.client.filestorage.FileStorageManagementGwtServiceAsync; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; /** * The async counterpart of {@link SailingService} @@ -365,13 +367,12 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void removeEvents(Collection eventIds, AsyncCallback asyncCallback); void createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, boolean isPublic, - List courseAreaNames, Iterable imageURLs, Iterable videoURLs, - Iterable sponsorImageURLs, String logoImageURL, String officialWebsiteURL, AsyncCallback callback); + List courseAreaNames, String officialWebsiteURL, Iterable images, Iterable videos, + AsyncCallback callback); void updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, - String logoImageURL, Iterable imageURLs, Iterable videoURLs, - Iterable sponsorImageURLs, AsyncCallback callback); + Iterable images, Iterable videos, AsyncCallback callback); void createCourseArea(UUID eventId, String courseAreaName, AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 942cb8ec6fd..6a6eb4c65c2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1082,6 +1082,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String photos(); String videos(); String image(); + String images(); String video(); String flagImageURL(); String imageURLs(); @@ -1290,6 +1291,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String raceIsCanceled(); String raceIsPostponed(); String startingInMinutes(@PluralCount(DefaultRule_0_1_n.class) int minutes); + String latestNews(); String showCompetitorTimeOnTimeFactorColumn(); String showCompetitorTimeOnDistanceAllowanceColumn(); String rankingMetric(); @@ -1309,4 +1311,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String raceCalculatedTimeAtEstimatedArrivalAtCompetitorFarthestAheadInSecondsTooltip(); String sailorInfo(); String sailorInfoLongText(); + String resultsUpdate(); + String competitorWonRace(String competitorName); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 71501436ce8..5031095902d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1056,6 +1056,7 @@ media=Media photos=Photos videos=Videos image=Image +images=Images video=Video flagImageURL=Flag image URL (image renders with 18x12px) imageURLs=Image URLs @@ -1254,10 +1255,13 @@ analyseRace=Analyse race nextRaceStartingIn=Next race starts in startingIn={0} starts in raceIsPlanned=Planned -raceIsCanceled=Canceled +raceIsCanceled=Abandoned raceIsPostponed=Postponed startingInMinutes=Starts in {0,number} min startingInMinutes[none]=Starting now +latestNews=Latest News +resultsUpdate=Result update +competitorWonRace={0} won race showCompetitorTimeOnTimeFactorColumn=Time on Time Factor showCompetitorTimeOnDistanceAllowanceColumn=Time on Distance Allowance rankingMetric=Ranking diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 676b8e34404..7eaac84e13c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1050,6 +1050,7 @@ media=Medien photos=Fotos videos=Videos image=Bild +images=Bilder video=Video flagImageURL=Flaggenbild-URL (wird mit 18x12px Größe gezeigt) imageURLs=Bilder-URLs @@ -1248,6 +1249,9 @@ raceIsCanceled=Abgebrochen raceIsPostponed=Verschoben startingInMinutes=Startet in {0,number} min startingInMinutes[none]=Startet gleich +latestNews=Neuigkeiten +resultsUpdate=Neue Ergebnisse +competitorWonRace={0} gewann die Wettfahrt showCompetitorTimeOnTimeFactorColumn=Zeit-für-Zeit Faktor showCompetitorTimeOnDistanceAllowanceColumn=Zeit-für-Distanz Gutschrift rankingMetric=Wertung/Handicap diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index e38f7f943ac..d72df5494e2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1010,6 +1010,7 @@ media=Media photos=Photos videos=Videos image=Image +images=Images video=Video flagImageURL=Flag image URL (image renders with 18x12px) imageURLs=Image URLs @@ -1196,6 +1197,9 @@ raceIsCanceled=Canceled raceIsPostponed=Postponed startingInMinutes=Starts in {0,number} min startingInMinutes[none]=Starting now +latestNews=Последние Новости +resultsUpdate=Результаты обновление +competitorWonRace={0} won race showCompetitorTimeOnTimeFactorColumn=Time on Time Factor showCompetitorTimeOnDistanceAllowanceColumn=Time on Distance Allowance rankingMetric=Ranking diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 9fd9f948e43..5258afc357f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -1249,6 +1249,9 @@ raceIsCanceled=Canceled raceIsPostponed=Postponed startingInMinutes=Starts in {0,number} min startingInMinutes[none]=Starting now +latestNews=最新消息 +resultsUpdate=結果更新 +competitorWonRace={0} won race showCompetitorTimeOnTimeFactorColumn=Time on Time Factor showCompetitorTimeOnDistanceAllowanceColumn=Time on Distance Allowance rankingMetric=Ranking diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 2970f01f639..cf3c2afc995 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -431,10 +431,18 @@ import com.sap.sse.common.Util.Triple; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.common.mail.MailException; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.MediaUtils; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.filestorage.FileStorageService; import com.sap.sse.filestorage.InvalidPropertiesException; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; import com.sap.sse.gwt.server.filestorage.FileStorageServiceDTOUtils; import com.sap.sse.gwt.shared.filestorage.FileStorageServiceDTO; import com.sap.sse.gwt.shared.filestorage.FileStorageServicePropertyErrorsDTO; @@ -3292,48 +3300,30 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public EventDTO updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURLString, - String logoImageURLString, Iterable imageURLStrings, Iterable videoURLStrings, - Iterable sponsorImageURLStrings) throws MalformedURLException { + Iterable images, Iterable videos) throws MalformedURLException { TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; URL officialWebsiteURL = officialWebsiteURLString != null ? new URL(officialWebsiteURLString) : null; - URL logoImageURL = logoImageURLString != null ? new URL(logoImageURLString) : null; - List imageURLs = createURLsFromStrings(imageURLStrings); - List videoURLs = createURLsFromStrings(videoURLStrings); - List sponsorimagieURLs = createURLsFromStrings(sponsorImageURLStrings); + List eventImages = convertToImages(images); + List eventVideos = convertToVideos(videos); getService().apply( new UpdateEvent(eventId, eventName, eventDescription, startTimePoint, endTimePoint, venue.getName(), - isPublic, leaderboardGroupIds, logoImageURL, officialWebsiteURL, imageURLs, videoURLs, - sponsorimagieURLs)); + isPublic, leaderboardGroupIds, officialWebsiteURL, eventImages, eventVideos)); return getEventById(eventId, false); } - /** - * @param urlStrings - * @return - * @throws MalformedURLException - */ - private List createURLsFromStrings(Iterable urlStrings) throws MalformedURLException { - List imageURLs = new ArrayList<>(); - for (String imageURLString : urlStrings) { - imageURLs.add(new URL(imageURLString)); - } - return imageURLs; - } - @Override public EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, - boolean isPublic, List courseAreaNames, Iterable imageURLs, - Iterable videoURLs, Iterable sponsorImageURLs, String logoImageURLAsString, String officialWebsiteURLAsString) + boolean isPublic, List courseAreaNames, String officialWebsiteURLAsString, Iterable images, Iterable videos) throws MalformedURLException { UUID eventUuid = UUID.randomUUID(); TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; + List eventImages = convertToImages(images); + List eventVideos = convertToVideos(videos); getService().apply( new CreateEvent(eventName, eventDescription, startTimePoint, endTimePoint, venue, isPublic, eventUuid, - createURLsFromStrings(imageURLs), createURLsFromStrings(videoURLs), - createURLsFromStrings(sponsorImageURLs), - logoImageURLAsString == null ? null : new URL(logoImageURLAsString), officialWebsiteURLAsString == null ? null : new URL(officialWebsiteURLAsString))); + officialWebsiteURLAsString == null ? null : new URL(officialWebsiteURLAsString), eventImages, eventVideos)); for (String courseAreaName : courseAreaNames) { createCourseArea(eventUuid, courseAreaName); } @@ -3412,33 +3402,119 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.id = (UUID) event.getId(); eventDTO.setDescription(event.getDescription()); eventDTO.setOfficialWebsiteURL(event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); - if (event.getLogoImageURL() == null) { - eventDTO.setLogoImageURL(null); - } else { - eventDTO.setLogoImageURL(event.getLogoImageURL().toString()); - setImageSize(event, eventDTO, event.getLogoImageURL()); + for(ImageDescriptor image: event.getImages()) { + eventDTO.addImage(convertToImageDTO(image)); } - for(URL url: event.getSponsorImageURLs()) { - eventDTO.addSponsorImageURL(url.toString()); - setImageSize(event, eventDTO, url); - } - for(URL url: event.getImageURLs()) { - eventDTO.addImageURL(url.toString()); - setImageSize(event, eventDTO, url); - } - for(URL url: event.getVideoURLs()) { - eventDTO.addVideoURL(url.toString()); + for(VideoDescriptor video: event.getVideos()) { + eventDTO.addVideo(convertToVideoDTO(video)); } } - private void setImageSize(EventBase event, EventBaseDTO eventDTO, URL imageURL) { - try { - eventDTO.setImageSize(imageURL.toString(), event.getImageSize(imageURL)); - } catch (InterruptedException | ExecutionException e) { - logger.log(Level.FINE, "Was unable to obtain image size for "+imageURL+" earlier.", e); + private List convertToImages(Iterable images) throws MalformedURLException { + List eventImages = new ArrayList(); + for (ImageDTO image : images) { + try { + eventImages.add(convertToImage(image)); + } catch(Exception e) { + // broken URLs are not being stored + } } + return eventImages; + } + + private List convertToVideos(Iterable videos) throws MalformedURLException { + List eventVideos = new ArrayList(); + for (VideoDTO video : videos) { + try { + eventVideos.add(convertToVideo(video)); + } catch(Exception e) { + // broken URLs are not being stored + } + } + return eventVideos; + } + + private ImageDescriptor convertToImage(ImageDTO image) throws MalformedURLException { + ImageDescriptor result = new ImageDescriptorImpl(new URL(image.getSourceRef()), new MillisecondsTimePoint(image.getCreatedAtDate())); + result.setCopyright(image.getCopyright()); + result.setTitle(image.getTitle()); + result.setSubtitle(image.getSubtitle()); + result.setCopyright(image.getCopyright()); + result.setSize(image.getWidthInPx(), image.getHeightInPx()); + result.setLocale(toLocale(image.getLocale())); + for (String tag : image.getTags()) { + result.addTag(tag); + } + return result; + } + + private VideoDescriptor convertToVideo(VideoDTO video) throws MalformedURLException { + MimeType mimeType = video.getMimeType(); + if(mimeType == null || mimeType == MimeType.unknown) { + mimeType = MediaUtils.detectMimeTypeFromUrl(video.getSourceRef()); + } + VideoDescriptor result = new VideoDescriptorImpl(new URL(video.getSourceRef()), mimeType, new MillisecondsTimePoint(video.getCreatedAtDate())); + result.setCopyright(video.getCopyright()); + result.setTitle(video.getTitle()); + result.setSubtitle(video.getSubtitle()); + result.setCopyright(video.getCopyright()); + result.setLengthInSeconds(video.getLengthInSeconds()); + if(video.getThumbnailRef() != null && !video.getThumbnailRef().isEmpty()) + result.setThumbnailURL(new URL(video.getThumbnailRef())); + result.setLocale(toLocale(video.getLocale())); + for (String tag : video.getTags()) { + result.addTag(tag); + } + return result; + } + + private ImageDTO convertToImageDTO(ImageDescriptor image) { + ImageDTO result = new ImageDTO(image.getURL().toString(), image.getCreatedAtDate() != null ? image.getCreatedAtDate().asDate() : null); + result.setCopyright(image.getCopyright()); + result.setTitle(image.getTitle()); + result.setSubtitle(image.getSubtitle()); + result.setMimeType(image.getMimeType()); + result.setSizeInPx(image.getWidthInPx(), image.getHeightInPx()); + result.setLocale(toLocaleName(image.getLocale())); + List tags = new ArrayList(); + for(String tag: image.getTags()) { + tags.add(tag); + } + result.setTags(tags); + return result; + } + + private VideoDTO convertToVideoDTO(VideoDescriptor video) { + VideoDTO result = new VideoDTO(video.getURL().toString(), video.getMimeType(), + video.getCreatedAtDate() != null ? video.getCreatedAtDate().asDate() : null); + result.setCopyright(video.getCopyright()); + result.setTitle(video.getTitle()); + result.setSubtitle(video.getSubtitle()); + result.setThumbnailRef(video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null); + result.setLengthInSeconds(video.getLengthInSeconds()); + result.setLocale(toLocaleName(video.getLocale())); + List tags = new ArrayList(); + for(String tag: video.getTags()) { + tags.add(tag); + } + result.setTags(tags); + return result; } + private Locale toLocale(String localeName) { + if(localeName == null || localeName.isEmpty()) { + return null; + } + return Locale.forLanguageTag(localeName); + } + + private String toLocaleName(Locale locale) { + if(locale == null) { + return null; + } + return locale.toString(); + } + private EventDTO convertToEventDTO(Event event, boolean withStatisticalData) { EventDTO eventDTO = new EventDTO(event.getName()); copyEventBaseFieldsToDTO(event, eventDTO); @@ -4066,9 +4142,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventLeaderboardGroupUUIDs.add(lg.getId()); } updateEvent(newEvent.id, newEvent.getName(), description, newEvent.startDate, newEvent.endDate, newEvent.venue, - newEvent.isPublic, eventLeaderboardGroupUUIDs, newEvent.getLogoImageURL(), - newEvent.getOfficialWebsiteURL(), newEvent.getImageURLs(), newEvent.getVideoURLs(), - newEvent.getSponsorImageURLs()); + newEvent.isPublic, eventLeaderboardGroupUUIDs, newEvent.getOfficialWebsiteURL(), + newEvent.getImages(), newEvent.getVideos()); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/dispatch/DispatchRPCImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/dispatch/DispatchRPCImpl.java index 3514b566ec4..f5b63215fe3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/dispatch/DispatchRPCImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/dispatch/DispatchRPCImpl.java @@ -12,6 +12,7 @@ import com.sap.sailing.gwt.ui.shared.dispatch.RequestWrapper; import com.sap.sailing.gwt.ui.shared.dispatch.Result; import com.sap.sailing.gwt.ui.shared.dispatch.ResultWrapper; import com.sap.sailing.gwt.ui.shared.dispatch.rpc.DispatchRPC; +import com.sap.sailing.news.EventNewsService; import com.sap.sailing.server.RacingEventService; import com.sap.sse.util.ServiceTrackerFactory; @@ -19,16 +20,18 @@ public class DispatchRPCImpl extends ProxiedRemoteServiceServlet implements Disp private static final long serialVersionUID = -245230476512348999L; private final ServiceTracker racingEventServiceTracker; + private final ServiceTracker eventNewsServiceTracker; public DispatchRPCImpl() { final BundleContext context = Activator.getDefault(); racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + eventNewsServiceTracker = ServiceTrackerFactory.createAndOpen(context, EventNewsService.class); } @Override public > ResultWrapper execute(RequestWrapper request) throws DispatchException { - R executionResult = request.getAction().execute(new DispatchContextImpl(request.getCurrentClientTime(), racingEventServiceTracker.getService(), request.getClientLocaleName())); + R executionResult = request.getAction().execute(new DispatchContextImpl(request.getCurrentClientTime(), racingEventServiceTracker.getService(), eventNewsServiceTracker.getService(), request.getClientLocaleName())); return new ResultWrapper(executionResult); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java index cd88ab3025c..940c015ac8d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java @@ -10,6 +10,9 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sailing.domain.common.dto.NamedDTO; import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.gwt.client.media.ImageDTO; +import com.sap.sse.gwt.client.media.VideoDTO; public class EventBaseDTO extends NamedDTO implements IsSerializable { private static final long serialVersionUID = 818666323178097939L; @@ -22,14 +25,9 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { private String description; private List leaderboardGroups; - private List imageURLs = new ArrayList<>(); - private List videoURLs = new ArrayList<>(); - private List sponsorImageURLs = new ArrayList<>(); - private String logoImageURL; private String officialWebsiteURL; - /** placeholder for social media URL's -> attributes will be implemented later on */ - private String facebookURL; - private String twitterURL; + private List images = new ArrayList<>(); + private List videos = new ArrayList<>(); /** * For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it @@ -65,6 +63,17 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { this.imageSizes = new HashMap(); } + public ImageDTO getLogoImage() { + ImageDTO result = null; + for (ImageDTO image : images) { + if (image.hasTag(MediaTagConstants.LOGO)) { + result = image; + break; + } + } + return result; + } + public boolean isRunning() { Date now = new Date(); if (startDate != null && endDate != null && (now.after(startDate) && now.before(endDate))) { @@ -81,14 +90,6 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { this.description = description; } - public String getLogoImageURL() { - return logoImageURL; - } - - public void setLogoImageURL(String logoImageURL) { - this.logoImageURL = logoImageURL; - } - public String getOfficialWebsiteURL() { return officialWebsiteURL; } @@ -110,30 +111,6 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { this.baseURL = baseURL; } - public void addImageURL(String imageURL) { - imageURLs.add(imageURL); - } - - public void addVideoURL(String videoURL) { - videoURLs.add(videoURL); - } - - public void addSponsorImageURL(String sponsorImageURL) { - sponsorImageURLs.add(sponsorImageURL); - } - - public List getImageURLs() { - return imageURLs; - } - - public List getVideoURLs() { - return videoURLs; - } - - public List getSponsorImageURLs() { - return sponsorImageURLs; - } - public Iterable getLeaderboardGroups() { return leaderboardGroups; } @@ -146,20 +123,20 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { this.isOnRemoteServer = isOnRemoteServer; } - public String getFacebookURL() { - return facebookURL; + public void addImage(ImageDTO image) { + images.add(image); } - public void setFacebookURL(String facebookURL) { - this.facebookURL = facebookURL; + public List getImages() { + return images; } - public String getTwitterURL() { - return twitterURL; + public void addVideo(VideoDTO video) { + videos.add(video); } - public void setTwitterURL(String twitterURL) { - this.twitterURL = twitterURL; + public List getVideos() { + return videos; } public void setImageSize(String imageURL, ImageSize imageSize) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContext.java index 8ea041e4819..ae000b8a3cb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContext.java @@ -3,12 +3,15 @@ package com.sap.sailing.gwt.ui.shared.dispatch; import java.util.Date; import java.util.Locale; +import com.sap.sailing.news.EventNewsService; import com.sap.sailing.server.RacingEventService; public interface DispatchContext { RacingEventService getRacingEventService(); + + EventNewsService getEventNewsService(); String getClientLocaleName(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContextImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContextImpl.java index 1813050dcaf..a99966322fa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContextImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/DispatchContextImpl.java @@ -3,18 +3,21 @@ package com.sap.sailing.gwt.ui.shared.dispatch; import java.util.Date; import java.util.Locale; +import com.sap.sailing.news.EventNewsService; import com.sap.sailing.server.RacingEventService; public class DispatchContextImpl implements DispatchContext { private final RacingEventService racingEventService; + private final EventNewsService eventNewsService; private final Date currentClientTime; // private final Date currentServerTime = new Date(); private String clientLocaleName; - public DispatchContextImpl(Date currentClientTime, RacingEventService racingEventService, String clientLocaleName) { + public DispatchContextImpl(Date currentClientTime, RacingEventService racingEventService, EventNewsService eventNewsService, String clientLocaleName) { this.currentClientTime = currentClientTime; this.racingEventService = racingEventService; + this.eventNewsService = eventNewsService; this.clientLocaleName = clientLocaleName; } @@ -23,6 +26,10 @@ public class DispatchContextImpl implements DispatchContext { return racingEventService; } + public EventNewsService getEventNewsService() { + return eventNewsService; + } + @Override public Date getCurrentClientTime() { return currentClientTime; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/ListResult.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/ListResult.java new file mode 100644 index 00000000000..467f6d2235e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/ListResult.java @@ -0,0 +1,21 @@ +package com.sap.sailing.gwt.ui.shared.dispatch; + +import java.util.ArrayList; +import java.util.List; + +public class ListResult implements DTO, Result { + private ArrayList values = new ArrayList<>(); + + @SuppressWarnings("unused") + private ListResult() { + } + + public ListResult(List values) { + super(); + this.values.addAll(values); + } + + public List getValues() { + return values; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/EventOverviewStageDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/EventOverviewStageDTO.java index 0e7af6506f5..6500765e5f2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/EventOverviewStageDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/EventOverviewStageDTO.java @@ -7,8 +7,6 @@ public class EventOverviewStageDTO implements DTO { private String eventMessage; private EventOverviewStageContentDTO stageContent; - // TODO news - @SuppressWarnings("unused") private EventOverviewStageDTO() { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewNewsAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewNewsAction.java new file mode 100644 index 00000000000..c36144046bb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewNewsAction.java @@ -0,0 +1,53 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.event; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.gwt.ui.shared.dispatch.Action; +import com.sap.sailing.gwt.ui.shared.dispatch.DispatchContext; +import com.sap.sailing.gwt.ui.shared.dispatch.ListResult; +import com.sap.sailing.gwt.ui.shared.dispatch.ResultWithTTL; +import com.sap.sailing.gwt.ui.shared.dispatch.news.InfoNewsEntryDTO; +import com.sap.sailing.gwt.ui.shared.dispatch.news.LeaderboardNewsEntryDTO; +import com.sap.sailing.gwt.ui.shared.dispatch.news.NewsEntryDTO; +import com.sap.sailing.news.EventNewsItem; +import com.sap.sailing.news.impl.InfoEventNewsItem; +import com.sap.sailing.news.impl.LeaderboardUpdateNewsItem; + +public class GetEventOverviewNewsAction implements Action>> { + private UUID eventId; + + public GetEventOverviewNewsAction() { + } + + public GetEventOverviewNewsAction(UUID eventId) { + this.eventId = eventId; + } + + @Override + @GwtIncompatible + public ResultWithTTL> execute(DispatchContext context) { + Event event = context.getRacingEventService().getEvent(eventId); + long ttl = 1000 * 60 * 2; + return new ResultWithTTL<>(ttl, new ListResult(getNews(context, event))); + } + + @GwtIncompatible + private List getNews(DispatchContext dispatchContext, Event event) { + List newsItems = dispatchContext.getEventNewsService().getNews(event); + + List news = new ArrayList<>(newsItems.size()); + for(EventNewsItem newsItem: newsItems) { + if(newsItem instanceof InfoEventNewsItem) { + news.add(new InfoNewsEntryDTO((InfoEventNewsItem) newsItem, dispatchContext.getClientLocale())); + } + if(newsItem instanceof LeaderboardUpdateNewsItem) { + news.add(new LeaderboardNewsEntryDTO((LeaderboardUpdateNewsItem) newsItem)); + } + } + return news; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewStageAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewStageAction.java index d1009001f1d..77dd332870d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewStageAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventOverviewStageAction.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.shared.dispatch.event; -import java.net.URL; +import java.util.Arrays; +import java.util.Collection; import java.util.UUID; import com.google.gwt.core.shared.GwtIncompatible; @@ -10,12 +11,14 @@ import com.sap.sailing.gwt.ui.shared.dispatch.Action; import com.sap.sailing.gwt.ui.shared.dispatch.DispatchContext; import com.sap.sailing.gwt.ui.shared.dispatch.ResultWithTTL; import com.sap.sailing.gwt.ui.shared.general.EventState; -import com.sap.sailing.gwt.ui.shared.media.MediaUtils; -import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.common.media.VideoDescriptor; public class GetEventOverviewStageAction implements Action> { + private static final Collection rankedTags = Arrays.asList(MediaTagConstants.LIVESTREAM, MediaTagConstants.FEATURED, MediaTagConstants.HIGHLIGHT); + private static final Collection rankedTagsFinished = Arrays.asList(MediaTagConstants.FEATURED, MediaTagConstants.HIGHLIGHT); + private UUID eventId; public GetEventOverviewStageAction() { @@ -40,24 +43,21 @@ public class GetEventOverviewStageAction implements Action(ttl, new EventOverviewStageDTO(null, getStageContent(context, event, state, now))); + EventOverviewStageDTO stage = new EventOverviewStageDTO(null, getStageContent(context, event, state, now)); + return new ResultWithTTL<>(ttl, stage); } @GwtIncompatible public EventOverviewStageContentDTO getStageContent(DispatchContext context, Event event, EventState state, MillisecondsTimePoint now) { // Simple solution: - // P1: Show the last video if available + // P1: Show the best matching video if available // P2: Show Countdown for upcoming events // P3: Show Stage image without Countdown - Iterable videoURLs = event.getVideoURLs(); - for (int i = Util.size(videoURLs) - 1; i >= 0; i--) { - // TODO fmittag/pgt: implement locale specific live stream using context.getClientLocaleName() or context.getClientLocale() - String videoUrl = Util.get(videoURLs, i).toString(); - MimeType type = MediaUtils.detectMimeTypeFromUrl(videoUrl); - if (type == MimeType.youtube || type == MimeType.vimeo || type == MimeType.mp4) { - return new EventOverviewVideoStageDTO(EventOverviewVideoStageDTO.Type.MEDIA, type, videoUrl); - } + Collection tags = state == EventState.FINISHED ? rankedTagsFinished : rankedTags; + VideoDescriptor stageVideo = HomeServiceUtil.getStageVideo(event, context.getClientLocale(), tags, true); + if(stageVideo != null) { + return new EventOverviewVideoStageDTO(EventOverviewVideoStageDTO.Type.MEDIA, stageVideo.getMimeType(), stageVideo.getURL().toString()); } String stageImageUrl = HomeServiceUtil.getStageImageURLAsString(event); if(state == EventState.UPCOMING || state == EventState.PLANNED) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventViewAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventViewAction.java new file mode 100644 index 00000000000..aca16fc0cb7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/GetEventViewAction.java @@ -0,0 +1,132 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.event; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.gwt.server.HomeServiceUtil; +import com.sap.sailing.gwt.ui.shared.dispatch.Action; +import com.sap.sailing.gwt.ui.shared.dispatch.DispatchContext; +import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO; +import com.sap.sailing.gwt.ui.shared.eventview.EventViewDTO.EventType; +import com.sap.sailing.gwt.ui.shared.eventview.RegattaMetadataDTO; +import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO; +import com.sap.sailing.gwt.ui.shared.general.EventState; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.MediaTagConstants; + +public class GetEventViewAction implements Action { + private UUID eventId; + + public GetEventViewAction() { + } + + public GetEventViewAction(UUID eventId) { + this.eventId = eventId; + } + + @GwtIncompatible + public EventViewDTO execute(DispatchContext context) { + Event event = context.getRacingEventService().getEvent(eventId); + if (event == null) { + throw new RuntimeException("Event not found"); + } + + EventViewDTO dto = new EventViewDTO(); + HomeServiceUtil.mapToMetadataDTO(event, dto, context.getRacingEventService()); + + ImageDescriptor logoImage = event.findImageWithTag(MediaTagConstants.LOGO); + dto.setLogoImage(logoImage != null ? HomeServiceUtil.convertToImageDTO(logoImage) : null); + dto.setOfficialWebsiteURL(event.getOfficialWebsiteURL() == null ? null : event.getOfficialWebsiteURL().toString()); + + dto.setHasMedia(HomeServiceUtil.hasMedia(event)); + dto.setState(HomeServiceUtil.calculateEventState(event)); + dto.setHasAnalytics(EventState.RUNNING.compareTo(dto.getState()) <= 0); + + boolean isFakeSeries = HomeServiceUtil.isFakeSeries(event); + + for (LeaderboardGroup leaderboardGroup : event.getLeaderboardGroups()) { + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { + if(leaderboard instanceof RegattaLeaderboard) { + Regatta regatta = context.getRacingEventService().getRegattaByName(leaderboard.getName()); + if(isFakeSeries && !HomeServiceUtil.isPartOfEvent(event, regatta)) { + continue; + } + + RegattaMetadataDTO regattaDTO = createRegattaMetadataDTO(leaderboardGroup, leaderboard); + regattaDTO.setStartDate(regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null); + regattaDTO.setEndDate(regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null); + regattaDTO.setState(HomeServiceUtil.calculateRegattaState(regattaDTO)); + dto.getRegattas().add(regattaDTO); + + } else if(leaderboard instanceof FlexibleLeaderboard) { + RegattaMetadataDTO regattaDTO = createRegattaMetadataDTO(leaderboardGroup, leaderboard); + + regattaDTO.setStartDate(null); + regattaDTO.setEndDate(null); + regattaDTO.setState(HomeServiceUtil.calculateRegattaState(regattaDTO)); + dto.getRegattas().add(regattaDTO); + } + } + } + + if (isFakeSeries) { + dto.setType(EventType.SERIES_EVENT); + + LeaderboardGroup overallLeaderboardGroup = event.getLeaderboardGroups().iterator().next(); + dto.setSeriesName(HomeServiceUtil.getLeaderboardDisplayName(overallLeaderboardGroup)); + List fakeSeriesEvents = new ArrayList(); + + for (Event eventOfSeries : context.getRacingEventService().getAllEvents()) { + for (LeaderboardGroup leaderboardGroup : eventOfSeries.getLeaderboardGroups()) { + if (overallLeaderboardGroup.equals(leaderboardGroup)) { + fakeSeriesEvents.add(eventOfSeries); + } + } + } + Collections.sort(fakeSeriesEvents, new Comparator() { + public int compare(Event e1, Event e2) { + return e1.getStartDate().compareTo(e2.getEndDate()); + } + }); + for(Event eventInSeries: fakeSeriesEvents) { + String displayName = HomeServiceUtil.getLocation(eventInSeries, context.getRacingEventService()); + if(displayName == null) { + displayName = eventInSeries.getName(); + } + dto.getEventsOfSeries().add(new EventReferenceDTO(eventInSeries.getId(), displayName)); + } + } else { + dto.setType(dto.getRegattas().size() == 1 ? EventType.SINGLE_REGATTA: EventType.MULTI_REGATTA); + } + + // Special solution for Kieler Woche 2015 + if("a9d6c5d5-cac3-47f2-9b5c-506e441819a1".equals(event.getId().toString())) { + dto.setSailorsInfoURL(Locale.GERMAN.equals(context.getClientLocale()) ? "http://sailorsinfo.kieler-woche.de/" : "http://sailorsinfo.kieler-woche.de/en"); + } + + return dto; + } + + @GwtIncompatible + private RegattaMetadataDTO createRegattaMetadataDTO(LeaderboardGroup leaderboardGroup, Leaderboard leaderboard) { + RegattaMetadataDTO regattaDTO = new RegattaMetadataDTO(leaderboard.getName(), leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : leaderboard.getName()); + regattaDTO.setBoatCategory(leaderboardGroup.getDisplayName() != null ? leaderboardGroup.getDisplayName() : leaderboardGroup.getName()); + regattaDTO.setCompetitorsCount(HomeServiceUtil.calculateCompetitorsCount(leaderboard)); + regattaDTO.setRaceCount(HomeServiceUtil.calculateRaceCount(leaderboard)); + regattaDTO.setTrackedRacesCount(HomeServiceUtil.calculateTrackedRaceCount(leaderboard)); + regattaDTO.setBoatClass(HomeServiceUtil.calculateBoatClass(leaderboard)); + + return regattaDTO; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/LiveRaceCalculator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/LiveRaceCalculator.java index 0c80b501c19..ca29f22a07b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/LiveRaceCalculator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/LiveRaceCalculator.java @@ -23,7 +23,7 @@ final class LiveRaceCalculator implements RaceCallback { } else if(race.getViewState() == RaceViewState.SCHEDULED) { int timeTillRace = (int) MillisecondsTimePoint.now().until(new MillisecondsTimePoint(race.getStart())).asMillis(); ttl = Math.min(ttl, Math.min(1000 * 60, timeTillRace)); - } else if(race.getViewState() == RaceViewState.POSTPONED || race.getViewState() == RaceViewState.CANCELED) { + } else if(race.getViewState() == RaceViewState.POSTPONED || race.getViewState() == RaceViewState.ABANDONED) { ttl = Math.min(ttl, 1000 * 60); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java index 25ae2f4c1fe..71fd6f51aa0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/event/RaceContext.java @@ -126,7 +126,7 @@ public class RaceContext { private FlagStateDTO getFlagStateOrNull() { // Code extracted from SailingServiceImpl.createRaceInfoDTO - // TODO extract to to util to be used from both places + // TODO: extract to to util to be used from both places TimePoint startTime = state.getStartTime(); Flags lastUpperFlag = null; @@ -150,30 +150,34 @@ public class RaceContext { } } - RaceLogRaceStatus lastStatus = state.getStatus(); - - AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); - - RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); - if (abortingFlagEvent != null) { - if (lastStatus == RaceLogRaceStatus.UNSCHEDULED) { - lastUpperFlag = abortingFlagEvent.getUpperFlag(); - lastLowerFlag = abortingFlagEvent.getLowerFlag(); - lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed(); - lastFlagsDisplayedStateChanged = true; - } - } - - - if (lastStatus == RaceLogRaceStatus.FINISHED) { - TimePoint protestStartTime = state.getProtestTime(); - if (protestStartTime != null) { - lastUpperFlag = Flags.BRAVO; - lastLowerFlag = Flags.NONE; - lastFlagsAreDisplayed = true; - lastFlagsDisplayedStateChanged = true; - } - } + switch(state.getStatus()) { + case FINISHED: + TimePoint protestStartTime = state.getProtestTime(); + if (protestStartTime != null) { + lastUpperFlag = Flags.BRAVO; + lastLowerFlag = Flags.NONE; + lastFlagsAreDisplayed = true; + lastFlagsDisplayedStateChanged = true; + } + break; + case UNSCHEDULED: + // search for race aborting in last pass + AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); + RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); + if (abortingFlagEvent != null) { + lastUpperFlag = abortingFlagEvent.getUpperFlag(); + lastLowerFlag = abortingFlagEvent.getLowerFlag(); + lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed(); + lastFlagsDisplayedStateChanged = true; + } + break; + case FINISHING: + case RUNNING: + case SCHEDULED: + case STARTPHASE: + case UNKNOWN: + break; + }; if(lastUpperFlag != null || lastLowerFlag != null) { return new FlagStateDTO(lastUpperFlag, lastLowerFlag, lastFlagsAreDisplayed, lastFlagsDisplayedStateChanged); @@ -279,7 +283,7 @@ public class RaceContext { if(isLiveOrOfPublicInterest(startTime, finishTime)) { // the start time is always given for live races LiveRaceDTO liveRaceDTO = new LiveRaceDTO(getRegattaName(), raceColumn.getName()); - liveRaceDTO.setViewState(getRaceViewState(startTime, finishTime)); + liveRaceDTO.setViewState(getLiveRaceViewState(startTime, finishTime)); liveRaceDTO.setRegattaDisplayName(getRegattaDisplayName()); liveRaceDTO.setTrackedRaceName(trackedRace != null ? trackedRace.getRaceIdentifier().getRaceName() : null); liveRaceDTO.setTrackingState(getRaceTrackingState()); @@ -337,14 +341,25 @@ public class RaceContext { return trackingState; } - private RaceViewState getRaceViewState(TimePoint startTime, TimePoint finishTime) { + private RaceViewState getLiveRaceViewState(TimePoint startTime, TimePoint finishTime) { RaceViewState raceState = RaceViewState.RUNNING; if (startTime != null && now.before(startTime)) { raceState = RaceViewState.SCHEDULED; } else if (finishTime != null && now.after(finishTime)) { raceState = RaceViewState.FINISHED; - } else { - // TODO: Resolve ABORTED and POSTPONED states + } else if(raceLog != null) { + AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); + RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); + if (abortingFlagEvent != null) { + Flags upperFlag = abortingFlagEvent.getUpperFlag(); + if(upperFlag.equals(Flags.AP)) { + raceState = RaceViewState.POSTPONED; + } else if(upperFlag.equals(Flags.NOVEMBER)) { + raceState = RaceViewState.ABANDONED; + } else if(upperFlag.equals(Flags.FIRSTSUBSTITUTE)) { + raceState = RaceViewState.ABANDONED; + } + } } return raceState; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/AbstractRaceNewsEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/AbstractRaceNewsEntryDTO.java new file mode 100644 index 00000000000..0c82cecc687 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/AbstractRaceNewsEntryDTO.java @@ -0,0 +1,44 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.news; + +import java.util.Date; + +import com.google.gwt.core.shared.GwtIncompatible; + +public abstract class AbstractRaceNewsEntryDTO extends NewsEntryDTO { + + private String regattaName; + private String trackedRaceName; + private String boatClass; + + protected AbstractRaceNewsEntryDTO() { + } + + @GwtIncompatible + public AbstractRaceNewsEntryDTO(String regattaName, String trackedRaceName, String raceTitle, String boatClass, Date timestamp) { + super(raceTitle, timestamp, null); + this.regattaName = regattaName; + this.trackedRaceName = trackedRaceName; + this.boatClass = boatClass; + } + + @Override + public String getBoatClass() { + return boatClass; + } + + public String getRegattaName() { + return regattaName; + } + + public void setRegattaName(String regattaName) { + this.regattaName = regattaName; + } + + public String getTrackedRaceName() { + return trackedRaceName; + } + + public void setTrackedRaceName(String trackedRaceName) { + this.trackedRaceName = trackedRaceName; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/InfoNewsEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/InfoNewsEntryDTO.java new file mode 100644 index 00000000000..b11c8085268 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/InfoNewsEntryDTO.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.news; + +import java.util.Locale; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.news.impl.InfoEventNewsItem; + +public class InfoNewsEntryDTO extends NewsEntryDTO { + + private String subtitle; + + @SuppressWarnings("unused") + private InfoNewsEntryDTO() { + } + + @GwtIncompatible + public InfoNewsEntryDTO(InfoEventNewsItem item, Locale locale) { + super(item.getTitle(locale), item.getCreatedAtDate(), item.getRelatedItemLink()); + this.subtitle = item.getMessage(locale); + } + + @Override + public String getMessage() { + return subtitle; + } + + @Override + public String getBoatClass() { + return null; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/LeaderboardNewsEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/LeaderboardNewsEntryDTO.java new file mode 100644 index 00000000000..03ed23fafc1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/LeaderboardNewsEntryDTO.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.news; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.news.impl.LeaderboardUpdateNewsItem; + +public class LeaderboardNewsEntryDTO extends NewsEntryDTO { + + public enum Type { + RESULTS_UPDATE + } + + private String leaderboardName; + private String boatClassName; + private Type type; + + @SuppressWarnings("unused") + private LeaderboardNewsEntryDTO() { + } + + @GwtIncompatible + public LeaderboardNewsEntryDTO(LeaderboardUpdateNewsItem item) { + super(item.getTitle(), item.getCreatedAtDate(), null); + this.boatClassName = item.getBoatClass(); + this.type = Type.RESULTS_UPDATE; + this.leaderboardName = item.getLeaderboardName(); + } + + @Override + public String getBoatClass() { + return boatClassName; + } + + @Override + public String getMessage() { + switch (type) { + case RESULTS_UPDATE: + return StringMessages.INSTANCE.resultsUpdate(); + } + return ""; + } + + public String getLeaderboardName() { + return leaderboardName; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/NewsEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/NewsEntryDTO.java new file mode 100644 index 00000000000..314c07eb4fb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/NewsEntryDTO.java @@ -0,0 +1,47 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.news; + +import java.net.URL; +import java.util.Date; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.gwt.ui.shared.dispatch.DTO; + +public abstract class NewsEntryDTO implements DTO { + + private Date timestamp; + + private String title; + + private String externalURL; + + protected NewsEntryDTO() { + } + + @GwtIncompatible + public NewsEntryDTO(String title, Date timestamp, URL externalURL) { + this(title, timestamp, externalURL == null ? null : externalURL.toString()); + } + + @GwtIncompatible + private NewsEntryDTO(String title, Date timestamp, String externalURL) { + this.title = title; + this.timestamp = timestamp; + this.externalURL = externalURL; + } + + public String getTitle() { + return title; + } + + public abstract String getMessage(); + + public abstract String getBoatClass(); + + public Date getTimestamp() { + return timestamp; + } + + public String getExternalURL() { + return externalURL; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/RaceCompetitorNewsEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/RaceCompetitorNewsEntryDTO.java new file mode 100644 index 00000000000..77d26641128 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/dispatch/news/RaceCompetitorNewsEntryDTO.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.shared.dispatch.news; + +import java.util.Date; + +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class RaceCompetitorNewsEntryDTO extends AbstractRaceNewsEntryDTO { + + public enum Type { + WINNER + } + + private String competitorName; + private Type type; + + @SuppressWarnings("unused") + private RaceCompetitorNewsEntryDTO() { + } + + public RaceCompetitorNewsEntryDTO(String regattaName, String trackedRaceName, String raceTitle, String boatClass, Date timestamp, String competitorName, Type type) { + super(regattaName, trackedRaceName, raceTitle, boatClass, timestamp); + this.competitorName = competitorName; + this.type = type; + } + + @Override + public String getMessage() { + switch (type) { + case WINNER: + return StringMessages.INSTANCE.competitorWonRace(competitorName); + } + return ""; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/eventview/EventViewDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/eventview/EventViewDTO.java index af436fc3559..78279dc1f0c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/eventview/EventViewDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/eventview/EventViewDTO.java @@ -3,10 +3,12 @@ package com.sap.sailing.gwt.ui.shared.eventview; import java.util.ArrayList; import java.util.List; +import com.sap.sailing.gwt.ui.shared.dispatch.Result; import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO; import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO; +import com.sap.sse.gwt.client.media.ImageDTO; -public class EventViewDTO extends EventMetadataDTO { +public class EventViewDTO extends EventMetadataDTO implements Result { public enum EventType { SINGLE_REGATTA, MULTI_REGATTA, SERIES_EVENT @@ -19,7 +21,7 @@ public class EventViewDTO extends EventMetadataDTO { private boolean hasMedia; private boolean hasAnalytics; private String seriesName; - private String logoImageURL; + private ImageDTO logoImage; private String officialWebsiteURL; private String sailorsInfoURL; @@ -87,12 +89,12 @@ public class EventViewDTO extends EventMetadataDTO { return false; } - public void setLogoImageURL(String logoImageURL) { - this.logoImageURL = logoImageURL; + public void setLogoImage(ImageDTO logoImage) { + this.logoImage = logoImage; } - public String getLogoImageURL() { - return logoImageURL; + public ImageDTO getLogoImage() { + return logoImage; } public void setOfficialWebsiteURL(String officialWebsiteURL) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/fakeseries/EventSeriesViewDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/fakeseries/EventSeriesViewDTO.java index eeb8a90b13c..b4166c35045 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/fakeseries/EventSeriesViewDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/fakeseries/EventSeriesViewDTO.java @@ -7,12 +7,13 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sailing.gwt.ui.shared.general.EventMetadataDTO; import com.sap.sailing.gwt.ui.shared.general.LabelType; +import com.sap.sse.gwt.client.media.ImageDTO; public class EventSeriesViewDTO implements IsSerializable { private UUID id; private String displayName; private ArrayList events = new ArrayList<>(); - private String logoImageURL; + private ImageDTO logoImage; private boolean hasMedia; private boolean hasAnalytics; @@ -61,12 +62,12 @@ public class EventSeriesViewDTO implements IsSerializable { this.events.add(event); } - public String getLogoImageURL() { - return logoImageURL; + public ImageDTO getLogoImage() { + return logoImage; } - public void setLogoImageURL(String logoImageURL) { - this.logoImageURL = logoImageURL; + public void setLogoImage(ImageDTO logoImage) { + this.logoImage = logoImage; } public boolean isHasMedia() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaConstants.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaConstants.java new file mode 100644 index 00000000000..402fb961100 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaConstants.java @@ -0,0 +1,21 @@ +package com.sap.sailing.gwt.ui.shared.media; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.common.media.MimeType; + +public final class MediaConstants { + + public static final List imageTagSuggestions = Arrays.asList(new String[] { MediaTagConstants.STAGE, MediaTagConstants.TEASER, + MediaTagConstants.SPONSOR, MediaTagConstants.LOGO, MediaTagConstants.GALLERY }); + public static final List videoTagSuggestions = Arrays.asList(new String[] { MediaTagConstants.LIVESTREAM, MediaTagConstants.HIGHLIGHT,MediaTagConstants.FEATURED }); + public static final Set SUPPORTED_VIDEO_TYPES = new HashSet<>(Arrays.asList(MimeType.youtube, MimeType.vimeo, MimeType.mp4)); + + private MediaConstants() { + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaUtils.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaUtils.java deleted file mode 100644 index e61f0c0eb93..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.sap.sailing.gwt.ui.shared.media; - -import java.util.regex.Pattern; - -import com.google.gwt.core.shared.GwtIncompatible; -import com.sap.sse.common.media.MimeType; - -/** - * Temporary solution for mimetype detection for current videos. - * - * @author pgtaboada - * - */ -public class MediaUtils { - - /** - * Youtube regex detection from: - * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 - * at 15:33 - */ - @GwtIncompatible - private static final Pattern YOUTUBE_ID_REGEX = Pattern - .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$"); - - @GwtIncompatible - private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*"); - - @GwtIncompatible - private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$"); - - /** - * Detect mimetype for given url. - * - * @param url - * the source pointing to the video mediafile - * @return mimetype detected or MimeType.unknown - */ - @GwtIncompatible - public static MimeType detectMimeTypeFromUrl(String url) { - - if (YOUTUBE_ID_REGEX.matcher(url).matches()) { - return MimeType.youtube; - } else if (VIMEO_REGEX.matcher(url).matches()) { - return MimeType.vimeo; - } else if (MP4_REGEX.matcher(url).matches()) { - return MimeType.mp4; - } else { - return MimeType.unknown; - } - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingImageDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingImageDTO.java index 02da494bb86..cd501f548cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingImageDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingImageDTO.java @@ -3,23 +3,19 @@ package com.sap.sailing.gwt.ui.shared.media; import java.util.Date; import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO; -import com.sap.sse.common.media.ImageMetadataDTO; -import com.sap.sse.common.media.ImageSize; +import com.sap.sse.gwt.client.media.ImageDTO; -public class SailingImageDTO extends ImageMetadataDTO { +public class SailingImageDTO extends ImageDTO { - private static final long serialVersionUID = 1L; - private EventReferenceDTO eventRef; - @SuppressWarnings("unused") - private SailingImageDTO() { + protected SailingImageDTO() { + super(); } - public SailingImageDTO(EventReferenceDTO eventRef, String imageURL, ImageSize size, Date createdAtDate) { - super(imageURL, size, createdAtDate); + public SailingImageDTO(EventReferenceDTO eventRef, String imageRef, Date createdAtDate) { + super(imageRef, createdAtDate); this.eventRef = eventRef; - } public EventReferenceDTO getEventRef() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingVideoDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingVideoDTO.java index bbfc2ebe186..d4270438627 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingVideoDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/SailingVideoDTO.java @@ -4,16 +4,14 @@ import java.util.Date; import com.sap.sailing.gwt.ui.shared.general.EventReferenceDTO; import com.sap.sse.common.media.MimeType; -import com.sap.sse.common.media.VideoMetadataDTO; +import com.sap.sse.gwt.client.media.VideoDTO; -public class SailingVideoDTO extends VideoMetadataDTO { +public class SailingVideoDTO extends VideoDTO { - private static final long serialVersionUID = 1L; - private EventReferenceDTO eventRef; - @SuppressWarnings("unused") - private SailingVideoDTO() { + /** for GWT */ + protected SailingVideoDTO() { } public SailingVideoDTO(EventReferenceDTO eventRef, String srcReference, MimeType mimeType, Date createdAtDate) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/race/RaceMetadataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/race/RaceMetadataDTO.java index d168b244d0d..41c49440dc7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/race/RaceMetadataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/race/RaceMetadataDTO.java @@ -38,7 +38,7 @@ public class RaceMetadataDTO implements IsSerializable { return StringMessages.INSTANCE.raceIsPostponed(); } }, - CANCELED { // the running racing has been canceled + ABANDONED { // the running racing has been abandoned @Override public String getLabel() { return StringMessages.INSTANCE.raceIsCanceled(); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 0149848272f..1b3b874b812 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -18,6 +18,7 @@ import java.util.Calendar; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.UUID; import java.util.logging.Logger; @@ -89,6 +90,12 @@ import com.sap.sse.common.Color; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest { private static final Logger logger = Logger.getLogger(TestStoringAndLoadingEventsAndRegattas.class.getName()); @@ -137,14 +144,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest event.addLeaderboardGroup(lg1); event.addLeaderboardGroup(lg2); event.setDescription(eventDescription); - event.addImageURL(new URL("http://some.host/with/some/file1.jpg")); - event.addImageURL(new URL("http://some.host/with/some/file2.jpg")); - event.addVideoURL(new URL("http://some.host/with/some/file1.mp4")); - event.addVideoURL(new URL("http://some.host/with/some/file2.mp4")); - event.addSponsorImageURL(new URL("http://some.host/with/some/file4.mp4")); - event.addSponsorImageURL(new URL("http://some.host/with/some/file5.mp4")); event.setOfficialWebsiteURL(new URL("http://official.website.com")); - event.setLogoImageURL(new URL("http://official.logo.com")); mof.storeEvent(event); DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); @@ -169,7 +169,6 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest assertEquals(eventName, loadedEvent.getName()); assertEquals(eventDescription, loadedEvent.getDescription()); assertEquals(event.getOfficialWebsiteURL(), loadedEvent.getOfficialWebsiteURL()); - assertEquals(event.getLogoImageURL(), loadedEvent.getLogoImageURL()); assertEquals(2, Util.size(loadedEvent.getLeaderboardGroups())); Iterator lgIter = loadedEvent.getLeaderboardGroups().iterator(); assertSame(lg1, lgIter.next()); @@ -182,9 +181,6 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest for (CourseArea loadedCourseArea : loadedVenue.getCourseAreas()) { assertEquals(courseAreaNames[i++], loadedCourseArea.getName()); } - assertTrue("image URLs "+loadedEvent.getImageURLs()+" but expected "+event.getImageURLs(), Util.equals(event.getImageURLs(), loadedEvent.getImageURLs())); - assertTrue("video URLs "+loadedEvent.getVideoURLs()+" but expected "+event.getVideoURLs(), Util.equals(event.getVideoURLs(), loadedEvent.getVideoURLs())); - assertTrue("sponsor image URLs "+loadedEvent.getSponsorImageURLs()+" but expected "+event.getSponsorImageURLs(), Util.equals(event.getSponsorImageURLs(), loadedEvent.getSponsorImageURLs())); } @Test @@ -218,6 +214,155 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest assertEquals(loadedRegatta.getEndDate(), regattaEndDate); } + @Test + public void testLoadStoreSimpleEventWithImages() throws MalformedURLException { + final URL imageURL = new URL("http://some.host/with/some/file2.jpg"); + final String copyright = "copyright by Alex"; + final String imageTitle = "My image title"; + final String imageSubtitle = "My image subtitle"; + final Integer imageWidth = 500; + final Integer imageHeight = 300; + final TimePoint createdAt = MillisecondsTimePoint.now(); + final String eventName = "Event Name"; + final String venueName = "Venue Name"; + final String courseAreaName = "Alpha"; + final Venue venue = new VenueImpl(venueName); + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); + venue.addCourseArea(courseArea); + + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); + + ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt); + image1.setCopyright(copyright); + image1.setSize(imageWidth, imageHeight); + image1.setTitle(imageTitle); + image1.setSubtitle(imageSubtitle); + image1.addTag("Tag1"); + image1.addTag("Tag2"); + image1.addTag("Tag3"); + event.addImage(image1); + + ImageDescriptor image2 = new ImageDescriptorImpl(new URL("http://some.host/with/some/file2.jpg"), MillisecondsTimePoint.now()); + image2.setCopyright("copyright"); + event.addImage(image2); + + mof.storeEvent(event); + + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + final Event loadedEvent = dof.loadEvent(eventName); + assertEquals(2, Util.size(loadedEvent.getImages())); + ImageDescriptor loadedImage1 = loadedEvent.getImages().iterator().next(); + assertNotNull(loadedImage1); + assertEquals(imageURL, loadedImage1.getURL()); + assertEquals(copyright, loadedImage1.getCopyright()); + assertEquals(copyright, loadedImage1.getCopyright()); + assertEquals(imageTitle, loadedImage1.getTitle()); + assertEquals(imageSubtitle, loadedImage1.getSubtitle()); + assertEquals(createdAt, loadedImage1.getCreatedAtDate()); + assertEquals(imageWidth, loadedImage1.getWidthInPx()); + assertEquals(imageHeight, loadedImage1.getHeightInPx()); + assertEquals(3, Util.size(loadedImage1.getTags())); + } + + @Test + public void testLoadStoreSimpleEventWithVideos() throws MalformedURLException { + final URL videoURL = new URL("http://some.host/with/some/video.mpg"); + final URL videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg"); + final Locale locale = Locale.GERMAN; + final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h + final MimeType mimeType = MimeType.mp4; + final String copyright = "copyright by Don"; + final String videoTitle = "My video title"; + final String videoSubtitle = "My video subtitle"; + final TimePoint createdAt = MillisecondsTimePoint.now(); + final String eventName = "Event Name"; + final String venueName = "Venue Name"; + final String courseAreaName = "Alpha"; + final Venue venue = new VenueImpl(venueName); + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); + venue.addCourseArea(courseArea); + + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); + + VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt); + video1.setCopyright(copyright); + video1.setTitle(videoTitle); + video1.setLocale(locale); + video1.setSubtitle(videoSubtitle); + video1.setThumbnailURL(videoThumbnailURL); + video1.setLengthInSeconds(videoLengthInSeconds); + video1.addTag("Tag1"); + video1.addTag("Tag2"); + video1.addTag("Tag3"); + event.addVideo(video1); + + VideoDescriptor video2 = new VideoDescriptorImpl(new URL("http://some.host/with/some/file2.ogg"), MimeType.ogg, MillisecondsTimePoint.now()); + video2.setCopyright("copyright"); + event.addVideo(video2); + + mof.storeEvent(event); + + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + final Event loadedEvent = dof.loadEvent(eventName); + assertEquals(2, Util.size(loadedEvent.getVideos())); + VideoDescriptor loadedVideo1 = loadedEvent.getVideos().iterator().next(); + assertNotNull(loadedVideo1); + assertEquals(videoURL, loadedVideo1.getURL()); + assertEquals(videoThumbnailURL, loadedVideo1.getThumbnailURL()); + assertEquals(videoLengthInSeconds, loadedVideo1.getLengthInSeconds()); + assertEquals(copyright, loadedVideo1.getCopyright()); + assertEquals(locale, loadedVideo1.getLocale()); + assertEquals(videoTitle, loadedVideo1.getTitle()); + assertEquals(videoSubtitle, loadedVideo1.getSubtitle()); + assertEquals(createdAt, loadedVideo1.getCreatedAtDate()); + assertEquals(3, Util.size(loadedVideo1.getTags())); + } + + @SuppressWarnings("deprecation") + @Test + /** + * We expected that the migration code creates also an image URL for each image we create. + * Images with the 'Sponsor' tag should create a corresponding sponsor image URL + * Videos should create a video URL. + */ + public void testLoadStoreSimpleEventWithImageAndVideoURLMigration() throws MalformedURLException { + final URL imageURL = new URL("http://some.host/with/some/bla.jpg"); + final URL sponsorImageURL = new URL("http://some.host/with/some/sponsor.jpg"); + final URL videoURL = new URL("http://some.host/with/some/video.mpg"); + final TimePoint createdAt = MillisecondsTimePoint.now(); + final String eventName = "Event Name"; + final Venue venue = new VenueImpl("My Venue"); + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), "Alfa"); + venue.addCourseArea(courseArea); + + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); + + ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt); + image1.addTag(MediaTagConstants.GALLERY); + event.addImage(image1); + + ImageDescriptor image2 = new ImageDescriptorImpl(sponsorImageURL, createdAt); + event.addImage(image2); + image2.addTag(MediaTagConstants.SPONSOR); + + VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, MimeType.mp4, createdAt); + event.addVideo(video1); + + mof.storeEvent(event); + + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + final Event loadedEvent = dof.loadEvent(eventName); + assertEquals(2, Util.size(loadedEvent.getImages())); + assertEquals(1, Util.size(loadedEvent.getVideos())); + assertEquals(1, Util.size(loadedEvent.getImageURLs())); + assertEquals(1, Util.size(loadedEvent.getSponsorImageURLs())); + assertEquals(1, Util.size(loadedEvent.getVideoURLs())); + } + + @Test public void testLoadStoreRegattaConfiguration() { diff --git a/java/com.sap.sailing.news/.classpath b/java/com.sap.sailing.news/.classpath new file mode 100644 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sailing.news/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.news/.gitignore b/java/com.sap.sailing.news/.gitignore new file mode 100644 index 00000000000..e660fd93d31 --- /dev/null +++ b/java/com.sap.sailing.news/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/java/com.sap.sailing.news/.project b/java/com.sap.sailing.news/.project new file mode 100644 index 00000000000..cba19cda41d --- /dev/null +++ b/java/com.sap.sailing.news/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.news + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.news/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.news/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.news/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.news/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.news/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.news/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.news/META-INF/MANIFEST.MF b/java/com.sap.sailing.news/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..25142f906b9 --- /dev/null +++ b/java/com.sap.sailing.news/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Shared sailing news APIs +Bundle-SymbolicName: com.sap.sailing.news +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: com.sap.sailing.news.Activator +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ActivationPolicy: lazy +Export-Package: com.sap.sailing.news, + com.sap.sailing.news.impl +Import-Package: org.json.simple, + org.json.simple.parser, + org.osgi.framework;version="1.5.0", + org.osgi.util.tracker;version="1.5.1" +Require-Bundle: com.sap.sse.common, + com.sap.sailing.server, + com.sap.sailing.domain, + com.sap.sailing.domain.common diff --git a/java/com.sap.sailing.news/build.properties b/java/com.sap.sailing.news/build.properties new file mode 100644 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.news/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.news/pom.xml b/java/com.sap.sailing.news/pom.xml new file mode 100644 index 00000000000..0748fb121e5 --- /dev/null +++ b/java/com.sap.sailing.news/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.news + eclipse-plugin + diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/Activator.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/Activator.java new file mode 100644 index 00000000000..01d1717848c --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/Activator.java @@ -0,0 +1,29 @@ +package com.sap.sailing.news; + +import java.util.logging.Logger; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.sap.sailing.news.impl.EventNewsProviderRegistryImpl; +import com.sap.sailing.news.impl.EventNewsServiceImpl; +import com.sap.sailing.news.impl.LeaderboardUpdateEventNewsProvider; + +public class Activator implements BundleActivator { + private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + public void start(BundleContext context) throws Exception { + EventNewsProviderRegistry providerRegistry = new EventNewsProviderRegistryImpl(); + EventNewsService newsService = new EventNewsServiceImpl(providerRegistry); + context.registerService(EventNewsService.class, newsService, null); + context.registerService(EventNewsProviderRegistry.class, providerRegistry, null); + + // The json event news provider is only a fallback and should not be activated per default + // providerRegistry.registerNewsProvider(new JsonEventNewsProvider()); + providerRegistry.registerNewsProvider(new LeaderboardUpdateEventNewsProvider()); + logger.info("EventNews Service registered."); + } + + public void stop(BundleContext bundleContext) throws Exception { + } +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsItem.java new file mode 100644 index 00000000000..86a9ac7eec3 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsItem.java @@ -0,0 +1,12 @@ +package com.sap.sailing.news; + +import java.util.UUID; + +/** + * An interface for event related news item + * @author Frank + * + */ +public interface EventNewsItem extends NewsItem { + UUID getEventUUID(); +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProvider.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProvider.java new file mode 100644 index 00000000000..b8e81be838f --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProvider.java @@ -0,0 +1,16 @@ +package com.sap.sailing.news; + +import java.util.Collection; +import java.util.Date; + +import com.sap.sailing.domain.base.Event; + +public interface EventNewsProvider { + Collection getNews(Event event); + + Collection getNews(Event event, Date startingFrom); + + boolean hasNews(Event event, Date startingFrom); + + boolean hasNews(Event event); +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProviderRegistry.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProviderRegistry.java new file mode 100644 index 00000000000..3fccf34d024 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsProviderRegistry.java @@ -0,0 +1,10 @@ +package com.sap.sailing.news; + +public interface EventNewsProviderRegistry { + boolean registerNewsProvider(EventNewsProvider provider); + + boolean deregisterNewsProvider(EventNewsProvider provider); + + Iterable getEventNewsProvider(); +} + \ No newline at end of file diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsService.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsService.java new file mode 100644 index 00000000000..c7a7218041b --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/EventNewsService.java @@ -0,0 +1,16 @@ +package com.sap.sailing.news; + +import java.util.Date; +import java.util.List; + +import com.sap.sailing.domain.base.Event; + +public interface EventNewsService { + List getNews(Event event); + + List getNews(Event event, Date startingFrom); + + List getNewsByCategory(Event event, String category); + + List getNewsByCategory(Event event, Date startingFrom, String category); +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/LimitedQueue.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/LimitedQueue.java new file mode 100644 index 00000000000..5c330de04c4 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/LimitedQueue.java @@ -0,0 +1,21 @@ +package com.sap.sailing.news; + +import java.util.LinkedList; + +public class LimitedQueue extends LinkedList { + private static final long serialVersionUID = 9026209104040359183L; + private int limit; + + public LimitedQueue(int limit) { + this.limit = limit; + } + + @Override + public boolean add(E o) { + boolean added = super.add(o); + while (added && size() > limit) { + super.remove(); + } + return added; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/NewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/NewsItem.java new file mode 100644 index 00000000000..df47e9e43fa --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/NewsItem.java @@ -0,0 +1,24 @@ +package com.sap.sailing.news; + +import java.net.URL; +import java.util.Date; +import java.util.UUID; + +/** + * An interface for a generic news item + * @author Frank + * + */ +public interface NewsItem extends Comparable { + UUID getId(); + + String getCategory(); + + String getTitle(); + + String getMessage(); + + URL getRelatedItemLink(); + + Date getCreatedAtDate(); +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractEventNewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractEventNewsItem.java new file mode 100644 index 00000000000..1dd93a2abde --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractEventNewsItem.java @@ -0,0 +1,26 @@ +package com.sap.sailing.news.impl; + +import java.net.URL; +import java.util.Date; +import java.util.UUID; + +import com.sap.sailing.news.EventNewsItem; + +public abstract class AbstractEventNewsItem extends AbstractNewsItem implements EventNewsItem { + private UUID eventId; + + public AbstractEventNewsItem(UUID eventId, String title, String message, Date createdAtDate, String category, URL relatedItemLink) { + super(title, message, createdAtDate, category, relatedItemLink); + this.eventId = eventId; + } + + public AbstractEventNewsItem(UUID eventId, String title, String message, Date createdAtDate, String category) { + super(title, message, createdAtDate, category); + this.eventId = eventId; + } + + @Override + public UUID getEventUUID() { + return eventId; + } +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractNewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractNewsItem.java new file mode 100644 index 00000000000..1528859b722 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/AbstractNewsItem.java @@ -0,0 +1,73 @@ +package com.sap.sailing.news.impl; + +import java.net.URL; +import java.util.Date; +import java.util.UUID; + +import com.sap.sailing.news.NewsItem; + +public class AbstractNewsItem implements NewsItem { + private final UUID id; + + private String category; + + private String title; + + private String message; + + private URL relatedItemLink; + + private Date createdAtDate; + + public AbstractNewsItem(String title, String message, Date createdAtDate, String category) { + this(title, message, createdAtDate, category, null); + } + + public AbstractNewsItem(String title, String message, Date createdAtDate, String category, URL relatedItemLink) { + this.title = title; + this.message = message; + this.category = category; + this.createdAtDate = createdAtDate; + this.relatedItemLink = relatedItemLink; + this.id = UUID.randomUUID(); + } + + public String getCategory() { + return category; + } + + public String getTitle() { + return title; + } + + public String getMessage() { + return message; + } + + public URL getRelatedItemLink() { + return relatedItemLink; + } + + public Date getCreatedAtDate() { + return createdAtDate; + } + + public UUID getId() { + return id; + } + + @Override + public int compareTo(NewsItem o) { + Date otherCreatedAtDate = o.getCreatedAtDate(); + if(createdAtDate == otherCreatedAtDate) { + return 0; + } + if(createdAtDate == null) { + return 1; + } + if(otherCreatedAtDate == null) { + return -1; + } + return -createdAtDate.compareTo(otherCreatedAtDate); + } +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsProviderRegistryImpl.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsProviderRegistryImpl.java new file mode 100644 index 00000000000..23d9c22520b --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsProviderRegistryImpl.java @@ -0,0 +1,31 @@ +package com.sap.sailing.news.impl; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.news.EventNewsProvider; +import com.sap.sailing.news.EventNewsProviderRegistry; + +public class EventNewsProviderRegistryImpl implements EventNewsProviderRegistry { + private Set newsProvider; + + public EventNewsProviderRegistryImpl() { + newsProvider = new HashSet(); + } + + @Override + public boolean registerNewsProvider(EventNewsProvider provider) { + return newsProvider.add(provider); + } + + @Override + public boolean deregisterNewsProvider(EventNewsProvider provider) { + return newsProvider.remove(provider); + } + + @Override + public Iterable getEventNewsProvider() { + return newsProvider; + } + +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsServiceImpl.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsServiceImpl.java new file mode 100644 index 00000000000..abf923c1bf5 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/EventNewsServiceImpl.java @@ -0,0 +1,60 @@ +package com.sap.sailing.news.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.news.EventNewsItem; +import com.sap.sailing.news.EventNewsProvider; +import com.sap.sailing.news.EventNewsProviderRegistry; +import com.sap.sailing.news.EventNewsService; + +public class EventNewsServiceImpl implements EventNewsService { + private final EventNewsProviderRegistry providerRegistry; + + public EventNewsServiceImpl(EventNewsProviderRegistry providerRegistry) { + this.providerRegistry = providerRegistry; + } + + @Override + public List getNews(Event event) { + List result = new ArrayList<>(); + for(EventNewsProvider provider: providerRegistry.getEventNewsProvider()) { + Collection news = provider.getNews(event); + result.addAll(news); + } + Collections.sort(result); + if(result.size() <= 15) { + return result; + } + return result.subList(0, 16); + } + + @Override + public List getNews(Event event, Date startingFrom) { + List result = new ArrayList<>(); + for(EventNewsProvider provider: providerRegistry.getEventNewsProvider()) { + Collection news = provider.getNews(event, startingFrom); + result.addAll(news); + } + Collections.sort(result); + if(result.size() <= 15) { + return result; + } + return result.subList(0, 16); + } + + @Override + public List getNewsByCategory(Event event, String category) { + return getNews(event); + } + + @Override + public List getNewsByCategory(Event event, Date startingFrom, String category) { + return getNews(event, startingFrom); + } + +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/InfoEventNewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/InfoEventNewsItem.java new file mode 100644 index 00000000000..afb804964a0 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/InfoEventNewsItem.java @@ -0,0 +1,34 @@ +package com.sap.sailing.news.impl; + +import java.net.URL; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class InfoEventNewsItem extends AbstractEventNewsItem { + private Map titles; + private Map messages; + + public InfoEventNewsItem(UUID eventId, String title, String message, Date createdAtDate, String category, + URL relatedItemLink, Map titles, Map messages) { + super(eventId, title, message, createdAtDate, category, relatedItemLink); + this.titles = titles; + this.messages = messages; + } + + public String getTitle(Locale locale) { + if(titles.containsKey(locale)) { + return titles.get(locale); + } + return super.getTitle(); + } + + public String getMessage(Locale locale) { + if(messages.containsKey(locale)) { + return messages.get(locale); + } + return super.getMessage(); + } + +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/JsonEventNewsProvider.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/JsonEventNewsProvider.java new file mode 100644 index 00000000000..78c4c241f9a --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/JsonEventNewsProvider.java @@ -0,0 +1,142 @@ +package com.sap.sailing.news.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.news.EventNewsItem; +import com.sap.sailing.news.EventNewsProvider; + +public class JsonEventNewsProvider implements EventNewsProvider { + private static final int UPDATE_INTERVAL = 60_000; + private static final int CONNECTION_TIMEOUT = 15_000; + + private static final Logger logger = Logger.getLogger(JsonEventNewsProvider.class.getName()); + + private static URL staticNewsURL; + + private List news = new LinkedList<>(); + private long nextUpdate = 0; + + static { + try { + staticNewsURL = new URL("http://static.sapsailing.com/events_news/news.json"); + } catch (MalformedURLException e) { + } + } + + public JsonEventNewsProvider() { + } + + private synchronized void update() { + if(nextUpdate >= System.currentTimeMillis()) { + // update already done + return; + } + nextUpdate = System.currentTimeMillis() + UPDATE_INTERVAL; + + try { + BufferedReader bufferedReader = null; + try { + logger.fine("Updating news from URL "+staticNewsURL); + URLConnection urlConnection = staticNewsURL.openConnection(); + urlConnection.setConnectTimeout(CONNECTION_TIMEOUT); + urlConnection.connect(); + bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); + JSONParser parser = new JSONParser(); + Object newsAsObject = parser.parse(bufferedReader); + JSONArray newsAsJsonArray = (JSONArray) newsAsObject; + List newNews = new LinkedList<>(); + for (Object eventAsObject : newsAsJsonArray) { + JSONObject eventAsJson = (JSONObject) eventAsObject; + String eventIdAsString = (String) eventAsJson.get("event"); + Number timestamp = (Number) eventAsJson.get("timestamp"); + String title = (String) eventAsJson.get("title"); + Map titles = readI18nText(eventAsJson, "titles"); + String message = (String) eventAsJson.get("message"); + Map messages = readI18nText(eventAsJson, "messages"); + String newsURL = (String) eventAsJson.get("url"); + UUID eventId = UUID.fromString(eventIdAsString); + InfoEventNewsItem newsItem = new InfoEventNewsItem(eventId, title, message, new Date(timestamp.longValue()), null, newsURL == null ? null : new URL(newsURL), titles, messages); + newNews.add(newsItem); + } + news = newNews; + } finally { + if (bufferedReader != null) { + bufferedReader.close(); + } + } + } catch (IOException | ParseException e) { + logger.log(Level.INFO, "Exception trying to fetch news from " + staticNewsURL, e); + } + } + + private Map readI18nText(JSONObject eventAsJson, String field) { + Object object = eventAsJson.get(field); + if(object == null) { + return Collections.emptyMap(); + } + JSONObject texts = (JSONObject) object; + Set> entrySet = texts.entrySet(); + Map result = new HashMap<>(); + for (Entry entry : entrySet) { + result.put(Locale.forLanguageTag((String) entry.getKey()), (String) entry.getValue()); + } + return result; + } + + @Override + public Collection getNews(Event event) { + if(nextUpdate < System.currentTimeMillis()) { + update(); + } + + UUID eventId = event.getId(); + List result = new LinkedList<>(); + for(InfoEventNewsItem newsEntry: news) { + if(eventId.equals(newsEntry.getEventUUID())) { + result.add(newsEntry); + } + } + + return result; + } + + @Override + public Collection getNews(Event event, Date startingFrom) { + return getNews(event); + } + + @Override + public boolean hasNews(Event event, Date startingFrom) { + return news.size() > 0; + } + + @Override + public boolean hasNews(Event event) { + return news.size() > 0; + } + +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateEventNewsProvider.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateEventNewsProvider.java new file mode 100644 index 00000000000..29f972c1313 --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateEventNewsProvider.java @@ -0,0 +1,73 @@ +package com.sap.sailing.news.impl; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.news.EventNewsItem; +import com.sap.sailing.news.EventNewsProvider; +import com.sap.sse.common.TimePoint; + +/** + * An event news provider looking at leaderboard updates triggered by changes of timePointOfLatestModification + * @author Frank + * + */ +public class LeaderboardUpdateEventNewsProvider implements EventNewsProvider { + + @Override + public Collection getNews(Event event) { + List result = new LinkedList<>(); + Iterable leaderboardGroups = event.getLeaderboardGroups(); + for(LeaderboardGroup leaderboardGroup: leaderboardGroups) { + for(Leaderboard leaderboard: leaderboardGroup.getLeaderboards()) { + TimePoint timePointOfLatestModification = leaderboard.getTimePointOfLatestModification(); + if(timePointOfLatestModification == null) { + continue; + } + + String displayName = leaderboard.getDisplayName() != null ?leaderboard.getDisplayName() :leaderboard.getName(); + String boatClassName= null; + if(leaderboard instanceof RegattaLeaderboard) { + BoatClass boatClass = ((RegattaLeaderboard) leaderboard).getRegatta().getBoatClass(); + if(boatClass != null) { + boatClassName = boatClass.getDisplayName(); + } + } + result.add(new LeaderboardUpdateNewsItem(event.getId(), timePointOfLatestModification.asDate(), leaderboard.getName(), displayName, boatClassName)); + } + } + + Collections.sort(result); + + if(result.size() <= 10) { + return result; + } + return result.subList(0, 11); + } + + @Override + public Collection getNews(Event event, Date startingFrom) { + // TODO correct implementation + return getNews(event); + } + + @Override + public boolean hasNews(Event event, Date startingFrom) { + // TODO correct implementation + return true; + } + + @Override + public boolean hasNews(Event event) { + return true; + } + +} diff --git a/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateNewsItem.java b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateNewsItem.java new file mode 100644 index 00000000000..6a64cacbfce --- /dev/null +++ b/java/com.sap.sailing.news/src/com/sap/sailing/news/impl/LeaderboardUpdateNewsItem.java @@ -0,0 +1,23 @@ +package com.sap.sailing.news.impl; + +import java.util.Date; +import java.util.UUID; + +public class LeaderboardUpdateNewsItem extends AbstractEventNewsItem { + private String leaderboardName; + private String boatClass; + + public LeaderboardUpdateNewsItem(UUID eventId, Date updateDate, String leaderboardName, String leaderboardDisplayName, String boatClass) { + super(eventId, leaderboardDisplayName, null, updateDate, null, null); + this.leaderboardName = leaderboardName; + this.boatClass = boatClass; + } + + public String getLeaderboardName() { + return leaderboardName; + } + + public String getBoatClass() { + return boatClass; + } +} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java index 9139a137641..a49790f7da3 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java @@ -3,7 +3,10 @@ package com.sap.sailing.server.gateway.deserialization.impl; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.UUID; import org.json.simple.JSONArray; @@ -17,7 +20,15 @@ import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; public class EventBaseJsonDeserializer implements JsonDeserializer { private final JsonDeserializer venueDeserializer; @@ -28,22 +39,21 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { this.leaderboardGroupDeserializer = leaderboardGroupDeserializer; } - public EventBase deserialize(JSONObject object) throws JsonDeserializationException { - UUID id = UUID.fromString((String) object.get(EventBaseJsonSerializer.FIELD_ID)); - String name = (String) object.get(EventBaseJsonSerializer.FIELD_NAME); - String description = (String) object.get(EventBaseJsonSerializer.FIELD_DESCRIPTION); - String officialWebsiteURLAsString = (String) object.get(EventBaseJsonSerializer.FIELD_OFFICIAL_WEBSITE_URL); - String logoImageURLAsString = (String) object.get(EventBaseJsonSerializer.FIELD_LOGO_IMAGE_URL); - Number startDate = (Number) object.get(EventBaseJsonSerializer.FIELD_START_DATE); - Number endDate = (Number) object.get(EventBaseJsonSerializer.FIELD_END_DATE); + public EventBase deserialize(JSONObject eventJson) throws JsonDeserializationException { + UUID id = UUID.fromString((String) eventJson.get(EventBaseJsonSerializer.FIELD_ID)); + String name = (String) eventJson.get(EventBaseJsonSerializer.FIELD_NAME); + String description = (String) eventJson.get(EventBaseJsonSerializer.FIELD_DESCRIPTION); + String officialWebsiteURLAsString = (String) eventJson.get(EventBaseJsonSerializer.FIELD_OFFICIAL_WEBSITE_URL); + Number startDate = (Number) eventJson.get(EventBaseJsonSerializer.FIELD_START_DATE); + Number endDate = (Number) eventJson.get(EventBaseJsonSerializer.FIELD_END_DATE); final Venue venue; - if (object.get(EventBaseJsonSerializer.FIELD_VENUE) != null) { - JSONObject venueObject = Helpers.getNestedObjectSafe(object, EventBaseJsonSerializer.FIELD_VENUE); + if (eventJson.get(EventBaseJsonSerializer.FIELD_VENUE) != null) { + JSONObject venueObject = Helpers.getNestedObjectSafe(eventJson, EventBaseJsonSerializer.FIELD_VENUE); venue = venueDeserializer.deserialize(venueObject); } else { venue = null; } - JSONArray leaderboardGroupsJson = (JSONArray) object.get(EventBaseJsonSerializer.FIELDS_LEADERBOARD_GROUPS); + JSONArray leaderboardGroupsJson = (JSONArray) eventJson.get(EventBaseJsonSerializer.FIELDS_LEADERBOARD_GROUPS); List leaderboardGroups = new ArrayList(); if (leaderboardGroupsJson != null) { for (Object lgJson : leaderboardGroupsJson) { @@ -60,40 +70,75 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { throw new JsonDeserializationException("Error deserializing official website URL for event "+name, e); } } + JSONArray imagesJson = (JSONArray) eventJson.get(EventBaseJsonSerializer.FIELD_IMAGES); + if (imagesJson != null) { + for (Object imageJson : imagesJson) { + ImageDescriptor imgaeDescriptor = loadImage((JSONObject) imageJson); + if (imgaeDescriptor != null) { + result.addImage(imgaeDescriptor); + } + } + } + JSONArray videosJson = (JSONArray) eventJson.get(EventBaseJsonSerializer.FIELD_VIDEOS); + if (videosJson != null) { + for (Object videoJson : videosJson) { + VideoDescriptor videoDescriptor = loadVideo((JSONObject) videoJson); + if (videoDescriptor != null) { + result.addVideo(videoDescriptor); + } + } + } + // if we read an event with data in the old image/video format we need migrate them + if(Util.size(result.getImages()) == 0 && Util.size(result.getVideos()) == 0) { + readImageAndVideoURLsFromLegacyServer(eventJson, result); + + } + return result; + } + + private void readImageAndVideoURLsFromLegacyServer(JSONObject eventJson, StrippedEventImpl event) throws JsonDeserializationException { + URL logoImageURL = null; + List imageURLs = new ArrayList(); + List sponsorImageURLs = new ArrayList(); + List videoURLs = new ArrayList(); + + String logoImageURLAsString = (String) eventJson.get(EventBaseJsonSerializer.FIELD_LOGO_IMAGE_URL); if (logoImageURLAsString != null) { try { - result.setLogoImageURL(new URL(logoImageURLAsString)); + logoImageURL = new URL(logoImageURLAsString); } catch (MalformedURLException e) { - throw new JsonDeserializationException("Error deserializing logo image URL for event "+name, e); + throw new JsonDeserializationException("Error deserializing logo image URL for event "+event.getName(), e); } } - if (object.get(EventBaseJsonSerializer.FIELD_IMAGE_URLS) != null) { + if (eventJson.get(EventBaseJsonSerializer.FIELD_IMAGE_URLS) != null) { try { - result.setImageURLs(getURLsFromStrings(Helpers.getNestedArraySafe(object, EventBaseJsonSerializer.FIELD_IMAGE_URLS))); + imageURLs = getURLsFromStrings(Helpers.getNestedArraySafe(eventJson, EventBaseJsonSerializer.FIELD_IMAGE_URLS)); } catch (MalformedURLException e) { - throw new JsonDeserializationException("Error deserializing image URLs for event "+name, e); + throw new JsonDeserializationException("Error deserializing image URLs for event "+event.getName(), e); } } - if (object.get(EventBaseJsonSerializer.FIELD_VIDEO_URLS) != null) { + if (eventJson.get(EventBaseJsonSerializer.FIELD_VIDEO_URLS) != null) { try { - result.setVideoURLs(getURLsFromStrings(Helpers.getNestedArraySafe(object, EventBaseJsonSerializer.FIELD_VIDEO_URLS))); + videoURLs = getURLsFromStrings(Helpers.getNestedArraySafe(eventJson, EventBaseJsonSerializer.FIELD_VIDEO_URLS)); } catch (MalformedURLException e) { - throw new JsonDeserializationException("Error deserializing video URLs for event "+name, e); + throw new JsonDeserializationException("Error deserializing video URLs for event "+event.getName(), e); } } - if (object.get(EventBaseJsonSerializer.FIELD_SPONSOR_IMAGE_URLS) != null) { + if (eventJson.get(EventBaseJsonSerializer.FIELD_SPONSOR_IMAGE_URLS) != null) { try { - result.setSponsorImageURLs(getURLsFromStrings(Helpers.getNestedArraySafe(object, EventBaseJsonSerializer.FIELD_SPONSOR_IMAGE_URLS))); + sponsorImageURLs = getURLsFromStrings(Helpers.getNestedArraySafe(eventJson, EventBaseJsonSerializer.FIELD_SPONSOR_IMAGE_URLS)); } catch (MalformedURLException e) { - throw new JsonDeserializationException("Error deserializing sponsor image URLs for event "+name, e); + throw new JsonDeserializationException("Error deserializing sponsor image URLs for event "+event.getName(), e); } - } - JSONArray imageSizes = (JSONArray) object.get(EventBaseJsonSerializer.FIELD_IMAGE_SIZES); - if (imageSizes != null) { - for (Object imageURLAndSizeObject : imageSizes) { + } + // handle images sizes coming from an old server instance not having images and videos in the rich format + Map imageSizes = new HashMap();; + JSONArray imageSizesJson = (JSONArray) eventJson.get(EventBaseJsonSerializer.FIELD_IMAGE_SIZES); + if (imageSizesJson != null) { + for (Object imageURLAndSizeObject : imageSizesJson) { JSONObject imageURLAndSizeJson = (JSONObject) imageURLAndSizeObject; try { - result.setImageSize( + imageSizes.put( new URL((String) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_URL)), new ImageSizeImpl( ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH)).intValue(), @@ -103,10 +148,77 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { } } } - return result; + event.setMediaURLs(imageURLs, sponsorImageURLs, videoURLs, logoImageURL, imageSizes); } - private Iterable getURLsFromStrings(JSONArray strings) throws MalformedURLException { + private ImageDescriptor loadImage(JSONObject imageJson) { + ImageDescriptor image = null; + URL imageURL = Helpers.getURLField(imageJson, EventBaseJsonSerializer.FIELD_SOURCE_URL); + if (imageURL != null) { + String title = (String) imageJson.get(EventBaseJsonSerializer.FIELD_TITLE); + String subtitle = (String) imageJson.get(EventBaseJsonSerializer.FIELD_SUBTITLE); + String copyright = (String) imageJson.get(EventBaseJsonSerializer.FIELD_COPYRIGHT); + String localeRaw = (String) imageJson.get(EventBaseJsonSerializer.FIELD_LOCALE); + Locale locale = localeRaw != null ? Locale.forLanguageTag(localeRaw) : null; + Number imageWidth = (Number) imageJson.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH_IN_PX); + Number imageHeight = (Number) imageJson.get(EventBaseJsonSerializer.FIELD_IMAGE_HEIGHT_IN_PX); + Number createdAtDateInMs = (Number) imageJson.get(EventBaseJsonSerializer.FIELD_CREATEDATDATE); + TimePoint createdAtDate = createdAtDateInMs != null ? new MillisecondsTimePoint(createdAtDateInMs.longValue()) : null; + JSONArray tags = (JSONArray) imageJson.get(EventBaseJsonSerializer.FIELD_TAGS); + List imageTags = new ArrayList(); + if (tags != null) { + for (Object tagObject : tags) { + imageTags.add((String) tagObject); + } + } + image = new ImageDescriptorImpl(imageURL, createdAtDate); + image.setCopyright(copyright); + image.setLocale(locale); + image.setTitle(title); + image.setSubtitle(subtitle); + image.setTags(imageTags); + if (imageWidth != null && imageHeight != null) { + image.setSize(imageWidth.intValue(), imageHeight.intValue()); + } + } + return image; + } + + private VideoDescriptor loadVideo(JSONObject videoJson) { + VideoDescriptor video = null; + URL videoURL = Helpers.getURLField(videoJson, EventBaseJsonSerializer.FIELD_SOURCE_URL); + if(videoURL != null) { + String title = (String) videoJson.get(EventBaseJsonSerializer.FIELD_TITLE); + String subtitle = (String) videoJson.get(EventBaseJsonSerializer.FIELD_SUBTITLE); + String copyright = (String) videoJson.get(EventBaseJsonSerializer.FIELD_COPYRIGHT); + String localeRaw = (String) videoJson.get(EventBaseJsonSerializer.FIELD_LOCALE); + Locale locale = localeRaw != null ? Locale.forLanguageTag(localeRaw) : null; + Object mimeTypeRaw = videoJson.get(EventBaseJsonSerializer.FIELD_MIMETYPE); + MimeType mimeType = mimeTypeRaw == null ? null : MimeType.valueOf((String) mimeTypeRaw); + Number createdAtDateInMs = (Number) videoJson.get(EventBaseJsonSerializer.FIELD_CREATEDATDATE); + TimePoint createdAtDate = createdAtDateInMs != null ? new MillisecondsTimePoint(createdAtDateInMs.longValue()) : null; + JSONArray tags = (JSONArray) videoJson.get(EventBaseJsonSerializer.FIELD_TAGS); + Integer lengthInSeconds = (Integer) videoJson.get(EventBaseJsonSerializer.FIELD_VIDEO_LENGTH_IN_SECONDS); + URL thumbnailURL = Helpers.getURLField(videoJson, EventBaseJsonSerializer.FIELD_VIDEO_THUMBNAIL_URL); + List videoTags = new ArrayList(); + if (tags != null) { + for (Object tagObject : tags) { + videoTags.add((String) tagObject); + } + } + video = new VideoDescriptorImpl(videoURL, mimeType, createdAtDate); + video.setCopyright(copyright); + video.setLocale(locale); + video.setTitle(title); + video.setSubtitle(subtitle); + video.setTags(videoTags); + video.setLengthInSeconds(lengthInSeconds); + video.setThumbnailURL(thumbnailURL); + } + return video; + } + + private List getURLsFromStrings(JSONArray strings) throws MalformedURLException { List result = new ArrayList(); if (strings != null) { for (Object string : strings) { @@ -115,5 +227,4 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { } return result; } - } diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/Helpers.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/Helpers.java index 499f2d60e88..6762c862c3e 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/Helpers.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/Helpers.java @@ -1,6 +1,8 @@ package com.sap.sailing.server.gateway.deserialization.impl; import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; import java.util.UUID; import java.util.logging.Logger; @@ -48,6 +50,19 @@ public class Helpers { return (JSONArray) childObject; } + public static URL getURLField(JSONObject parent, String fieldName) { + URL result = null; + String urlAsString = (String) parent.get(fieldName); + if (urlAsString != null) { + try { + result = new URL(urlAsString); + } catch (MalformedURLException e) { + logger.severe("Error deserializing URL " + urlAsString); + } + } + return result; + } + public static Serializable tryUuidConversion(Serializable serializableId) { try { return UUID.fromString(serializableId.toString()); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java index 82c729f0025..6155f8b61d7 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java @@ -1,9 +1,6 @@ package com.sap.sailing.server.gateway.serialization.impl; import java.net.URL; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -12,11 +9,10 @@ import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sse.common.media.ImageSize; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class EventBaseJsonSerializer implements JsonSerializer { - private static final Logger logger = Logger.getLogger(EventBaseJsonSerializer.class.getName()); - public static final String FIELD_ID = "id"; public static final String FIELD_NAME = "name"; public static final String FIELD_DESCRIPTION = "description"; @@ -31,9 +27,27 @@ public class EventBaseJsonSerializer implements JsonSerializer { public static final String FIELD_IMAGE_URL = "imageURL"; public static final String FIELD_IMAGE_WIDTH = "imageWidth"; public static final String FIELD_IMAGE_HEIGHT = "imageHeight"; + public static final String FIELD_VIDEO_URL = "videoURL"; public static final String FIELD_OFFICIAL_WEBSITE_URL = "officialWebsiteURL"; public static final String FIELDS_LEADERBOARD_GROUPS = "leaderboardGroups"; + public static final String FIELD_SOURCE_URL = "sourceURL"; + public static final String FIELD_TITLE = "title"; + public static final String FIELD_SUBTITLE = "subtitle"; + public static final String FIELD_MIMETYPE = "mimetype"; + public static final String FIELD_COPYRIGHT = "copyright"; + public static final String FIELD_CREATEDATDATE = "createdAtDate"; + public static final String FIELD_LOCALE = "locale"; + public static final String FIELD_TAGS = "tags"; + // specific image fields + public static final String FIELD_IMAGES = "images"; + public static final String FIELD_IMAGE_WIDTH_IN_PX = "widthInPixel"; + public static final String FIELD_IMAGE_HEIGHT_IN_PX = "heightInPixel"; + // specific video fields + public static final String FIELD_VIDEOS = "videos"; + public static final String FIELD_VIDEO_THUMBNAIL_URL = "thumbnailURL"; + public static final String FIELD_VIDEO_LENGTH_IN_SECONDS = "lengthInSeconds"; + private final JsonSerializer venueSerializer; private final JsonSerializer leaderboardGroupBaseSerializer; @@ -42,16 +56,17 @@ public class EventBaseJsonSerializer implements JsonSerializer { this.venueSerializer = venueSerializer; } + @SuppressWarnings("deprecation") public JSONObject serialize(EventBase event) { JSONObject result = new JSONObject(); result.put(FIELD_ID, event.getId().toString()); result.put(FIELD_NAME, event.getName()); result.put(FIELD_DESCRIPTION, event.getDescription()); result.put(FIELD_OFFICIAL_WEBSITE_URL, event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); - result.put(FIELD_LOGO_IMAGE_URL, event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); result.put(FIELD_START_DATE, event.getStartDate() != null ? event.getStartDate().asMillis() : null); result.put(FIELD_END_DATE, event.getStartDate() != null ? event.getEndDate().asMillis() : null); result.put(FIELD_VENUE, venueSerializer.serialize(event.getVenue())); + result.put(FIELD_LOGO_IMAGE_URL, event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); result.put(FIELD_IMAGE_URLS, getURLsAsStringArray(event.getImageURLs())); result.put(FIELD_VIDEO_URLS, getURLsAsStringArray(event.getVideoURLs())); result.put(FIELD_SPONSOR_IMAGE_URLS, getURLsAsStringArray(event.getSponsorImageURLs())); @@ -62,37 +77,71 @@ public class EventBaseJsonSerializer implements JsonSerializer { } JSONArray imageSizes = new JSONArray(); result.put(FIELD_IMAGE_SIZES, imageSizes); - for (URL imageURL : event.getImageURLs()) { - addImageSize(imageURL, imageSizes, event); + for (ImageDescriptor image : event.getImages()) { + addImageSize(image, imageSizes); } - if (event.getLogoImageURL() != null) { - addImageSize(event.getLogoImageURL(), imageSizes, event); + JSONArray jsonImages = new JSONArray(); + for(ImageDescriptor imageDescriptor: event.getImages()) { + addImage(imageDescriptor, jsonImages); } - for (URL sponsorImageURL : event.getSponsorImageURLs()) { - addImageSize(sponsorImageURL, imageSizes, event); + result.put(FIELD_IMAGES, jsonImages); + JSONArray jsonVideos = new JSONArray(); + for(VideoDescriptor videoDescriptor: event.getVideos()) { + addVideo(videoDescriptor, jsonVideos); } + result.put(FIELD_VIDEOS, jsonVideos); return result; } + private void addImage(ImageDescriptor image, JSONArray imagesJson) { + JSONObject imageJson = new JSONObject(); + imageJson.put(FIELD_SOURCE_URL, image.getURL().toString()); + imageJson.put(FIELD_LOCALE, image.getLocale() != null ? image.getLocale().toLanguageTag() : null); + imageJson.put(FIELD_TITLE, image.getTitle()); + imageJson.put(FIELD_SUBTITLE, image.getSubtitle()); + imageJson.put(FIELD_MIMETYPE, image.getMimeType().name()); + imageJson.put(FIELD_COPYRIGHT, image.getCopyright()); + imageJson.put(FIELD_IMAGE_WIDTH_IN_PX, image.getWidthInPx()); + imageJson.put(FIELD_IMAGE_HEIGHT_IN_PX, image.getHeightInPx()); + imageJson.put(FIELD_CREATEDATDATE, image.getCreatedAtDate().asMillis()); + JSONArray tags = new JSONArray(); + for (String tag : image.getTags()) { + tags.add(tag); + } + imageJson.put(FIELD_TAGS, tags); + imagesJson.add(imageJson); + } + + private void addVideo(VideoDescriptor video, JSONArray videosJson) { + JSONObject videoJson = new JSONObject(); + videoJson.put(FIELD_SOURCE_URL, video.getURL().toString()); + videoJson.put(FIELD_LOCALE, video.getLocale() != null ? video.getLocale().toLanguageTag() : null); + videoJson.put(FIELD_VIDEO_THUMBNAIL_URL, video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null); + videoJson.put(FIELD_TITLE, video.getTitle()); + videoJson.put(FIELD_SUBTITLE, video.getSubtitle()); + videoJson.put(FIELD_MIMETYPE, video.getMimeType().name()); + videoJson.put(FIELD_COPYRIGHT, video.getCopyright()); + videoJson.put(FIELD_VIDEO_LENGTH_IN_SECONDS, video.getLengthInSeconds()); + videoJson.put(FIELD_CREATEDATDATE, video.getCreatedAtDate().asMillis()); + JSONArray tags = new JSONArray(); + for (String tag : video.getTags()) { + tags.add(tag); + } + videoJson.put(FIELD_TAGS, tags); + videosJson.add(videoJson); + } + /** - * If eventBase knows the size of the image with URL imageURL, that size is serialized as + * For backward compatibility the size of an image is also serialized as * JSON object to imageSizes */ - private void addImageSize(URL imageURL, JSONArray imageSizes, EventBase eventBase) { - ImageSize imageSize; - try { - imageSize = eventBase.getImageSize(imageURL); - if (imageSize != null) { - JSONObject imageSizeJson = new JSONObject(); - imageSizes.add(imageSizeJson); - imageSizeJson.put(FIELD_IMAGE_URL, imageURL.toString()); - imageSizeJson.put(FIELD_IMAGE_WIDTH, imageSize.getWidth()); - imageSizeJson.put(FIELD_IMAGE_HEIGHT, imageSize.getHeight()); - } - } catch (InterruptedException e) { - logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e); - } catch (ExecutionException e) { - logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e); + private void addImageSize(ImageDescriptor image, JSONArray imageSizes) { + if(image.getHeightInPx() != null && image.getWidthInPx() != null) { + JSONObject imageSizeJson = new JSONObject(); + imageSizes.add(imageSizeJson); + imageSizeJson.put(FIELD_IMAGE_URL, image.getURL().toString()); + imageSizeJson.put(FIELD_IMAGE_WIDTH, image.getWidthInPx()); + imageSizeJson.put(FIELD_IMAGE_HEIGHT, image.getHeightInPx()); } } diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java index 9351a3dcd5b..0ea003c6dfd 100755 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java @@ -32,6 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class EventDataJsonSerializerTest { protected final UUID expectedId = UUID.randomUUID(); @@ -55,6 +57,7 @@ public class EventDataJsonSerializerTest { } // see https://groups.google.com/forum/?fromgroups=#!topic/mockito/iMumB0_bpdo + @SuppressWarnings("deprecation") @Before public void setUp() { // Event and its basic attributes ... @@ -70,6 +73,8 @@ public class EventDataJsonSerializerTest { when(event.getImageURLs()).thenReturn(Collections.emptySet()); when(event.getVideoURLs()).thenReturn(Collections.emptySet()); when(event.getSponsorImageURLs()).thenReturn(Collections.emptySet()); + when(event.getImages()).thenReturn(Collections.emptySet()); + when(event.getVideos()).thenReturn(Collections.emptySet()); // ... and the serializer itself. serializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), new LeaderboardGroupBaseJsonDeserializer()); diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithImagesAndVideosTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithImagesAndVideosTest.java new file mode 100644 index 00000000000..d4d833b9be9 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithImagesAndVideosTest.java @@ -0,0 +1,191 @@ +package com.sap.sailing.server.gateway.serialization.test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.base.impl.VenueImpl; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.ImageDescriptorImpl; +import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; +import com.sap.sse.common.media.VideoDescriptorImpl; + +public class EventDataJsonSerializerWithImagesAndVideosTest { + private final UUID expectedId = UUID.randomUUID(); + private final String expectedName = "ab"; + private final Locale locale = Locale.GERMAN; + private final TimePoint expectedStartDate = new MillisecondsTimePoint(new Date()); + private final TimePoint expectedEndDate = new MillisecondsTimePoint(new Date()); + private final Venue expectedVenue = new VenueImpl("Expected Venue"); + private final LeaderboardGroup expectedLeaderboardGroup = mock(LeaderboardGroup.class); + private final URL imageURL; + private final String copyright = "copyright by Alex"; + private final String imageTitle = "My image title"; + private final String imageSubtitle = "My image subtitle"; + private final Integer imageWidth = 500; + private final Integer imageHeight = 300; + + private final URL videoURL; + private final URL videoThumbnailURL; + private final Locale locale2 = Locale.CANADA_FRENCH; + private final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h + private final MimeType mimeType = MimeType.mp4; + private final String copyright2 = "copyright by Don"; + private final String videoTitle = "My video title"; + private final String videoSubtitle = "My video subtitle"; + + private final TimePoint createdAt = MillisecondsTimePoint.now(); + private List images = new ArrayList(); + private List videos = new ArrayList(); + + private EventBaseJsonSerializer serializer; + private EventBaseJsonDeserializer deserializer; + private EventBase event; + + public EventDataJsonSerializerWithImagesAndVideosTest() throws MalformedURLException { + imageURL = new URL("http://some.host/with/some/file2.jpg"); + ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt); + image1.setCopyright(copyright); + image1.setLocale(locale); + image1.setSize(imageWidth, imageHeight); + image1.setTitle(imageTitle); + image1.setSubtitle(imageSubtitle); + image1.addTag("Tag1"); + image1.addTag("Tag2"); + image1.addTag("Tag3"); + images.add(image1); + + videoURL = new URL("http://some.host/with/some/video.mpg"); + videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg"); + + VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt); + video1.setCopyright(copyright2); + video1.setLocale(locale2); + video1.setTitle(videoTitle); + video1.setSubtitle(videoSubtitle); + video1.setThumbnailURL(videoThumbnailURL); + video1.setLengthInSeconds(videoLengthInSeconds); + video1.addTag("Tag1"); + video1.addTag("Tag2"); + video1.addTag("Tag3"); + video1.addTag("Tag4"); + videos.add(video1); + } + + @SuppressWarnings("deprecation") + @Before + public void setUp() { + // Event and its basic attributes ... + event = mock(EventBase.class); + when(event.getId()).thenReturn(expectedId); + when(event.getName()).thenReturn(expectedName); + when(event.getStartDate()).thenReturn(expectedStartDate); + when(event.getEndDate()).thenReturn(expectedEndDate); + when(event.getVenue()).thenReturn(expectedVenue); + when(event.getImageURLs()).thenReturn(Collections.emptySet()); + when(event.getVideoURLs()).thenReturn(Collections.emptySet()); + when(event.getSponsorImageURLs()).thenReturn(Collections.emptySet()); + when(event.getImages()).thenReturn(images); + when(event.getVideos()).thenReturn(videos); + // ... and the serializer itself. + serializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); + deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), new LeaderboardGroupBaseJsonDeserializer()); + + when(expectedLeaderboardGroup.getId()).thenReturn(UUID.randomUUID()); + when(expectedLeaderboardGroup.getName()).thenReturn("LG"); + when(expectedLeaderboardGroup.getDescription()).thenReturn("LG Description"); + when(expectedLeaderboardGroup.getDisplayName()).thenReturn("LG Display Name"); + when(expectedLeaderboardGroup.hasOverallLeaderboard()).thenReturn(false); + doReturn(Collections.singleton(expectedLeaderboardGroup)).when(event).getLeaderboardGroups(); + } + + @Test + public void testImageAndVideosAttributes() throws MalformedURLException { + JSONObject result = serializer.serialize(event); + JSONArray imagesJson = (JSONArray) result.get(EventBaseJsonSerializer.FIELD_IMAGES); + JSONObject image1Json = (JSONObject) imagesJson.get(0); + assertEquals(imageURL, new URL((String) image1Json.get(EventBaseJsonSerializer.FIELD_SOURCE_URL))); + assertEquals(createdAt, new MillisecondsTimePoint(((Number) image1Json.get(EventBaseJsonSerializer.FIELD_CREATEDATDATE)).longValue())); + assertEquals(copyright, (String) image1Json.get(EventBaseJsonSerializer.FIELD_COPYRIGHT)); + assertEquals(imageTitle, (String) image1Json.get(EventBaseJsonSerializer.FIELD_TITLE)); + assertEquals(imageSubtitle, (String) image1Json.get(EventBaseJsonSerializer.FIELD_SUBTITLE)); + assertEquals(imageWidth, (Integer) image1Json.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH_IN_PX)); + assertEquals(imageHeight, (Integer) image1Json.get(EventBaseJsonSerializer.FIELD_IMAGE_HEIGHT_IN_PX)); + JSONArray imageTagsJson = (JSONArray) image1Json.get(EventBaseJsonSerializer.FIELD_TAGS); + assertEquals(3, imageTagsJson.size()); + + JSONArray videosJson = (JSONArray) result.get(EventBaseJsonSerializer.FIELD_VIDEOS); + JSONObject video1Json = (JSONObject) videosJson.get(0); + assertEquals(videoURL, new URL((String) video1Json.get(EventBaseJsonSerializer.FIELD_SOURCE_URL))); + assertEquals(createdAt, new MillisecondsTimePoint(((Number) video1Json.get(EventBaseJsonSerializer.FIELD_CREATEDATDATE)).longValue())); + assertEquals(copyright2, (String) video1Json.get(EventBaseJsonSerializer.FIELD_COPYRIGHT)); + assertEquals(videoTitle, (String) video1Json.get(EventBaseJsonSerializer.FIELD_TITLE)); + assertEquals(videoSubtitle, (String) video1Json.get(EventBaseJsonSerializer.FIELD_SUBTITLE)); + assertEquals(videoThumbnailURL, new URL((String) video1Json.get(EventBaseJsonSerializer.FIELD_VIDEO_THUMBNAIL_URL))); + assertEquals(videoLengthInSeconds, (Integer) video1Json.get(EventBaseJsonSerializer.FIELD_VIDEO_LENGTH_IN_SECONDS)); + JSONArray videoTagsJson = (JSONArray) video1Json.get(EventBaseJsonSerializer.FIELD_TAGS); + assertEquals(4, videoTagsJson.size()); + } + + @Test + public void testImageAndVideosAfterDeserialization() throws JsonDeserializationException { + final JSONObject result = serializer.serialize(event); + final EventBase deserializedEvent = deserializer.deserialize(result); + + ImageDescriptor image1 = deserializedEvent.getImages().iterator().next(); + assertEquals(createdAt, image1.getCreatedAtDate()); + assertEquals(imageURL, image1.getURL()); + assertEquals(locale, image1.getLocale()); + assertEquals(copyright, image1.getCopyright()); + assertEquals(imageTitle, image1.getTitle()); + assertEquals(imageSubtitle, image1.getSubtitle()); + assertEquals(imageWidth, image1.getWidthInPx()); + assertEquals(imageHeight, image1.getHeightInPx()); + assertEquals(3, Util.size(image1.getTags())); + + VideoDescriptor video1 = deserializedEvent.getVideos().iterator().next(); + assertEquals(createdAt, video1.getCreatedAtDate()); + assertEquals(videoURL, video1.getURL()); + assertEquals(mimeType, video1.getMimeType()); + assertEquals(copyright2, video1.getCopyright()); + assertEquals(locale2, video1.getLocale()); + assertEquals(videoTitle, video1.getTitle()); + assertEquals(videoSubtitle, video1.getSubtitle()); + assertEquals(videoThumbnailURL, video1.getThumbnailURL()); + assertEquals(videoLengthInSeconds, video1.getLengthInSeconds()); + assertEquals(4, Util.size(video1.getTags())); + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java index 3bee26cc81a..b9622aeda1c 100755 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java @@ -32,6 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class EventDataJsonSerializerWithNullValuesTest { protected final UUID expectedId = UUID.randomUUID(); @@ -50,6 +52,7 @@ public class EventDataJsonSerializerWithNullValuesTest { protected EventBase event; // see https://groups.google.com/forum/?fromgroups=#!topic/mockito/iMumB0_bpdo + @SuppressWarnings("deprecation") @Before public void setUp() { // Event and its basic attributes ... @@ -65,6 +68,8 @@ public class EventDataJsonSerializerWithNullValuesTest { when(event.getImageURLs()).thenReturn(Collections.emptySet()); when(event.getVideoURLs()).thenReturn(Collections.emptySet()); when(event.getSponsorImageURLs()).thenReturn(Collections.emptySet()); + when(event.getImages()).thenReturn(Collections.emptySet()); + when(event.getVideos()).thenReturn(Collections.emptySet()); // ... and the serializer itself. serializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), new LeaderboardGroupBaseJsonDeserializer()); @@ -79,31 +84,23 @@ public class EventDataJsonSerializerWithNullValuesTest { @Test public void testBasicAttributes() throws MalformedURLException { JSONObject result = serializer.serialize(event); - assertEquals( - expectedId, + assertEquals(expectedId, UUID.fromString(result.get(EventBaseJsonSerializer.FIELD_ID).toString())); - assertEquals( - expectedName, + assertEquals(expectedName, result.get(EventBaseJsonSerializer.FIELD_NAME)); - assertEquals( - expectedDescription, + assertEquals(expectedDescription, result.get(EventBaseJsonSerializer.FIELD_DESCRIPTION)); - assertEquals( - expectedOfficialWebsiteURL, + assertEquals(expectedOfficialWebsiteURL, result.get(EventBaseJsonSerializer.FIELD_OFFICIAL_WEBSITE_URL) == null ? null : new URL((String) result.get(EventBaseJsonSerializer.FIELD_OFFICIAL_WEBSITE_URL))); - assertEquals( - expectedLogoImageURL, + assertEquals(expectedLogoImageURL, result.get(EventBaseJsonSerializer.FIELD_LOGO_IMAGE_URL) == null ? null : new URL((String) result.get(EventBaseJsonSerializer.FIELD_LOGO_IMAGE_URL))); - assertEquals( - expectedDescription, + assertEquals(expectedDescription, result.get(EventBaseJsonSerializer.FIELD_DESCRIPTION)); - assertEquals( - expectedStartDate, + assertEquals(expectedStartDate, new MillisecondsTimePoint(((Number) result.get(EventBaseJsonSerializer.FIELD_START_DATE)).longValue())); - assertEquals( - expectedEndDate, + assertEquals(expectedEndDate, new MillisecondsTimePoint(((Number) result.get(EventBaseJsonSerializer.FIELD_END_DATE)).longValue())); } diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java index ddfc72afdf7..6caf3c22561 100755 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java @@ -31,6 +31,8 @@ import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class EventWithNullStartAndEndDataJsonSerializerTest { @@ -55,6 +57,7 @@ public class EventWithNullStartAndEndDataJsonSerializerTest { expectedLogoImageURL = new URL("http://official.logo.com/logo.png"); } + @SuppressWarnings("deprecation") @Before public void setUp() { // Event and its basic attributes ... @@ -74,6 +77,8 @@ public class EventWithNullStartAndEndDataJsonSerializerTest { when(event.getImageURLs()).thenReturn(Collections.emptySet()); when(event.getVideoURLs()).thenReturn(Collections.emptySet()); when(event.getSponsorImageURLs()).thenReturn(Collections.emptySet()); + when(event.getImages()).thenReturn(Collections.emptySet()); + when(event.getVideos()).thenReturn(Collections.emptySet()); doReturn(expectedLeaderboardGroups).when(event).getLeaderboardGroups(); // ... and the serializer itself. @@ -91,6 +96,7 @@ public class EventWithNullStartAndEndDataJsonSerializerTest { assertNull(result.get(EventBaseJsonSerializer.FIELD_END_DATE)); } + @SuppressWarnings("deprecation") @Test public void testBasicAttributesAfterDeserialization() throws JsonDeserializationException { final JSONObject result = serializer.serialize(event); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 4bf9378e56b..cd6423286a8 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -127,7 +126,9 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; import com.sap.sse.common.media.MimeType; +import com.sap.sse.common.media.VideoDescriptor; import com.sap.sse.mongodb.MongoDBConfiguration; import com.sap.sse.mongodb.MongoDBService; @@ -1326,12 +1327,10 @@ public class MasterDataImportTest { InterruptedException, ClassNotFoundException { RacingEventService sourceService = new RacingEventServiceImpl(); - Iterable imageURLs = new HashSet<>(); - Iterable videoURLs = new HashSet<>(); - Iterable sponsorImageURLs = new HashSet<>(); Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null, - new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, - videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null); + new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), + /* officialWebsiteURL */ null, /* images */Collections. emptyList(), + /* videos */Collections. emptyList()); CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID()); Regatta regatta = sourceService.createRegatta( @@ -1393,12 +1392,10 @@ public class MasterDataImportTest { // Setup source service RacingEventService sourceService = new RacingEventServiceImpl(); - Iterable imageURLs = new HashSet<>(); - Iterable videoURLs = new HashSet<>(); - Iterable sponsorImageURLs = new HashSet<>(); Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null, - new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, - videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null); + new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), + /* officialWebsiteURL */ null, /* images */Collections. emptyList(), + /* videos */Collections. emptyList()); CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID()); List raceColumnNames = new ArrayList(); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index 3235fccba05..0933a8d1256 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -73,6 +72,8 @@ import com.sap.sse.common.Color; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; @@ -119,9 +120,7 @@ public class SearchServiceTest { cal.set(2014, 5, 8, 16, 00); final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ - "Kiel", true, UUID.randomUUID(), Collections.emptySet(), - Collections.emptySet(), - /* sponsorImageURLs */ Collections.emptySet(), /* logoImageURLAsString */ null, /* officialWebsiteURLAsString */ null)); + "Kiel", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /* images */Collections. emptyList(), /* videos */Collections. emptyList())); kiel = pfingstbusch.getVenue(); final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); kiel.addCourseArea(kielAlpha); @@ -154,9 +153,8 @@ public class SearchServiceTest { cal.set(2014, 5, 8, 18, 00); final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ - "Flensburg", true, UUID.randomUUID(), Collections.emptySet(), - Collections.emptySet(), /* sponsorImageURLs */ Collections.emptySet(), - /* logoimageURL */ null, /* officialWebsiteURLAsString */ null)); + "Flensburg", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, + /* images */Collections. emptyList(), /* videos */Collections. emptyList())); flensburg = aalEvent.getVenue(); final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); flensburg.addCourseArea(flensburgStandard); diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 7614e7a8e9a..62feacb163b 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -34,5 +34,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index c71cbc26452..568a0fb6aef 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 34fea4ab561..7bdb8892027 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 8960a922fc4..f217788eb09 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -16,7 +16,7 @@ - + @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index fe8ccfd0d3c..2dd0f73aea5 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index c5041dbd0f6..680a7d606b5 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -16,7 +16,7 @@ - + @@ -27,5 +27,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 6a403e984d0..c79588fae06 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 9c04071ec1e..7441e7a94ce 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -34,6 +34,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 59419fdd57d..08f7149dd98 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 6d4f97df3be..0c026c8faa5 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -17,7 +17,7 @@ - + @@ -27,5 +27,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index 1c196a6a607..f6cc5fe138d 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -26,5 +26,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 0e91cb26dc2..164247e4a42 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -28,6 +28,6 @@ - + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index f0aa9d9f5d6..ccfc26062cd 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -74,6 +74,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.TypeBasedServiceFinderFactory; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.common.search.Searchable; @@ -369,7 +371,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch */ void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, - URL logoImageURL, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs); + Iterable images, Iterable videos); /** * Renames a sailing event. If a sailing event by the name oldName does not exist in {@link #getEvents()}, @@ -464,7 +466,8 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch ConcurrentHashMap getPersistentRegattasForRaceIDs(); Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, - boolean isPublic, UUID id, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL); + boolean isPublic, UUID id, URL officialWebsiteURL, Iterable images, + Iterable videos); void setRegattaForRace(Regatta regatta, String raceIdAsString); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 657e50f24b8..dd38e8ea84a 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -207,7 +207,10 @@ import com.sap.sse.BuildVersion; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TypeBasedServiceFinderFactory; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.common.search.ResultImpl; @@ -538,7 +541,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes // This is more for debugging purposes than for anything else. addFlexibleLeaderboard(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME, null, new int[] { 5, 8 }, getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), null); - loadStoredEvents(); + + final Iterable> loadedEventsWithRequireStoreFlag = loadStoredEvents(); loadStoredRegattas(); loadRaceIDToRegattaAssociations(); loadStoredLeaderboardsAndGroups(); @@ -546,6 +550,14 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes loadMediaLibary(); loadStoredDeviceConfigurations(); loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh(); + + // Stores all events which run through a data migration + // Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation + for (Pair eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) { + if (eventAndRequireStoreFlag.getB()) { + mongoObjectFactory.storeEvent(eventAndRequireStoreFlag.getA()); + } + } } @Override @@ -632,13 +644,16 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes } } - private void loadStoredEvents() { - for (Event event : domainObjectFactory.loadAllEvents()) { + private Iterable> loadStoredEvents() { + Iterable> loadedEventsWithRequireStoreFlag = domainObjectFactory.loadAllEvents(); + for (Pair eventAndFlag : loadedEventsWithRequireStoreFlag) { + Event event = eventAndFlag.getA(); if (event.getId() != null) eventsById.put(event.getId(), event); } + return loadedEventsWithRequireStoreFlag; } - + private void loadLinksFromEventsToLeaderboardGroups() { domainObjectFactory.loadLeaderboardGroupLinksForEvents(/* eventResolver */this, /* leaderboardGroupResolver */ this); @@ -2572,13 +2587,10 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, UUID id) { Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic, - id, /* imageURLs */Collections. emptyList(), - /* videoURLs */Collections. emptyList(), /* sponsorImageURLs */Collections. emptyList(), /* logoImageURL */ - null, /* officialWebsiteURL */null); - replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, /* imageURLs */ - id, Collections. emptyList(), - /* videoURLs */Collections. emptyList(), /* sponsorImageURLs */Collections. emptyList(), - /* logoimageURL */null, /* officialWebsiteURLAsString */null)); + id, /* officialWebsiteURL */null, /* images */Collections. emptyList(), + /* videos */Collections. emptyList()); + replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, id, + /* officialWebsiteURLAsString */null, /* images */Collections. emptyList(), /* videos */Collections. emptyList())); return result; } @@ -2589,16 +2601,14 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, - TimePoint endDate, String venue, boolean isPublic, UUID id, Iterable imageURLs, - Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) { + TimePoint endDate, String venue, boolean isPublic, UUID id, URL officialWebsiteURL, Iterable images, + Iterable videos) { Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id); addEvent(result); result.setDescription(eventDescription); - result.setImageURLs(imageURLs); - result.setVideoURLs(videoURLs); - result.setSponsorImageURLs(sponsorImageURLs); - result.setLogoImageURL(logoImageURL); result.setOfficialWebsiteURL(officialWebsiteURL); + result.setImages(images); + result.setVideos(videos); return result; } @@ -2614,7 +2624,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, - URL logoImageURL, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs) { + Iterable images, Iterable videos) { final Event event = eventsById.get(id); if (event == null) { throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist."); @@ -2637,10 +2647,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes } event.setLeaderboardGroups(leaderboardGroups); event.setOfficialWebsiteURL(officialWebsiteURL); - event.setLogoImageURL(logoImageURL); - event.setImageURLs(imageURLs); - event.setVideoURLs(videoURLs); - event.setSponsorImageURLs(sponsorImageURLs); + event.setImages(images); + event.setVideos(videos); // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch // to keep changes minimial mongoObjectFactory.storeEvent(event); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java index f69739ad775..6f3e88beb25 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java @@ -7,6 +7,8 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; /** * Creates an {@link Event} in the server, with a new venue and an empty course area list. @@ -23,14 +25,12 @@ public class CreateEvent extends AbstractEventOperation { private final boolean isPublic; private final String eventName; private final String eventDescription; - private final Iterable videoURLs; - private final Iterable imageURLs; - private final Iterable sponsorImageURLs; - private final URL logoImageURL; + private final Iterable images; + private final Iterable videos; private final URL officialWebsiteURL; public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, - boolean isPublic, UUID id, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) { + boolean isPublic, UUID id, URL officialWebsiteURL, Iterable images, Iterable videos) { super(id); this.eventName = eventName; this.eventDescription = eventDescription; @@ -38,11 +38,9 @@ public class CreateEvent extends AbstractEventOperation { this.endDate = endDate; this.venue = venue; this.isPublic = isPublic; - this.imageURLs = imageURLs; - this.videoURLs = videoURLs; - this.sponsorImageURLs = sponsorImageURLs; - this.logoImageURL = logoImageURL; this.officialWebsiteURL = officialWebsiteURL; + this.images = images; + this.videos = videos; } @Override @@ -64,7 +62,7 @@ public class CreateEvent extends AbstractEventOperation { @Override public Event internalApplyTo(RacingEventService toState) { return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic, - getId(), imageURLs, videoURLs, sponsorImageURLs, logoImageURL, officialWebsiteURL); + getId(), officialWebsiteURL, images, videos); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java index 4b839e6a943..3f40e452ace 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java @@ -6,6 +6,8 @@ import java.util.UUID; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventServiceOperation; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.media.ImageDescriptor; +import com.sap.sse.common.media.VideoDescriptor; public class UpdateEvent extends AbstractEventOperation { private static final long serialVersionUID = -8271559266421161532L; @@ -17,14 +19,12 @@ public class UpdateEvent extends AbstractEventOperation { private final String eventName; private final String eventDescription; private final URL officialWebsiteURL; - private final URL logoImageURL; - private final Iterable imageURLs; - private final Iterable videoURLs; - private final Iterable sponsorImageURLs; + private final Iterable images; + private final Iterable videos; public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, - String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL logoImageURL, - URL officialWebsiteURL, Iterable imageURLs, Iterable videoURLs, Iterable sponsorImageURLs) { + String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, + Iterable images, Iterable videos) { super(id); this.eventName = eventName; this.eventDescription = eventDescription; @@ -34,10 +34,8 @@ public class UpdateEvent extends AbstractEventOperation { this.isPublic = isPublic; this.leaderboardGroupIds = leaderboardGroupIds; this.officialWebsiteURL = officialWebsiteURL; - this.logoImageURL = logoImageURL; - this.imageURLs = imageURLs; - this.videoURLs = videoURLs; - this.sponsorImageURLs = sponsorImageURLs; + this.images = images; + this.videos = videos; } @Override @@ -55,7 +53,7 @@ public class UpdateEvent extends AbstractEventOperation { @Override public Void internalApplyTo(RacingEventService toState) { toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic, - leaderboardGroupIds, officialWebsiteURL, logoImageURL, imageURLs, videoURLs, sponsorImageURLs); + leaderboardGroupIds, officialWebsiteURL, images, videos); return null; } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDTO.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDTO.java deleted file mode 100644 index 6e0ff8ba07b..00000000000 --- a/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDTO.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.sap.sse.common.media; - -import java.io.Serializable; -import java.util.Date; -import java.util.HashSet; - -/** - * Common media data for media items - * - * @author pgtaboada - * - */ -public class AbstractMediaDTO implements Serializable, Comparable { - private static final long serialVersionUID = 1L; - - /** - * TODO: would "caption" be a better name? - */ - private String title; - - /** - * TODO: should we provide a subtitle? - */ - protected String subtitle; - - /** - * This information should be used for chronological ordering - * TODO: Must not be null. We need to find a way to - * migrate data! - */ - protected Date createdAtDate; - - /** - * TODO: initially I though of "author", but I am not sure if we need the author name. But we probably will need - * some place to store copyright information... - */ - protected String copyright; - - private MimeType mimeType; - - /** - * TODO: tags will give us some way to categorize medias. If we pass this information to the frontend, we need some - * nice idea to show display this data. for now it should be something restricted to the backend domain modell - */ - private HashSet tags = new HashSet(); - - /** - * URL pointing to the media data. - */ - private String sourceRef; - - /** - * Default constructor required for GWT serialization. - */ - protected AbstractMediaDTO() { - } - - /** - * Media item with minimal set of information - * - * @param sourceRef - * @param mimeType - */ - public AbstractMediaDTO(String sourceRef, MimeType mimeType, Date createdAtDate) { - this.mimeType = mimeType; - this.sourceRef = sourceRef; - this.createdAtDate = createdAtDate; - } - - public MimeType getMimeType() { - return mimeType; - } - - public String getSourceRef() { - return sourceRef; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public HashSet getTags() { - return tags; - } - - public String getSubtitle() { - return subtitle; - } - - public void setSubtitle(String subtitle) { - this.subtitle = subtitle; - } - - public Date getCreatedAtDate() { - return createdAtDate; - } - - public void setCreatedAtDate(Date createdAtDate) { - this.createdAtDate = createdAtDate; - } - - public String getCopyright() { - return copyright; - } - - public void setCopyright(String copyright) { - this.copyright = copyright; - } - - @Override - public int compareTo(AbstractMediaDTO o) { - return createdAtDate.compareTo(o.createdAtDate); - } -} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDescriptor.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDescriptor.java new file mode 100644 index 00000000000..5621e82192a --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDescriptor.java @@ -0,0 +1,136 @@ +package com.sap.sse.common.media; + +import java.io.Serializable; +import java.net.URL; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Set; + +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; + +/** + * Common media data for media items + * + * @author pgtaboada + * + */ +public abstract class AbstractMediaDescriptor implements MediaDescriptor, Serializable { + private static final long serialVersionUID = -6671425870632517274L; + + protected String title; + + protected String subtitle; + + protected TimePoint createdAtDate; + + protected String copyright; + + protected MimeType mimeType; + + protected Set tags = new LinkedHashSet(); + + protected URL url; + + protected Locale locale; + + /** + * Media item with minimal set of information + * @param url + * @param mimeType + */ + public AbstractMediaDescriptor(URL url, MimeType mimeType, TimePoint createdAtDate) { + this.mimeType = mimeType; + this.url = url; + this.createdAtDate = createdAtDate; + } + + @Override + public MimeType getMimeType() { + return mimeType; + } + + @Override + public URL getURL() { + return url; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public void setTitle(String title) { + this.title = title; + } + + @Override + public Set getTags() { + return tags; + } + + @Override + public void setTags(Iterable tags) { + this.tags.clear(); + if (tags != null) { + Util.addAll(tags, this.tags); + } + } + + + @Override + public boolean addTag(String tagName) { + return tags.add(tagName); + } + + @Override + public boolean removeTag(String tagName) { + return tags.remove(tagName); + } + + @Override + public String getSubtitle() { + return subtitle; + } + + @Override + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + @Override + public TimePoint getCreatedAtDate() { + return createdAtDate; + } + + @Override + public void setCreatedAtDate(TimePoint createdAtDate) { + this.createdAtDate = createdAtDate; + } + + @Override + public String getCopyright() { + return copyright; + } + + @Override + public void setCopyright(String copyright) { + this.copyright = copyright; + } + + @Override + public Locale getLocale() { + return locale; + } + + @Override + public void setLocale(Locale locale) { + this.locale = locale; + } + + @Override + public boolean hasTag(String tagName) { + return tags.contains(tagName); + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptor.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptor.java new file mode 100644 index 00000000000..67ace752668 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptor.java @@ -0,0 +1,14 @@ +package com.sap.sse.common.media; + +import com.sap.sse.common.Util.Pair; + +public interface ImageDescriptor extends MediaDescriptor { + void setSize(Pair size); + void setSize(Integer widthInPx, Integer heightInPx); + + Integer getWidthInPx(); + Integer getHeightInPx(); + + boolean hasSize(); + int getArea(); +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptorImpl.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptorImpl.java new file mode 100644 index 00000000000..48f7c10896b --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptorImpl.java @@ -0,0 +1,62 @@ +package com.sap.sse.common.media; + +import java.net.URL; + +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; + + +public class ImageDescriptorImpl extends AbstractMediaDescriptor implements ImageDescriptor { + private static final long serialVersionUID = -702731462768602331L; + + private Integer widthInPx; + private Integer heightInPx; + + /** + * @param imageURL + * @param size + */ + public ImageDescriptorImpl(URL imageURL, TimePoint createdAtDate) { + super(imageURL, MimeType.image, createdAtDate); + } + + @Override + public Integer getWidthInPx() { + return widthInPx; + } + + @Override + public Integer getHeightInPx() { + return heightInPx; + } + + @Override + public void setSize(Pair size) { + if (size != null) { + this.widthInPx = size.getA(); + this.heightInPx = size.getB(); + } else { + this.widthInPx = null; + this.heightInPx = null; + } + } + + @Override + public void setSize(Integer widthInPx, Integer heightInPx) { + this.widthInPx = widthInPx; + this.heightInPx = heightInPx; + } + + @Override + public boolean hasSize() { + return widthInPx != null && heightInPx != null; + } + + @Override + public int getArea() { + if(hasSize()) { + return widthInPx * heightInPx; + } + return 0; + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageMetadataDTO.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageMetadataDTO.java deleted file mode 100644 index ee72871c032..00000000000 --- a/java/com.sap.sse.common/src/com/sap/sse/common/media/ImageMetadataDTO.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.sap.sse.common.media; - -import java.util.Date; - - -public class ImageMetadataDTO extends AbstractMediaDTO { - - private static final long serialVersionUID = 1L; - - private int widthInPx; - private int heightInPx; - - protected ImageMetadataDTO() { - } - - /** - * TODO: should we use imagesize, or should we store height and width? For now, this is a convenience method... - * - * @param imageURL - * @param size - */ - public ImageMetadataDTO(String imageURL, ImageSize size, Date createdAtDate) { - super(imageURL, MimeType.image, createdAtDate); - this.widthInPx = size.getWidth(); - this.heightInPx = size.getHeight(); - } - - public ImageMetadataDTO(String imageURL, int heightInPx, int widthInPx, Date createdAtDate) { - super(imageURL, MimeType.image, createdAtDate); - this.widthInPx = widthInPx; - this.heightInPx = heightInPx; - } - - public int getWidthInPx() { - return widthInPx; - } - - public int getHeightInPx() { - return heightInPx; - } - -} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaDescriptor.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaDescriptor.java new file mode 100644 index 00000000000..6f777b7b6be --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaDescriptor.java @@ -0,0 +1,36 @@ +package com.sap.sse.common.media; + +import java.io.Serializable; +import java.net.URL; +import java.util.Locale; + +import com.sap.sse.common.TimePoint; + +/** + * A common media interface for all kinds of media like images or videos. + */ +public interface MediaDescriptor extends Serializable { + MimeType getMimeType(); + URL getURL(); + + String getTitle(); + void setTitle(String title); + + Iterable getTags(); + void setTags(Iterable tags); + boolean addTag(String tagName); + boolean removeTag(String tagName); + boolean hasTag(String tagName); + + String getSubtitle(); + void setSubtitle(String subtitle); + + TimePoint getCreatedAtDate(); + void setCreatedAtDate(TimePoint createdAtDate); + + String getCopyright(); + void setCopyright(String copyright); + + Locale getLocale(); + void setLocale(Locale locale); +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaTagConstants.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaTagConstants.java new file mode 100644 index 00000000000..0e1c1038685 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaTagConstants.java @@ -0,0 +1,12 @@ +package com.sap.sse.common.media; + +public interface MediaTagConstants { + static final String GALLERY = "Gallery"; + static final String STAGE = "Stage"; + static final String LOGO = "Logo"; + static final String TEASER = "Teaser"; + static final String SPONSOR = "Sponsor"; + static final String HIGHLIGHT = "Highlight"; + static final String LIVESTREAM = "Livestream"; + static final String FEATURED = "Featured"; +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaUtils.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaUtils.java new file mode 100644 index 00000000000..ca276c22ac6 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/MediaUtils.java @@ -0,0 +1,99 @@ +package com.sap.sse.common.media; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Iterator; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import com.sap.sse.common.Util.Pair; + +public class MediaUtils { + private static final Logger logger = Logger.getLogger(MediaUtils.class.getName()); + + /** + * Youtube regex detection from: + * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 + * at 15:33 + */ + private static final Pattern YOUTUBE_ID_REGEX = Pattern + .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$"); + + private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*"); + + private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$"); + + /** + * Detect mimetype for given url. + * + * @param url + * the source pointing to the video mediafile + * @return mimetype detected or MimeType.unknown + */ + public static MimeType detectMimeTypeFromUrl(String url) { + + if (YOUTUBE_ID_REGEX.matcher(url).matches()) { + return MimeType.youtube; + } else if (VIMEO_REGEX.matcher(url).matches()) { + return MimeType.vimeo; + } else if (MP4_REGEX.matcher(url).matches()) { + return MimeType.mp4; + } else { + return MimeType.unknown; + } + } + + public static Pair getImageDimensions(URL imageURL) { + Future> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); + try { + return imageSizeCalculator.get(); + } catch (Exception e) { + return null; + } + } + + private static final ExecutorService executor = Executors.newCachedThreadPool(); + + private static Future> getOrCreateImageSizeCalculator(final URL imageURL) { + Future> imageSizeFetcher = executor.submit(new Callable>() { + @Override + public Pair call() throws IOException { + Pair result = null; + ImageInputStream in = null; + try { + URLConnection conn = imageURL.openConnection(); + in = ImageIO.createImageInputStream(conn.getInputStream()); + final Iterator readers = ImageIO.getImageReaders(in); + if (readers.hasNext()) { + ImageReader reader = readers.next(); + try { + reader.setInput(in); + result = new Pair<>(reader.getWidth(0), reader.getHeight(0)); + } finally { + reader.dispose(); + } + } + } catch (IOException ioe) { + logger.log(Level.SEVERE, "Stale image URL: "+imageURL, ioe); + throw ioe; + } finally { + if (in != null) { + in.close(); + } + } + return result; + } + }); + return imageSizeFetcher; + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptor.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptor.java new file mode 100644 index 00000000000..1f21c990afa --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptor.java @@ -0,0 +1,11 @@ +package com.sap.sse.common.media; + +import java.net.URL; + +public interface VideoDescriptor extends MediaDescriptor { + Integer getLengthInSeconds(); + void setLengthInSeconds(Integer lengthInSeconds); + + URL getThumbnailURL(); + void setThumbnailURL(URL thumbnailURL); +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptorImpl.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptorImpl.java new file mode 100644 index 00000000000..ce8d112252e --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptorImpl.java @@ -0,0 +1,40 @@ +package com.sap.sse.common.media; + +import java.net.URL; + +import com.sap.sse.common.TimePoint; + +public class VideoDescriptorImpl extends AbstractMediaDescriptor implements VideoDescriptor { + private static final long serialVersionUID = 2651747912466590862L; + + private Integer lengthInSeconds; + + /** + * URL to thumbnail image. This information works as override for youtube videos or as missing thumbnail + * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64 + * encoded image. + */ + private URL thumbnailURL; + + public VideoDescriptorImpl(URL url, MimeType mimeType, TimePoint createdAtDate) { + super(url, mimeType, createdAtDate); + } + + @Override + public Integer getLengthInSeconds() { + return lengthInSeconds; + } + + public void setLengthInSeconds(Integer lengthInSeconds) { + this.lengthInSeconds = lengthInSeconds; + } + + @Override + public URL getThumbnailURL() { + return thumbnailURL; + } + + public void setThumbnailURL(URL thumbnailURL) { + this.thumbnailURL = thumbnailURL; + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoMetadataDTO.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoMetadataDTO.java deleted file mode 100644 index 72207aefd00..00000000000 --- a/java/com.sap.sse.common/src/com/sap/sse/common/media/VideoMetadataDTO.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.sap.sse.common.media; - -import java.util.Date; - -public class VideoMetadataDTO extends AbstractMediaDTO { - - private static final long serialVersionUID = 1L; - - /** - * TODO: Do we really need video length? Should it be mandatory? - */ - private int lengthInSeconds; - - /** - * URL Reference to thumbnail image. This information works as override for youtube videos or as missing thumbnail - * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64 - * encoded image. - */ - private String thumbnailRef; - - - protected VideoMetadataDTO() { - } - - - public VideoMetadataDTO(String url, MimeType mimeType, Date createdAtDate) { - super(url, mimeType, createdAtDate); - } - - public int getLengthInSeconds() { - return lengthInSeconds; - } - - public void setLengthInSeconds(int lengthInSeconds) { - this.lengthInSeconds = lengthInSeconds; - } - - public void setThumbnailRef(String thumbnailRef) { - this.thumbnailRef = thumbnailRef; - } - - public String getThumbnailRef() { - return thumbnailRef; - } -} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/WithImages.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithImages.java new file mode 100644 index 00000000000..ade9f8c6791 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithImages.java @@ -0,0 +1,25 @@ +package com.sap.sse.common.media; + +public interface WithImages { + /** + * Returns a non-null live but unmodifiable collection of image resources that can be + * used to represent the event, e.g., on a web page. + * + * @return a non-null value which may be empty + */ + Iterable getImages(); + + /** + * Replaces the {@link #getImages() current contents of the image sequence by the images in + * images. + * + * @param images + * if null, the internal sequence of images is cleared but remains valid (non- + * null) + */ + void setImages(Iterable images); + + void addImage(ImageDescriptor image); + + void removeImage(ImageDescriptor image); +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java new file mode 100644 index 00000000000..ac433dc3bbf --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java @@ -0,0 +1,12 @@ +package com.sap.sse.common.media; + +import java.util.List; + +public interface WithMedia extends WithImages, WithVideos { + ImageDescriptor findImageWithTag(String tagName); + List findImagesWithTag(String tagName); + boolean hasImageWithTag(String tagName); + + VideoDescriptor findVideoWithTag(String tagName); + List findVideosWithTag(String tagName); +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/media/WithVideos.java b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithVideos.java new file mode 100644 index 00000000000..5706f49757b --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithVideos.java @@ -0,0 +1,26 @@ +package com.sap.sse.common.media; + +public interface WithVideos { + + /** + * Returns a non-null live but unmodifiable collection of video resources that can be + * used to represent the event, e.g., on a web page. + * + * @return a non-null value which may be empty + */ + Iterable getVideos(); + + /** + * Replaces the {@link #getVideos() current contents of the video sequence by the videos in + * videos. + * + * @param videos + * if null, the internal sequence of videos is cleared but remains valid (non- + * null) + */ + void setVideos(Iterable videos); + + void addVideo(VideoDescriptor video); + + void removeVideo(VideoDescriptor video); +} diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 0f5f9d68ece..bdcd673bb77 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -5,6 +5,9 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; @@ -15,6 +18,7 @@ import com.google.gwt.user.client.ui.FileUpload; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; +import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; @@ -29,7 +33,7 @@ import com.google.gwt.user.client.ui.VerticalPanel; * @author Axel Uhl (d043530) * */ -public class URLFieldWithFileUpload extends Composite { +public class URLFieldWithFileUpload extends Composite implements HasValue { private final TextBox urlTextBox; private final FileUpload fileUploadField; @@ -37,6 +41,8 @@ public class URLFieldWithFileUpload extends Composite { private String uri; private final Button removeButton; + + private boolean valueChangeHandlerInitialized = false; public URLFieldWithFileUpload(final StringMessages stringMessages) { final VerticalPanel mainPanel = new VerticalPanel(); @@ -139,4 +145,43 @@ public class URLFieldWithFileUpload extends Composite { removeButton.setEnabled(true); } } + + public void setFocus(boolean focused) { + urlTextBox.setFocus(focused); + } + + private HandlerRegistration addChangeHandler(ChangeHandler handler) { + return addDomHandler(handler, ChangeEvent.getType()); + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + if (!valueChangeHandlerInitialized) { + valueChangeHandlerInitialized = true; + addChangeHandler(new ChangeHandler() { + public void onChange(ChangeEvent event) { + ValueChangeEvent.fire(URLFieldWithFileUpload.this, getValue()); + } + }); + } + return addHandler(handler, ValueChangeEvent.getType()); + } + + @Override + public String getValue() { + return getURL(); + } + + @Override + public void setValue(String value) { + setURL(value); + } + + @Override + public void setValue(String value, boolean fireEvents) { + setValue(value); + if (fireEvents) { + ValueChangeEvent.fire(this, value); + } + } } diff --git a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF index 787134236e9..9f582333b47 100644 --- a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF @@ -29,6 +29,7 @@ Export-Package: com.google.gwt.user.client.rpc.core.java.util, com.sap.sse.gwt.client.dialog, com.sap.sse.gwt.client.event, com.sap.sse.gwt.client.filestorage, + com.sap.sse.gwt.client.media, com.sap.sse.gwt.client.mvp, com.sap.sse.gwt.client.panels, com.sap.sse.gwt.client.player, diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/carousel/ImageCarousel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/carousel/ImageCarousel.java index 3f627ca83b6..3e8192ca6ed 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/carousel/ImageCarousel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/carousel/ImageCarousel.java @@ -15,7 +15,7 @@ import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.ui.Widget; -import com.sap.sse.common.media.ImageMetadataDTO; +import com.sap.sse.gwt.client.media.ImageDTO; /** * Image carousel that uses the open source "slick carousel". @@ -53,7 +53,7 @@ import com.sap.sse.common.media.ImageMetadataDTO; * * Created by pgtaboada on 10.11.14. */ -public class ImageCarousel extends Widget { +public class ImageCarousel extends Widget { private static SlickSliderUiBinder ourUiBinder = GWT.create(SlickSliderUiBinder.class); @@ -316,4 +316,4 @@ public class ImageCarousel extends Widget { public void show(TYPE selectedImage, List imageList); } -} \ No newline at end of file +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/AbstractMediaDTO.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/AbstractMediaDTO.java new file mode 100644 index 00000000000..0c9d8fb67b6 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/AbstractMediaDTO.java @@ -0,0 +1,106 @@ +package com.sap.sse.gwt.client.media; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sse.common.media.MimeType; + +public abstract class AbstractMediaDTO implements IsSerializable { + protected String title; + + protected String subtitle; + + protected Date createdAtDate; + + protected String copyright; + + protected MimeType mimeType; + + protected List tags = new ArrayList(); + + protected String sourceRef; + + protected String locale; + + /** for GWT */ + protected AbstractMediaDTO() { + } + + public AbstractMediaDTO(String sourceRef, MimeType mimeType, Date createdAtDate) { + this.sourceRef = sourceRef; + this.mimeType = mimeType; + this.createdAtDate = createdAtDate; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public Date getCreatedAtDate() { + return createdAtDate; + } + + public void setCreatedAtDate(Date createdAtDate) { + this.createdAtDate = createdAtDate; + } + + public String getCopyright() { + return copyright; + } + + public void setCopyright(String copyright) { + this.copyright = copyright; + } + + public MimeType getMimeType() { + return mimeType; + } + + public void setMimeType(MimeType mimeType) { + this.mimeType = mimeType; + } + + public List getTags() { + return tags; + } + + public boolean hasTag(String tagName) { + return tags.contains(tagName); + } + + public void setTags(Iterable tags) { + for(String tag: tags) { + this.tags.add(tag); + } + } + + public String getSourceRef() { + return sourceRef; + } + + public void setSourceRef(String sourceRef) { + this.sourceRef = sourceRef; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/ImageDTO.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/ImageDTO.java new file mode 100644 index 00000000000..7e299d743db --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/ImageDTO.java @@ -0,0 +1,33 @@ +package com.sap.sse.gwt.client.media; + +import java.util.Date; + +import com.sap.sse.common.media.MimeType; + +public class ImageDTO extends AbstractMediaDTO { + + /** for GWT */ + protected ImageDTO() { + } + + /** can be null if not known */ + private Integer widthInPx; + private Integer heightInPx; + + public ImageDTO(String imageRef, Date createdAtDate) { + super(imageRef, MimeType.image, createdAtDate); + } + + public Integer getWidthInPx() { + return widthInPx; + } + + public Integer getHeightInPx() { + return heightInPx; + } + + public void setSizeInPx(Integer widthInPx, Integer heightInPx) { + this.widthInPx = widthInPx; + this.heightInPx = heightInPx; + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/VideoDTO.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/VideoDTO.java new file mode 100644 index 00000000000..53fbd533cf1 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/media/VideoDTO.java @@ -0,0 +1,34 @@ +package com.sap.sse.gwt.client.media; + +import java.util.Date; + +import com.sap.sse.common.media.MimeType; + +public class VideoDTO extends AbstractMediaDTO { + private Integer lengthInSeconds; + private String thumbnailRef; + + /** for GWT */ + protected VideoDTO() { + } + + public VideoDTO(String videoRef, MimeType mimeType, Date createdAtDate) { + super(videoRef, mimeType, createdAtDate); + } + + public Integer getLengthInSeconds() { + return lengthInSeconds; + } + + public void setLengthInSeconds(Integer lengthInSeconds) { + this.lengthInSeconds = lengthInSeconds; + } + + public String getThumbnailRef() { + return thumbnailRef; + } + + public void setThumbnailRef(String thumbnailRef) { + this.thumbnailRef = thumbnailRef; + } +} diff --git a/java/pom.xml b/java/pom.xml index 220fed91e88..39a382ba66f 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -50,6 +50,7 @@ com.sap.sailing.domain.swisstimingadapter com.sap.sailing.domain.swisstimingadapter.persistence com.sap.sailing.domain.swisstimingreplayadapter + com.sap.sailing.news com.sap.sailing.declination com.sap.sailing.udpconnector com.sap.sailing.expeditionconnector diff --git a/mobile/com.sap.sailing.android.shared/res/values/themes.xml b/mobile/com.sap.sailing.android.shared/res/values/themes.xml index 2eefe3b7581..918e28f7383 100644 --- a/mobile/com.sap.sailing.android.shared/res/values/themes.xml +++ b/mobile/com.sap.sailing.android.shared/res/values/themes.xml @@ -5,11 +5,11 @@ false - - + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/build.gradle b/mobile/com.sap.sailing.racecommittee.app/build.gradle index e6d4fd72c22..f1a47789178 100644 --- a/mobile/com.sap.sailing.racecommittee.app/build.gradle +++ b/mobile/com.sap.sailing.racecommittee.app/build.gradle @@ -46,12 +46,12 @@ android { versionNameSuffix ".debug" signingConfig signingConfigs.stg_debug - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-project.txt", "../proguard-mobile.txt" } release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-project.txt", "../proguard-mobile.txt" } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index 6a957f11c71..bab2771fb3b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -5,14 +5,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import android.app.ActionBar; -import android.app.ActionBar.OnNavigationListener; import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.util.Log; import android.view.Window; import android.widget.ArrayAdapter; @@ -46,7 +45,7 @@ public class RacingActivity extends SessionActivity implements RaceInfoListener private static final int RacesLoaderId = 0; - private class FilterModeSelectionBinder implements OnNavigationListener { + private class FilterModeSelectionBinder implements ActionBar.OnNavigationListener { private ManagedRaceListFragment targetList; private ActionBar actionBar; @@ -206,7 +205,7 @@ public class RacingActivity extends SessionActivity implements RaceInfoListener } private void setupActionBar(CourseArea courseArea) { - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); AbstractLogEventAuthor author = preferences.getAuthor(); String title = String.format(getString(R.string.racingview_header), courseArea.getName()); title += " (" + author.getName() + ")"; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SessionActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SessionActivity.java index e9f5ccb7593..ca4e38ddfab 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SessionActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SessionActivity.java @@ -1,12 +1,12 @@ package com.sap.sailing.racecommittee.app.ui.activities; -import android.app.ActionBar; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.support.v7.app.ActionBar; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.android.shared.logging.ExLog; @@ -19,8 +19,10 @@ public abstract class SessionActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ActionBar actionBar = getActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } } @Override diff --git a/wiki/505-worlds-kiel-2014.md b/wiki/505-worlds-kiel-2014.md index 0922c17e22b..3593fe11b09 100644 --- a/wiki/505-worlds-kiel-2014.md +++ b/wiki/505-worlds-kiel-2014.md @@ -33,16 +33,16 @@ Event Page (newhome): http://505worlds2014.sapsailing.com/gwt/Home.html#EventPla ## Replication to Cube Server -At this event we operate a replica on our SAP Sail Cube server. The server is physically located behind the ground floor desk. With one of its network adapters it is hooked up to the lounge network, using a radio link from realTV. The second network adapter (Mainboard) is connected to the cube's network, allowing access from studio PCs to the cube server. This adapter is also the default route, providing the cube server, cube showroom and realTV with Internet access. As a backup it is possible to switch the default route to the launch. See `ip route show`. +At this event we operate a replica on our SAP Sail Cube server. The server is physically located behind the ground floor desk. With one of its network adapters it is hooked up to the lounge network, using a radio link from realTV. The second network adapter (Mainboard) is connected to the cube's network, allowing access from studio PCs to the cube server. This adapter is also the default route, providing the cube server, cube showroom and realTV with Internet access. As a backup it is possible to switch the default route to the lounge. See `ip route show`. -The server runs a isc-dhcp-server, which requires all connected routers to NOT deliver a dhcp server. All required subnet config is in `/etc/dhcp/dhcpd.conf`. We have a /22 subnet for cube and a /24 subnet for the launch. BeTomorrow get static ips assigned by the dhcp through MAC-addresses, because their server always needs the same ip address. Futhermore the dhcp deploys our own DNS server here. For further description of the DNS setup read further. +The server runs a isc-dhcp-server, which requires all connected routers to NOT deliver a dhcp server. All required subnet config is in `/etc/dhcp/dhcpd.conf`. We have a /22 subnet for cube and a /24 subnet for the lounge. BeTomorrow get static ips assigned by the dhcp through MAC-addresses, because their server always needs the same ip address. Futhermore the dhcp deploys our own DNS server here. For further description of the DNS setup read further. -The server also runs a bind9 DNS server which is set as the primary DNS for all lounge and cube PCs. All DNS requests are forwarded to a public DNS server except for requests for 505worlds2014.sapsailing.com for which the local DNS server returns the IP address of the cube server for the NIC through which the DNS request was received. For that two DNS zones are introduced, which match by the incoming IP range request. See `/etc/bind/named.conf.zones-cube` and `/etc/bind/named.conf.zones-launch` for definition of zone and forwarding. Take a look at `/etc/bind/zones/505sapsailing.cube.db` and `/etc/bind/zones/505sapsailing.sailing.db` for the nameserver entries for the zone. The result is, that if a request comes from the launch network, the domain is resolved to the ip the cube server has in this subnet and also if the request comes from the cube, it returns the ip in the subnet of the cube. -As described in bug2204 wind data and API are not served through the replica, so BeTomorrow has to go to our main server. For that i introduced a subsubdomain `btmw.505worlds2014.sapsailing.com` which points to `sapsailing.com` and answers with our main active server of the event. +The server also runs a bind9 DNS server which is set as the primary DNS for all lounge and cube PCs. All DNS requests are forwarded to a public DNS server except for requests for 505worlds2014.sapsailing.com for which the local DNS server returns the IP address of the cube server for the NIC through which the DNS request was received. For that two DNS zones are introduced, which match by the incoming IP range request. See `/etc/bind/named.conf.zones-cube` and `/etc/bind/named.conf.zones-launch` for definition of zone and forwarding. Take a look at `/etc/bind/zones/505sapsailing.cube.db` and `/etc/bind/zones/505sapsailing.sailing.db` for the nameserver entries for the zone. The result is, that if a request comes from the lounge network, the domain is resolved to the ip the cube server has in this subnet and also if the request comes from the cube, it returns the ip in the subnet of the cube. +As described in bug2204 wind data and API are not served through the replica, so BeTomorrow has to go to our main server. For that I introduced a subsubdomain `btmw.505worlds2014.sapsailing.com` which points to `sapsailing.com` and answers with our main active server of the event. On the cube server we run a replica server instance as user `sailing` under the usual directory set-up (`/home/sailing/servers/server`). The `env.sh` file uses a configuration that lets the server start replicating when launched. The two possible master servers (A / B) are specified in two lines defining `REPLICATE_MASTER_SERVLET_HOST`, one is commented, the other one active. -When switching from the A server to the B server or vice versa, the replica needs to be re-configured. Before stopping it, connected clients need to be re-directed to the master when they hit 505worlds2014.sapsailing.com. This is achieved by re-configuring the Apache server running on the replica, as user `root`, editing `/etc/apache2/sites-enabled/001-events.conf` and activating the line for `505worlds2014.sapsailing.com` that points to the IP address of the new active server, then invoking `/etc/init.d/apache2 reload`. Once this is done, the running replica should cleanly stop replication (using the `Replication` tab in the AdminConsole), then stopping the instance (as user `sailing` run `/home/sailing/servers/server/stop`). Then, in `env.sh` the `REPLICATE_MASTER_SERVLET_HOST` variable needs to be set to point to the new master. After that, the replica Java instance can be launched using `/home/sailing/servers/server/start`). Before re-activating it is important that the rabbit initial load is done. So please watch eather `/home/sailing/servers/server/logs/sailing0.log.0` or the Rabbit Webinterface on `http://54.246.250.138:15672/#/queues`. Please deleted eventually displayed old queues (be sure not to remove the correct replicating queue!). Re-configure Apache again by editing `/etc/apache2/sites-enabled/001-events.conf` again, re-activating the record that lets 505worlds2014.sapsailing.com point to the replica. From this point on, clients in the lounge and the Cube will instantly be directed to the cube server again when hitting the replica server's Apache with a URL starting with `505worlds2014.sapsailing.com`. You can check this by example checking the load with `htop` or `apachetop`. +When switching from the A server to the B server or vice versa, the replica needs to be re-configured. Before stopping it, connected clients need to be re-directed to the master when they hit 505worlds2014.sapsailing.com. This is achieved by re-configuring the Apache server running on the replica, as user `root`, editing `/etc/apache2/sites-enabled/001-events.conf` and activating the line for `505worlds2014.sapsailing.com` that points to the IP address of the new active server, then invoking `/etc/init.d/apache2 reload`. Once this is done, the running replica should cleanly stop replication (using the `Replication` tab in the AdminConsole), then stopping the instance (as user `sailing` run `/home/sailing/servers/server/stop`). Then, in `env.sh` the `REPLICATE_MASTER_SERVLET_HOST` variable needs to be set to point to the new master. After that, the replica Java instance can be launched using `/home/sailing/servers/server/start`). Before re-activating it is important that the rabbit initial load is done. So please watch either `/home/sailing/servers/server/logs/sailing0.log.0` or the Rabbit Webinterface on `http://54.246.250.138:15672/#/queues`. Please deleted eventually displayed old queues (be sure not to remove the correct replicating queue!). Re-configure Apache again by editing `/etc/apache2/sites-enabled/001-events.conf` again, re-activating the record that lets 505worlds2014.sapsailing.com point to the replica. From this point on, clients in the lounge and the Cube will instantly be directed to the cube server again when hitting the replica server's Apache with a URL starting with `505worlds2014.sapsailing.com`. You can check this by example checking the load with `htop` or `apachetop`. ## Switching Servers diff --git a/wiki/event-network-with-replica.md b/wiki/event-network-with-replica.md new file mode 100644 index 00000000000..84a63d06147 --- /dev/null +++ b/wiki/event-network-with-replica.md @@ -0,0 +1,101 @@ +# Setup local area network for an Sailing Event + +## Hardware +- Cisco Meraki MX60W +- Sail Cube Server (2HE, Xeon E3-12XXv2, 32GB RAM, 120GB SSD) + +## Software +- Setup router through https://dashboard.meraki.com (get access from Axel, Frank or Steffen) +- Server OS: Debian Wheezy + +## Scenarios +### Standard FritzBox / ... network +If you have a given network setup (without own router) I would suggest the following solution: +The server has three network adapters to connect to multiple networks at the same time (e.g. Cube and Lounge,..). You could simply integrate the server into all networks and set up a static ip on each of the networks for the server. then simply configure the sailing instance to listen on all interfaces and that should be it. You could additionally use the replica's DNS server to also overwrite `event.sapsailing.com` URL's but therefore you need to tell each router of the network to server the replica as DNS server to other LAN clients. + +### Single WAN uplink (Meraki) +- Create 3 VLAN's with (VLAN-ID 10=>ADMIN,20=>CUBE,30=>GUEST) +- Link VLAN's with physical ports on the router +- Create 3 WLAN SSID's and link each with a VLAN +- Create a splash page for guest wifi (Security appliance->Access control and Splash page) +- Create traffic shaping rules / policies to limit bandwidth on each of the VLAN's + * give Cube network about 1600Kb/s down and 500Kb/s up + * make Admin network unlimited + * set guest network limit to 200Kb/s up and down +- Set up DHCP section to your desire + * e.g. deploy replica server IP as DNS server for Cube network + * give importand servers (replica, TriCaster, beTomorrow server a static ip lease in Cube network) => MAC's needed +- Create firewall rules to restrict access to special services (e.g. ssh on replica server) +- If needed create a Client VPN Server and choose a free subnet for it (L2TP over IPSec) + +### Dual WAN uplink (Meraki) +`not yet tested, but possible` + +### Dual WAN uplink (Meraki) with mobile LTE +`not yet tested, but possible` + +## Prepare Replica Server +### Physically +The server is localted behind the ground floor desk in the 19" cabinet. The router could be set up in the same place, so that a physical link can be established from server to router (short ways for better latency). + +### Services & configs +- runs `bind9`, `apache2`, `isc-dhcp-server` (default: off), 2x `sailing instances` +- apache2: `/etc/apache2/sites-enabled/{001-event.conf, 000-main.conf, 000-macros.conf}` +- bind9: + - `/etc/bind/named.conf.*` for client matching and zone forwarding + - `/etc/bind/zones/*` for the different nameserver zones +- isc-dhcp-server + - default `chkconfig off` + - only needed if you want the replica to operate as kind of router (e.g. turn off dhcp and dns from FritzBox) + - prepared configs: `/etc/dhcp/dhcpd.conf` + - 1x /22 network for cube (with static MAC/IP mappings for some clients) + - 1x /24 network for SAP-lounge setup +- user: sailing => `/home/sailing/servers/{servers,test}` +- Maven: ready to build code under `/home/sailing/code/` + +### Hostnames +- the replica should be available as `sailing-demo.sapsailing.com` from every local network +- the replica should be available as `event.sapsailing.com` from the desired network where replica should operate instead of EC2 instance + +### Set up replication with dns and apache + +The server runs a bind9 DNS server which is set as the primary DNS for the meraki networks which should server the replica instead of the event EC2 instance. + +Other then the event EC2 instance DNS requests are forwarded to a public DNS server (8.8.8.8). For the event server which should be locally replaced by the replica, e.g. 505worlds2014.sapsailing.com, the local DNS server returns the IP address of the cube server for the NIC through which the DNS request was received (this is received through client-matching). + +For that three DNS zones are introduced, which match by the incoming IP range request (choose what you need here, depending on the local setup!). See `/etc/bind/named.conf.zones-cube`, `/etc/bind/named.conf.zones-launch` and `/etc/bind/named.conf.zones-admin` for definition of zones and forwarding. + +Take a look at `/etc/bind/zones/kwsapsailing.cube.db` and `/etc/bind/zones/kwsapsailing.admin.db` for the nameserver entries for the zone. The result is, that if a request comes from the lounge network, the domain is resolved to the ip the cube server has in this subnet and also if the request comes from the cube, it returns the ip in the subnet of the cube. + +It depends on the network setup if you need this client matching or not. The Meraki supports inter-VLAN Routing, which leads to no need to multiple IP adresses for the replica server. Instead put the replica server into one of the networks and then restrict access through firewall rules. Furthermore just setup a single dns zone (e.g. `/etc/bind/named.conf.zones-cube` in which everything is maintained. + +As described in bug2204 wind data and API are not served through the replica, so BeTomorrow has to go to our main server. For that I introduced a subsubdomain `btmw.event.sapsailing.com` which points to `sapsailing.com` and answers with our main active server of the event. This has to be clarified if we need this or if the replica operates correctly and servers everything as RESTlets.. + +On the cube server we run a replica server instance as user `sailing` under the usual directory set-up (`/home/sailing/servers/server`). The `env.sh` file uses a configuration that lets the server start replicating when launched. The two possible master servers (A / B) are specified in two lines defining `REPLICATE_MASTER_SERVLET_HOST`, one is commented, the other one active. + +I have also prepared a second sailing instance as user `sailing` under the usual directory set-up (`/home/sailing/servers/test`) which could also be used to switch between to local instances if needed. + +When switching from the A server to the B server or vice versa, the replica needs to be re-configured. Before stopping it, connected clients need to be re-directed to the master when they hit `event.sapsailing.com`. This is achieved by re-configuring the Apache server running on the replica, as user `root`, editing `/etc/apache2/sites-enabled/001-events.conf` and activating the line for `event.sapsailing.com` that points to the IP address of the new active server, then invoking `/etc/init.d/apache2 reload`. Once this is done, the running replica should cleanly stop replication (using the `Replication` tab in the AdminConsole), then stopping the instance (as user `sailing` run `/home/sailing/servers/server/stop`). Then, in `env.sh` the `REPLICATE_MASTER_SERVLET_HOST` variable needs to be set to point to the new master. After that, the replica Java instance can be launched using `/home/sailing/servers/server/start`). Before re-activating it is important that the rabbit initial load is done. So please watch either `/home/sailing/servers/server/logs/sailing0.log.0` or the Rabbit Webinterface on `http://RABBIT-MQ-SERVER:15672/#/queues`. +Please deleted eventually displayed old queues (be sure not to remove the correct replicating queue!). Re-configure Apache again by editing `/etc/apache2/sites-enabled/001-events.conf` again, re-activating the record that lets event.sapsailing.com point to the replica. From this point on, clients in the Lounge, Cube and eventual other networks will instantly be directed (without connection loss!) to the cube server again when hitting the replica server's Apache with a URL starting with `event.sapsailing.com`. + +### Monitor the server +- You can check the load with `htop` +- You can check the accesses to apache with `apachetop` or you go to `http://sailing-demo.sapsailing.com/server-status`page to display the http status +- You can check the system temperature (depending on local environment may be important!) with `sensors` command + +### Switching Servers + +For maximum availability we normally have at least two EC2 servers reserved for a bigger event (e.g. building new releases in parallel and then switching, or with ELB). We can prepare the other server by installing a release, either by using the `refreshInstance.sh` script or by using `tmux attach` to go to a console and fetching from git and building and installing there. When done, fire up the server and load all races required. Test the server reasonably well. + +Then switch the cube server replica to point to the new server. This process is described above. + +Then, log on as `root` on `sapsailing.com`, go to `/etc/httpd/conf.d` and edit `001-events.conf` to update the event-related URLs. By convention, the inactive server URLs are suffixed with `-a` or `-b` name, respectively. When done editing, run `/etc/init.d/httpd reload` which will update the Apache configuration so that from that point on requests to `event.sapsailing.com` will go to the new server. Convince yourself by looking at the logs under `/home/sailing/servers/server/logs/sailing0.log.0` that the correct server is now active. Afterwards, stop the old server's Java instance. + +### Local builds on replica +- verify on which branch you are inside `/home/sailing/code/` with `git status` +- do a `git fetch` / `git pull` +- eventually switch branches for a new build with `git co branch` +- eventually merge other stuff to that branch `git merge origin/branch` +- start doing a maven build with `./configuration/buildAndUpdateProduct.sh -t build` +- after build is **SUCCESSFULLY FINISHED** install build with `./configuration/buildAndUpdateProduct.sh -s server install` +- now you are able to stop and start sailing instance (be careful with correct replication handling -> see above) diff --git a/wiki/kieler-woche-2015.md b/wiki/kieler-woche-2015.md new file mode 100644 index 00000000000..54afdcc9d74 --- /dev/null +++ b/wiki/kieler-woche-2015.md @@ -0,0 +1,40 @@ +# Kieler Woche 2015 + +## Server Landscape + +We run four servers for the event: +- on EC2: **Master**, **Replika 1** and **2** with an **ELB** in front, +- locally: **Cube-Server** + +For fast and available switching. The DB they use is called `KW2015`, and so is the replication channel. The git branch we use to merge the features we want to deploy to the event servers is called `kielerwoche2015` and the git tag used to build a release for it is called `XXX-release`. See http://hudson.sapsailing.com/job/SAPSailingAnalytics-XXX/ and http://hudson.sapsailing.com/job/SAPSailingAnalytics-XXX-release/, respectively, for their Hudson jobs. + +### Master Server +- Admin Console at http://ec2-54-154-151-10.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html +- External IP: 54.154.151.10 +- Internal IP: 172.31.24.187 + +### Replika 1 server +- Admin Console at http://ec2-54-72-226-124.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html +- External IP: 54.72.226.124 +- Internal IP: 172.31.20.11 + +### Replika 2 server +- Admin Console at http://ec2-54-154-175-180.eu-west-1.compute.amazonaws.com:8888/gwt/AdminConsole.html +- External IP: 54.154.175.180 +- Internal IP: 172.31.20.12 + +## TracTrac URL +- http://xxx.traclive.dk/events/event_xxx/jsonservice.php + +## Replication +- RabbitMQ server for replication: 52.16.112.6 (Web: http://52.16.112.6:15672/) +- Replication Exchange Name: KW2015 + +## Other links +- Regatta Overview: http://kielerwoche2015ro.sapsailing.com +- Manage2Sail Event URL: http://manage2sail.com/api/public/links/event/ID?accesstoken=TOKEN&mediaType=json + +## Local network setup +basically see [[Set up local network with replication server|wiki/event-network-with-replica]] operating with a Meraki and a local replica server using 1 physical WAN interface on the Meraki. Meraki network config for this event can be found under [[Meraki Dashboard|https://n142.meraki.com/KiWo-TraWo/n/Rzu9tdoc]] + +- Accessing replica server from `sailing-demo.sapsailing.com`via ssh or http from `ADMIN` network \ No newline at end of file