diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index e6230b9950b..3df14c0b671 100755 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 27c06e0ee36..39971c2fd18 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 @@ -7,6 +7,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 { @@ -22,9 +23,9 @@ public class EventLogo extends Image { sailingServiceAsync.getEventById(eventUUID, true, new AsyncCallback() { @Override public void onSuccess(final EventDTO event) { - 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); } 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..2a1101b145b --- 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,2187 @@ -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.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 hasMigratedSomething = loadLegacyImageAndVideoURLs(event, object); + if(hasMigratedSomething) { + new MongoObjectFactoryImpl(database).storeEvent(event); + } + result.add(event); + } + } 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..359b0818df1 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 @@ -3,7 +3,10 @@ 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; @@ -11,6 +14,16 @@ 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; @@ -22,11 +35,9 @@ public abstract class EventBaseImpl implements EventBase { 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; + 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); @@ -43,21 +54,17 @@ public abstract class EventBaseImpl implements EventBase { this.endDate = endDate; this.venue = venue; this.isPublic = isPublic; - this.imageURLs = new ConcurrentLinkedQueue(); - this.videoURLs = new ConcurrentLinkedQueue(); - this.sponsorImageURLs = new ConcurrentLinkedQueue(); + this.images = new ConcurrentLinkedQueue(); + this.videos = new ConcurrentLinkedQueue(); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); - if (imageURLs == null) { - imageURLs = new ConcurrentLinkedQueue(); + if (images == null) { + images = new ConcurrentLinkedQueue(); } - if (videoURLs == null) { - videoURLs = new ConcurrentLinkedQueue(); - } - if (sponsorImageURLs == null) { - sponsorImageURLs = new ConcurrentLinkedQueue(); + if (videos == null) { + videos = new ConcurrentLinkedQueue(); } } @@ -128,87 +135,41 @@ public abstract class EventBaseImpl implements EventBase { @Override public Iterable getImageURLs() { - return Collections.unmodifiableCollection(imageURLs); + 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 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); + List result = new ArrayList(); + for(VideoDescriptor video: videos) { + result.add(video.getURL()); } + return result; } @Override public Iterable getSponsorImageURLs() { - return Collections.unmodifiableCollection(sponsorImageURLs); + List result = new ArrayList(); + for(MediaDescriptor media : findMediaWithTag(images, MediaTagConstants.SPONSOR)) { + result.add(media.getURL()); + } + return result; } - @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; + URL result = null; + ImageDescriptor media = findImageWithTag(MediaTagConstants.LOGO); + if(media != null) { + result = media.getURL(); + } + return result; } @Override @@ -221,4 +182,165 @@ public abstract class EventBaseImpl implements EventBase { 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 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.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/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..fb68ecc06ca 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: 67.8em 50.46666666666667em; + background-position: center -0.933333333333333em; + 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..99bfb959893 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,7 +103,7 @@ 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()); 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/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..c32d87b69ae 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,9 +14,6 @@ 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; @@ -42,8 +39,8 @@ import com.sap.sailing.gwt.ui.shared.fakeseries.EventSeriesViewDTO.EventSeriesSt 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; @@ -57,9 +54,11 @@ 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.gwt.client.media.ImageDTO; import com.sap.sse.util.ServiceTrackerFactory; /** @@ -93,7 +92,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; @@ -183,14 +181,14 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home 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); @@ -217,22 +215,18 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home 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); @@ -281,7 +275,8 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home EventViewDTO dto = new EventViewDTO(); mapToMetadataDTO(event, dto); - dto.setLogoImageURL(event.getLogoImageURL() == null ? null : event.getLogoImageURL().toString()); + ImageDescriptor logoImage = event.findImageWithTag(MediaTagConstants.LOGO); + dto.setLogoImage(logoImage != null ? convertToImageDTO(logoImage) : null); dto.setOfficialWebsiteURL(event.getOfficialWebsiteURL() == null ? null : event.getOfficialWebsiteURL().toString()); dto.setHasMedia(HomeServiceUtil.hasMedia(event)); @@ -345,8 +340,8 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home 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())) { + // Special solution for Kieler Woche 2015 + if("a9d6c5d5-cac3-47f2-9b5c-506e441819a1".equals(event.getId().toString())) { dto.setSailorsInfoURL("http://sailorsinfo.kieler-woche.de/"); } @@ -373,7 +368,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 ? convertToImageDTO(logoImage) : null); // TODO implement correctly. We currently do not show media for series. dto.setHasMedia(false); @@ -430,25 +426,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); } } @@ -534,4 +525,20 @@ public class HomeServiceImpl extends ProxiedRemoteServiceServlet implements Home } return null; } + + 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(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/server/HomeServiceUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java index b5c9696e94b..20f244f25c5 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 @@ -1,15 +1,13 @@ 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 com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; @@ -25,20 +23,23 @@ import com.sap.sailing.domain.tracking.TrackedRace; 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.EventState; +import com.sap.sailing.gwt.ui.shared.media.MediaConstants; 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; 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,47 +101,43 @@ public final class HomeServiceUtil { return EventState.RUNNING; } - private static URL findEventThumbnailImageUrl(EventBase event) { + private static ImageDescriptor findEventThumbnailImage(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; + final ImageDescriptor image; + final boolean stage; ImageHolder() { - this(null, null); + this(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; + ImageHolder(ImageDescriptor image) { + this.image = image; + this.stage = image == null ? false : hasTag(image, MediaTagConstants.STAGE); + this.height = image == null || image.getHeightInPx() == null ? 0 : image.getHeightInPx(); + this.width = image == null || image.getWidthInPx() == null ? 0 : image.getWidthInPx(); + this.size = height * width; + if(width == 0) { 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)) { + if (stage) { return false; } @@ -175,13 +172,13 @@ public final class HomeServiceUtil { } boolean isNull() { - return size == -1; + return image == null; } } // search for name pattern - for (URL imageUrl : event.getImageURLs()) { - if (imageUrl.toString().toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + for (ImageDescriptor imageUrl : event.getImages()) { + if (hasTag(imageUrl, MediaTagConstants.TEASER)) { return imageUrl; } } @@ -189,16 +186,11 @@ public final class HomeServiceUtil { 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); - } + for (ImageDescriptor candidateImage : getPhotoGalleryImages(event)) { + ImageHolder candidate = new ImageHolder(candidateImage); if (candidate.isPerfectFit()) { - return candidate.url; + return candidate.image; } if (candidate.fitsRatio() && candidate.isBigEnough()) { @@ -214,20 +206,20 @@ public final class HomeServiceUtil { } if (!bestFit.isNull()) { - return bestFit.url; + return bestFit.image; } if (!actualWorstcase.isNull()) { - return actualWorstcase.url; + return actualWorstcase.image; } return null; } /** - * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back + * The stage image is determined from the {@link #images} 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 + *
  4. If one or more image has {@link MediaConstants#STAGE} tag, only they are considered candidates.
  5. + *
  6. If no image has {@link MediaConstants#STAGE} tag, all images are considered * candidates.
  7. *
  8. From all candidates, the one with the biggest known size (determined by the product of width and height) is * chosen.
  9. @@ -235,107 +227,88 @@ public final class HomeServiceUtil { *
*/ 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 + * The stage image is determined from the {@link #images} 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 + *
  4. If one or more image has {@link MediaConstants#STAGE} tag, only they are considered candidates.
  5. + *
  6. If no image has {@link MediaConstants#STAGE} tag, all images are considered * candidates.
  7. *
  8. From all candidates, the one with the biggest known size (determined by the product of width and height) is * chosen.
  9. *
  10. If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.
  11. *
*/ - 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; - } - - 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 result = new ArrayList(urls.size()); - for (URL url : urls) { - result.add(url.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(); + public static ImageDescriptor getStageImage(final EventBase event) { + ImageDescriptor bestMatch = null; - 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)) { + for(ImageDescriptor candidate : event.getImages()) { + if(bestMatch == null || isBetterStageImage(candidate, bestMatch)) { + bestMatch = candidate; continue; } - if ((first && !iter.hasNext()) || !Util.equalsWithNull(imageUrl, stageImageURL)) { - result.add(imageUrl); + } + + return bestMatch; + } + + private static boolean isBetterStageImage(ImageDescriptor candidate, ImageDescriptor reference) { + boolean stage = hasTag(candidate, MediaTagConstants.STAGE); + boolean stageRef = hasTag(reference, MediaTagConstants.STAGE); + if(stage != stageRef) { + return stage; + } + boolean teaser = hasTag(candidate, MediaTagConstants.TEASER); + boolean teaserRef = hasTag(reference, MediaTagConstants.TEASER); + if(teaser != teaserRef) { + return !teaser; + } + + int size = candidate.getArea(); + int sizeRef = candidate.getArea(); + if(size != sizeRef) { + return size > sizeRef; + } + return candidate.getCreatedAtDate().compareTo(reference.getCreatedAtDate()) > 0; + } + + public static List getPhotoGalleryImageURLsAsString(EventBase event) { + List urls = getPhotoGalleryImages(event); + List result = new ArrayList(urls.size()); + for (ImageDescriptor url : urls) { + result.add(url.getURL().toString()); + } + return result; + } + + public static List getPhotoGalleryImages(EventBase event) { + ImageDescriptor stageImage = getStageImage(event); // if set, exclude stage image from photo gallery + List result = new ArrayList<>(); + Iterable imageURLs = event.getImages(); + + boolean first = true; + for (Iterator iter = imageURLs.iterator(); iter.hasNext(); ) { + ImageDescriptor image = iter.next(); + if(hasTag(image, MediaTagConstants.TEASER)) { + continue; + } + if ((first && !iter.hasNext()) || !Util.equalsWithNull(image, stageImage)) { + result.add(image); } first = false; } return result; } - 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 +362,16 @@ public final class HomeServiceUtil { } public static boolean hasPhotos(Event event) { - // FIXME filter teaser images - return Util.size(event.getImageURLs()) > 1; + for(ImageDescriptor image : event.getImages()) { + if(!hasTag(image, MediaTagConstants.TEASER)) { + return true; + } + } + return false; } 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 +383,97 @@ 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; + } } 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/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..96452773d86 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(); 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..3db284c2c85 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 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..c8fb66f291a 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 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..eea5c792c37 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 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/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/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..056ef9f861f 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() { @@ -46,18 +49,14 @@ public class GetEventOverviewStageAction implements Action 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/eventview/EventViewDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/eventview/EventViewDTO.java index af436fc3559..55aad746141 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 @@ -5,6 +5,7 @@ import java.util.List; 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 { @@ -19,7 +20,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 +88,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.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.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/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..4ce6bd25815 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 @@ -208,6 +208,8 @@ import com.sap.sse.common.TimePoint; 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; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.common.search.ResultImpl; @@ -2572,13 +2574,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 +2588,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 +2611,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 +2634,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..71b0328c115 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java @@ -0,0 +1,11 @@ +package com.sap.sse.common.media; + +import java.util.List; + +public interface WithMedia extends WithImages, WithVideos { + ImageDescriptor findImageWithTag(String tagName); + List findImagesWithTag(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; + } +}