Merge branch 'master' into spectator

This commit is contained in:
Lennart Hensler
2012-03-22 09:17:21 +01:00
11 changed files with 80 additions and 57 deletions
@@ -97,6 +97,8 @@ public interface Placemark extends Serializable {
public class ByPopulationDistanceRatio implements Comparator<Placemark> {
private Position position;
private double populationFactor = 1.0;
private double distanceFactor = 1.0;
public ByPopulationDistanceRatio(Position position) {
this.position = position;
@@ -104,8 +106,8 @@ public interface Placemark extends Serializable {
@Override
public int compare(Placemark p1, Placemark p2) {
double r1 = p1.getPopulation() / p1.distanceFrom(position).getKilometers();
double r2 = p2.getPopulation() / p2.distanceFrom(position).getKilometers();
double r1 = (p1.getPopulation() * populationFactor) / (p1.distanceFrom(position).getKilometers() * distanceFactor);
double r2 = (p2.getPopulation() * populationFactor) / (p2.distanceFrom(position).getKilometers() * distanceFactor);
return r1 > r2 ? 1 : r1 < r2 ? -1 : 0;
}
@@ -229,4 +229,6 @@ public interface Leaderboard extends Named {
void updateIsMedalRace(String raceName, boolean isMedalRace);
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule);
Competitor getCompetitorByIdAsString(String idAsString);
}
@@ -175,6 +175,16 @@ public class LeaderboardImpl implements Named, Leaderboard {
}
return null;
}
@Override
public Competitor getCompetitorByIdAsString(String idAsString) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getId().toString().equals(idAsString)) {
return competitor;
}
}
return null;
}
@Override
public SettableScoreCorrection getScoreCorrection() {
@@ -3,7 +3,5 @@ package com.sap.sailing.gwt.ui.adminconsole;
import com.google.gwt.maps.client.geom.LatLngBounds;
public interface LatLngBoundsCalculator {
public LatLngBounds calculateNewBounds(RaceMap forMap);
LatLngBounds calculateNewBounds(RaceMap forMap);
}
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -155,11 +156,6 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private List<RaceIdentifier> selectedRaces;
/**
* If the user explicitly zoomed or panned the map, don't adjust zoom/pan unless a new race is selected
*/
private boolean mapZoomedOrPannedSinceLastRaceSelectionChange = false;
/**
* Used to check if the first initial zoom to the buoy markers was already done.
*/
@@ -268,7 +264,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
@Override
public void onZoomEnd(MapZoomEndEvent event) {
map.checkResizeAndCenter();
mapZoomedOrPannedSinceLastRaceSelectionChange = true;
final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
getSettings().getZoomSettings().setTypesToConsiderOnZoom(emptyList);
Set<CompetitorDTO> competitorDTOsOfUnusedMarkers = new HashSet<CompetitorDTO>(boatMarkers.keySet());
for (CompetitorDTO competitorDTO : getCompetitorsToShow()) {
boolean usedExistingMarker = updateBoatMarkerForCompetitor(competitorDTO);
@@ -284,7 +281,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
map.addMapDragEndHandler(new MapDragEndHandler() {
@Override
public void onDragEnd(MapDragEndEvent event) {
mapZoomedOrPannedSinceLastRaceSelectionChange = true;
final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
getSettings().getZoomSettings().setTypesToConsiderOnZoom(emptyList);
}
});
map.addMapMouseMoveHandler(new MapMouseMoveHandler() {
@@ -312,7 +310,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
@Override
public void onRaceSelectionChange(List<RaceIdentifier> selectedRaces) {
mapFirstZoomDone = false;
mapZoomedOrPannedSinceLastRaceSelectionChange = false;
// TODO bug 494: reset zoom settings to user preferences
this.selectedRaces = selectedRaces;
windPanel.onRaceSelectionChange(selectedRaces);
}
@@ -357,13 +355,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
List<MarkDTO> markData = raceMapDataDTO.markPositions;
showMarksOnMap(markData);
// Rezoom the map
// TODO make this a loop across the LatLongBoundsCalculators, pulling them from a collection updated in updateSettings
if (!getSettings().getZoomSettings().contains(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
zoomMapToNewBounds(getSettings().getZoomSettings().getNewBounds(RaceMap.this));
mapFirstZoomDone = true;
} else if (!mapZoomedOrPannedSinceLastRaceSelectionChange) { // Zoom once to the boats
zoomMapToNewBounds(new BoatsBoundsCalculater().calculateNewBounds(RaceMap.this));
mapFirstZoomDone = true;
} else if (!mapZoomedOrPannedSinceLastRaceSelectionChange && !mapFirstZoomDone) { // Zoom once to the buoys
} else if (!mapFirstZoomDone) { // Zoom once to the buoys
zoomMapToNewBounds(new BuoysBoundsCalculater().calculateNewBounds(RaceMap.this));
mapFirstZoomDone = true;
/*
@@ -372,7 +368,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
* triggered by the user. As a consequence the
* mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
*/
mapZoomedOrPannedSinceLastRaceSelectionChange = false;
// TODO bug 494: consider initial user-specific zoom settings
}
}
} else {
@@ -380,13 +376,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
});
asyncActionsExecutor.execute(getRaceMapDataAction);
// draw the wind into the map, get the combined wind
List<String> windSourceTypeNames = new ArrayList<String>();
windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, date, 1000L, 1, windSourceTypeNames,
new AsyncCallback<WindInfoForRaceDTO>() {
@Override
@@ -599,10 +592,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private void zoomMapToNewBounds(LatLngBounds newBounds) {
if (newBounds != null) {
boolean oldMapZoomedOrPannedSinceLastRaceSelectionChange = mapZoomedOrPannedSinceLastRaceSelectionChange;
List<ZoomTypes> oldZoomSettings = getSettings().getZoomSettings().getTypesToConsiderOnZoom();
map.setCenter(newBounds.getCenter());
map.setZoomLevel(map.getBoundsZoomLevel(newBounds));
mapZoomedOrPannedSinceLastRaceSelectionChange = oldMapZoomedOrPannedSinceLastRaceSelectionChange;
getSettings().getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings);
}
}
@@ -1300,7 +1293,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
} catch (IndexOutOfBoundsException e) {
//Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG)
// TODO can't this be predicted and the exception be avoided in the first place?
// Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG)
}
}
return newBounds;
@@ -119,18 +119,18 @@ public interface SailingService extends RemoteService {
Pair<String, String> getEventAndRaceNameOfTrackedRaceConnectedToLeaderboardColumn(String leaderboardName, String raceColumnName);
void updateLeaderboardCarryValue(String leaderboardName, String competitorName, Integer carriedPoints);
void updateLeaderboardCarryValue(String leaderboardName, String competitorIdAsString, Integer carriedPoints);
/**
* @return the new net points in {@link Pair#getA()} and the new total points in {@link Pair#getB()}
*/
Pair<Integer, Integer> updateLeaderboardMaxPointsReason(String leaderboardName, String competitorName,
Pair<Integer, Integer> updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString,
String raceColumnName, String maxPointsReasonAsString, Date date) throws NoWindException;
Pair<Integer, Integer> updateLeaderboardScoreCorrection(String leaderboardName, String competitorName,
Pair<Integer, Integer> updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString,
String raceName, Integer correctedScore, Date date) throws NoWindException;
void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorName, String displayName);
void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorIdAsString, String displayName);
void updateIsMedalRace(String leaderboardName, String columnName, boolean isMedalRace);
@@ -206,15 +206,15 @@ public interface SailingServiceAsync {
void disconnectLeaderboardColumnFromTrackedRace(String leaderboardName, String raceColumnName,
AsyncCallback<Void> callback);
void updateLeaderboardCarryValue(String leaderboardName, String competitorName, Integer carriedPoints, AsyncCallback<Void> callback);
void updateLeaderboardCarryValue(String leaderboardName, String competitorID, Integer carriedPoints, AsyncCallback<Void> callback);
void updateLeaderboardMaxPointsReason(String leaderboardName, String competitorName, String raceColumnName,
void updateLeaderboardMaxPointsReason(String leaderboardName, String competitorID, String raceColumnName,
String maxPointsReasonAsString, Date date, AsyncCallback<Pair<Integer, Integer>> asyncCallback);
void updateLeaderboardScoreCorrection(String leaderboardName, String competitorName, String raceName,
void updateLeaderboardScoreCorrection(String leaderboardName, String competitorID, String raceName,
Integer correctedScore, Date date, AsyncCallback<Pair<Integer, Integer>> asyncCallback);
void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorName, String displayName,
void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorID, String displayName,
AsyncCallback<Void> callback);
void moveLeaderboardColumnUp(String leaderboardName, String columnName,
@@ -42,7 +42,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
setFieldUpdater(new FieldUpdater<LeaderboardRowDTO, String>() {
@Override
public void update(final int rowIndex, final LeaderboardRowDTO row, final String value) {
getSailingService().updateLeaderboardCarryValue(getLeaderboardName(), row.competitor.name,
getSailingService().updateLeaderboardCarryValue(getLeaderboardName(), row.competitor.id,
value == null || value.length() == 0 ? null : Integer.valueOf(value.trim()),
new AsyncCallback<Void>() {
@Override
@@ -75,7 +75,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
setFieldUpdater(new FieldUpdater<LeaderboardRowDTO, String>() {
@Override
public void update(final int rowIndex, final LeaderboardRowDTO row, final String value) {
getSailingService().updateCompetitorDisplayNameInLeaderboard(getLeaderboardName(), row.competitor.name,
getSailingService().updateCompetitorDisplayNameInLeaderboard(getLeaderboardName(), row.competitor.id,
value == null || value.length() == 0 ? null : value.trim(),
new AsyncCallback<Void>() {
@Override
@@ -159,7 +159,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
final RowUpdateWhiteboard<LeaderboardRowDTO> whiteboard = new RowUpdateWhiteboard<LeaderboardRowDTO>(
EditableLeaderboardPanel.this.getData());
getWhiteboardOwner().whiteboardProduced(whiteboard);
getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.name,
getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.id,
raceName, value == null || value.trim().length() == 0 ? null : value.trim(),
getLeaderboardDisplayDate(), new AsyncCallback<Pair<Integer, Integer>>() {
@Override
@@ -217,7 +217,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
final RowUpdateWhiteboard<LeaderboardRowDTO> whiteboard = new RowUpdateWhiteboard<LeaderboardRowDTO>(
EditableLeaderboardPanel.this.getData());
getWhiteboardOwner().whiteboardProduced(whiteboard);
getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.name, raceName,
getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.id, raceName,
value == null || value.trim().length() == 0 ? null : Integer.valueOf(value.trim()), getLeaderboardDisplayDate(),
new AsyncCallback<Pair<Integer, Integer>>() {
@Override
@@ -849,6 +849,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
Iterator<GPSFixMoving> fixIter = fixes.iterator();
if (fixIter.hasNext()) {
final WindSource windSource = new WindSourceImpl(WindSourceType.COMBINED);
GPSFixMoving fix = fixIter.next();
while (fix != null && fix.getTimePoint().compareTo(toTimePointExcluding) < 0) {
Tack tack = trackedRace.getTack(competitor, fix.getTimePoint());
@@ -856,7 +857,9 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
fix.getTimePoint());
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
GPSFixDTO fixDTO = createGPSFixDTO(fix, fix.getSpeed(), tack, legType, /* extrapolate */
GPSFixDTO fixDTO = createGPSFixDTO(fix, fix.getSpeed(), createWindDTOFromAlreadyAveraged(trackedRace.getWind(fix.getPosition(),
toTimePointExcluding), trackedRace
.getOrCreateWindTrack(windSource)), tack, legType, /* extrapolate */
false);
fixesForCompetitor.add(fixDTO);
if (fixIter.hasNext()) {
@@ -870,10 +873,14 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
fix.getTimePoint());
SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(toTimePointExcluding);
GPSFixDTO extrapolated = new GPSFixDTO(to.get(competitorDTO), new PositionDTO(
position.getLatDeg(), position.getLngDeg()),
createSpeedWithBearingDTO(speedWithBearing), tack2, /* extrapolated */
legType2, true);
GPSFixDTO extrapolated = new GPSFixDTO(
to.get(competitorDTO),
new PositionDTO(position.getLatDeg(), position.getLngDeg()),
createSpeedWithBearingDTO(speedWithBearing),
createWindDTOFromAlreadyAveraged(trackedRace.getWind(position,
toTimePointExcluding), trackedRace
.getOrCreateWindTrack(windSource)), /* extrapolated */
tack2, legType2, true);
fixesForCompetitor.add(extrapolated);
}
fix = null;
@@ -891,10 +898,10 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
.getBearing().getDegrees());
}
private GPSFixDTO createGPSFixDTO(GPSFix fix, SpeedWithBearing speedWithBearing, Tack tack, LegType legType, boolean extrapolated) {
private GPSFixDTO createGPSFixDTO(GPSFix fix, SpeedWithBearing speedWithBearing, WindDTO windDTO, Tack tack, LegType legType, boolean extrapolated) {
return new GPSFixDTO(fix.getTimePoint().asDate(), new PositionDTO(fix
.getPosition().getLatDeg(), fix.getPosition().getLngDeg()),
createSpeedWithBearingDTO(speedWithBearing), tack, legType, extrapolated);
createSpeedWithBearingDTO(speedWithBearing), windDTO, tack, legType, extrapolated);
}
@Override
@@ -1341,10 +1348,10 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
@Override
public void updateLeaderboardCarryValue(String leaderboardName, String competitorName, Integer carriedPoints) {
public void updateLeaderboardCarryValue(String leaderboardName, String competitorIdAsString, Integer carriedPoints) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard != null) {
Competitor competitor = leaderboard.getCompetitorByName(competitorName);
Competitor competitor = leaderboard.getCompetitorByIdAsString(competitorIdAsString);
if (competitor != null) {
if (carriedPoints == null) {
leaderboard.unsetCarriedPoints(competitor);
@@ -1353,7 +1360,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
getService().updateStoredLeaderboard(leaderboard);
} else {
throw new IllegalArgumentException("Didn't find competitor "+competitorName+" in leaderboard "+leaderboardName);
throw new IllegalArgumentException("Didn't find competitor ID "+competitorIdAsString+" in leaderboard "+leaderboardName);
}
} else {
throw new IllegalArgumentException("Didn't find leaderboard "+leaderboardName);
@@ -1361,12 +1368,12 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
@Override
public Pair<Integer, Integer> updateLeaderboardMaxPointsReason(String leaderboardName, String competitorName, String raceColumnName,
public Pair<Integer, Integer> updateLeaderboardMaxPointsReason(String leaderboardName, String competitorIdAsString, String raceColumnName,
String maxPointsReasonAsString, Date date) throws NoWindException {
TimePoint timePoint = new MillisecondsTimePoint(date);
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard != null) {
Competitor competitor = leaderboard.getCompetitorByName(competitorName);
Competitor competitor = leaderboard.getCompetitorByIdAsString(competitorIdAsString);
if (competitor != null) {
RaceInLeaderboard raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
if (raceColumn == null) {
@@ -1381,7 +1388,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
Entry updatedEntry = leaderboard.getEntry(competitor, raceColumn, timePoint);
return new Pair<Integer, Integer>(updatedEntry.getNetPoints(), updatedEntry.getTotalPoints());
} else {
throw new IllegalArgumentException("Didn't find competitor "+competitorName+" in leaderboard "+leaderboardName);
throw new IllegalArgumentException("Didn't find competitor with ID "+competitorIdAsString+" in leaderboard "+leaderboardName);
}
} else {
throw new IllegalArgumentException("Didn't find leaderboard "+leaderboardName);
@@ -1389,13 +1396,13 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
@Override
public Pair<Integer, Integer> updateLeaderboardScoreCorrection(String leaderboardName, String competitorName, String raceName,
public Pair<Integer, Integer> updateLeaderboardScoreCorrection(String leaderboardName, String competitorIdAsString, String raceName,
Integer correctedScore, Date date) throws NoWindException {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
int newNetPoints;
int newTotalPoints;
if (leaderboard != null) {
Competitor competitor = leaderboard.getCompetitorByName(competitorName);
Competitor competitor = leaderboard.getCompetitorByIdAsString(competitorIdAsString);
if (competitor != null) {
MillisecondsTimePoint timePoint = new MillisecondsTimePoint(date);
RaceInLeaderboard raceColumn = leaderboard.getRaceColumnByName(raceName);
@@ -1411,7 +1418,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
newTotalPoints = leaderboard.getEntry(competitor, raceColumn, timePoint).getTotalPoints();
} else {
throw new IllegalArgumentException("Didn't find competitor "+competitorName+" in leaderboard "+leaderboardName);
throw new IllegalArgumentException("Didn't find competitor with ID "+competitorIdAsString+" in leaderboard "+leaderboardName);
}
} else {
throw new IllegalArgumentException("Didn't find leaderboard "+leaderboardName);
@@ -1421,9 +1428,9 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
}
@Override
public void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorName, String displayName) {
public void updateCompetitorDisplayNameInLeaderboard(String leaderboardName, String competitorIdAsString, String displayName) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
Competitor competitor = leaderboard.getCompetitorByName(competitorName);
Competitor competitor = leaderboard.getCompetitorByIdAsString(competitorIdAsString);
if (competitor != null) {
leaderboard.setDisplayName(competitor, displayName);
getService().updateStoredLeaderboard(leaderboard);
@@ -1618,6 +1625,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
Map<CompetitorDTO, List<GPSFixDTO>> result = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
if (trackedRace != null) {
final WindSource windSource = new WindSourceImpl(WindSourceType.COMBINED);
MeterDistance maxDistance = new MeterDistance(meters);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
CompetitorDTO competitorDTO = getCompetitorDTO(competitor);
@@ -1646,7 +1654,9 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, fix.getTimePoint());
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
GPSFixDTO fixDTO = createGPSFixDTO(fix, speedWithBearing, tack, legType, /* extrapolated */false);
GPSFixDTO fixDTO = createGPSFixDTO(fix, speedWithBearing, createWindDTOFromAlreadyAveraged(trackedRace.getWind(
fix.getPosition(), fix.getTimePoint()),
trackedRace.getOrCreateWindTrack(windSource)), tack, legType, /* extrapolated */false);
gpsFixDouglasList.add(fixDTO);
}
result.put(competitorDTO, gpsFixDouglasList);
@@ -10,6 +10,7 @@ public class GPSFixDTO implements IsSerializable {
public Date timepoint;
public PositionDTO position;
public SpeedWithBearingDTO speedWithBearing;
public WindDTO wind;
/**
* tells if this fix was computed by extrapolation instead of having been captured by a device directly
@@ -31,8 +32,8 @@ public class GPSFixDTO implements IsSerializable {
public GPSFixDTO() {}
public GPSFixDTO(Date timepoint, PositionDTO position, SpeedWithBearingDTO speedWithBearing, Tack tack,
LegType legType, boolean extrapolated) {
public GPSFixDTO(Date timepoint, PositionDTO position, SpeedWithBearingDTO speedWithBearing, WindDTO wind,
Tack tack, LegType legType, boolean extrapolated) {
super();
this.timepoint = timepoint;
this.position = position;
@@ -40,5 +41,6 @@ public class GPSFixDTO implements IsSerializable {
this.tack = tack;
this.legType = legType;
this.extrapolated = extrapolated;
this.wind = wind;
}
}
@@ -16,5 +16,10 @@ axel: axel,spectator,moderator,administrator
frank: frank,spectator,moderator,administrator
lennart: lennart,spectator,moderator,administrator
marcus: marcus,spectator,moderator,administrator
milan: milan,spectator,moderator,administrator
nils: nils,spectator,moderator,administrator
stefan: stefan,spectator,moderator,administrator
ricardo: ricardo,spectator,moderator,administrator
christopher: christopher,spectator,moderator,administrator
spectator: spectator,spectator
moderator: moderator,moderator