Skip to content

Commit

Permalink
Merge pull request #221 from bannergress/banner-status-rework
Browse files Browse the repository at this point in the history
Introduce placeholder missions.
  • Loading branch information
Poeschl authored Aug 11, 2021
2 parents b5a384b + a4cae5c commit 35d586e
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.bannergress.backend.dto.BannerDto;
import com.bannergress.backend.dto.BannerSettingsDto;
import com.bannergress.backend.dto.MissionDto;
import com.bannergress.backend.entities.Banner;
import com.bannergress.backend.entities.BannerSettings;
import com.bannergress.backend.entities.Mission;
import com.bannergress.backend.entities.PlaceInformation;
import com.bannergress.backend.enums.BannerListType;
import com.bannergress.backend.enums.BannerSortOrder;
Expand Down Expand Up @@ -204,6 +206,8 @@ private BannerDto toSummary(Banner banner) {
dto.id = banner.getCanonicalSlug();
dto.title = banner.getTitle();
dto.numberOfMissions = banner.getNumberOfMissions();
dto.numberOfSubmittedMissions = banner.getNumberOfSubmittedMissions();
dto.numberOfDisabledMissions = banner.getNumberOfDisabledMissions();
dto.lengthMeters = banner.getLengthMeters();
dto.startLatitude = getLatitude(banner.getStartPoint());
dto.startLongitude = getLongitude(banner.getStartPoint());
Expand All @@ -220,12 +224,16 @@ private BannerDto toSummary(Banner banner) {

private BannerDto toDetails(Banner banner) {
BannerDto dto = toSummary(banner);
dto.missions = Maps.transformValues(banner.getMissions(), MissionController::toDetails);
dto.missions = Maps.transformValues(banner.getMissionsAndPlaceholders(), this::toMissionOrPlaceholder);
dto.type = banner.getType();
dto.description = banner.getDescription();
return dto;
}

private MissionDto toMissionOrPlaceholder(Optional<Mission> input) {
return input.map(MissionController::toDetails).orElse(new MissionDto());
}

private void amendUserSettings(Principal principal, Collection<BannerDto> bannerDtos) {
if (principal != null) {
List<BannerSettings> bannerSettings = bannerSettingsService.getBannerSettings(principal.getName(),
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/bannergress/backend/dto/BannerDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public class BannerDto {
*/
public int numberOfMissions;

/**
* Number of submitted missions.
*/
public int numberOfSubmittedMissions;

/**
* Number of disabled missions.
*/
public int numberOfDisabledMissions;

/**
* Map between the zero-based mission position and the mission. The mission
* position must be less than {@link #numberOfMissions}. The map may be sparse,
Expand Down
77 changes: 59 additions & 18 deletions src/main/java/com/bannergress/backend/entities/Banner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.bannergress.backend.enums.BannerType;
import com.bannergress.backend.utils.PojoBuilder;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import net.karneim.pojobuilder.GeneratePojoBuilder;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.SortNatural;
Expand Down Expand Up @@ -73,6 +75,20 @@ public class Banner {
@NotAudited
private int numberOfMissions;

/**
* Number of submitted ("not yet published") missions.
*/
@Column(name = "number_of_submitted_missions", nullable = false)
@NotAudited
private int numberOfSubmittedMissions;

/**
* Number of disabled ("not published anymore") missions.
*/
@Column(name = "number_of_disabled_missions", nullable = false)
@NotAudited
private int numberOfDisabledMissions;

/**
* Map between the zero-based mission position and the mission. The mission
* position must be less than {@link #numberOfMissions}. The map may be sparse,
Expand All @@ -86,6 +102,16 @@ public class Banner {
@SortNatural
private SortedMap<Integer, Mission> missions = new TreeMap<>();

/**
* Set of zero-based mission positions where a mission is supposed to be. This set and the keyset of {@link #missions} are mutually exclusive.
*/
@ElementCollection
@CollectionTable(name = "banner_placeholder", joinColumns = {@JoinColumn(name = "banner")})
@Column(name = "position")
@AuditJoinTable(name = "banner_placeholder_audit")
@SortNatural
private SortedSet<Integer> placeholders = new TreeSet<>();

/**
* Start portal of the first mission.
*/
Expand All @@ -101,13 +127,6 @@ public class Banner {
@NotAudited
private Integer lengthMeters;

/**
* All mission information is present.
*/
@Column(name = "complete", nullable = false)
@NotAudited
private boolean complete;

/**
* All missions are online.
*/
Expand Down Expand Up @@ -210,6 +229,22 @@ public void setNumberOfMissions(int numberOfMissions) {
this.numberOfMissions = numberOfMissions;
}

public int getNumberOfSubmittedMissions() {
return numberOfSubmittedMissions;
}

public void setNumberOfSubmittedMissions(int numberOfSubmittedMissions) {
this.numberOfSubmittedMissions = numberOfSubmittedMissions;
}

public int getNumberOfDisabledMissions() {
return numberOfDisabledMissions;
}

public void setNumberOfDisabledMissions(int numberOfDisabledMissions) {
this.numberOfDisabledMissions = numberOfDisabledMissions;
}

public SortedMap<Integer, Mission> getMissions() {
return missions;
}
Expand All @@ -218,6 +253,20 @@ public void setMissions(SortedMap<Integer, Mission> missions) {
this.missions = missions;
}

public SortedSet<Integer> getPlaceholders() {
return placeholders;
}

public void setPlaceholders(SortedSet<Integer> placeholders) {
this.placeholders = placeholders;
}

public ImmutableSortedMap<Integer, Optional<Mission>> getMissionsAndPlaceholders() {
return ImmutableSortedMap.<Integer, Optional<Mission>>naturalOrder()
.putAll(Maps.transformValues(missions, Optional::of))
.putAll(Maps.asMap(placeholders, p -> Optional.empty())).build();
}

public Point getStartPoint() {
return startPoint;
}
Expand All @@ -242,14 +291,6 @@ public void setStartPlaces(Set<Place> startPlaces) {
this.startPlaces = startPlaces;
}

public boolean isComplete() {
return complete;
}

public void setComplete(boolean complete) {
this.complete = complete;
}

public boolean isOnline() {
return online;
}
Expand Down Expand Up @@ -292,7 +333,7 @@ public boolean equals(final Object o) {
}
final Banner banner = (Banner) o;
return Objects.equals(uuid, banner.uuid) && numberOfMissions == banner.numberOfMissions
&& complete == banner.complete && online == banner.online && Objects.equals(title, banner.title)
&& online == banner.online && Objects.equals(title, banner.title)
&& Objects.equals(description, banner.description) && Objects.equals(missions, banner.missions)
&& Objects.equals(startPoint, banner.startPoint) && Objects.equals(lengthMeters, banner.lengthMeters)
&& Objects.equals(picture, banner.picture) && Objects.equals(startPlaces, banner.startPlaces)
Expand All @@ -302,7 +343,7 @@ public boolean equals(final Object o) {

@Override
public int hashCode() {
return Objects.hash(uuid, title, description, numberOfMissions, missions, startPoint, lengthMeters, complete,
online, picture, startPlaces, created, type, canonicalSlug);
return Objects.hash(uuid, title, description, numberOfMissions, missions, startPoint, lengthMeters, online,
picture, startPlaces, created, type, canonicalSlug);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.SortedMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;

Expand Down Expand Up @@ -106,6 +107,9 @@ private String hash(Banner banner) {
hasher.putInt(entry.getKey()).putUnencodedChars(entry.getValue().getPicture().toString())
.putBoolean(entry.getValue().getStatus() == MissionStatus.published);
}
for (Integer position : banner.getPlaceholders()) {
hasher.putInt(position);
}
return hasher.hash().toString();
}

Expand All @@ -122,7 +126,8 @@ private static BufferedImage loadImage(String path) {

protected byte[] createPicture(Banner banner) {
final int numberColumns = banner.getWidth();
final int numberRows = banner.getMissions().lastKey() / numberColumns + 1;
SortedMap<Integer, Optional<Mission>> missionsAndPlaceholders = banner.getMissionsAndPlaceholders();
final int numberRows = missionsAndPlaceholders.lastKey() / numberColumns + 1;
final int DISTANCE_CIRCLES = 4;
final int DIAMETER = 96;
final int MISSIONSIZE = DIAMETER + DISTANCE_CIRCLES;
Expand All @@ -136,24 +141,27 @@ protected byte[] createPicture(Banner banner) {

// loads, draws and masks the individual mission images to the banner image.
try {
threadPool.submit(() -> banner.getMissions().entrySet().parallelStream().forEach(entry -> {
BufferedImage missionImage;
Request request = new Request.Builder().url(entry.getValue().getPicture()).build();
try (Response response = client.newCall(request).execute()) {
missionImage = ImageIO.read(response.body().byteStream());
} catch (IOException ex) {
throw new RuntimeException("failed ro read image: " + entry.getValue().getPicture(), ex);
}
threadPool.submit(() -> missionsAndPlaceholders.entrySet().parallelStream().forEach(entry -> {
Optional<BufferedImage> optionalMissionImage = entry.getValue().map(mission -> {
Request request = new Request.Builder().url(mission.getPicture()).build();
try (Response response = client.newCall(request).execute()) {
return ImageIO.read(response.body().byteStream());
} catch (IOException ex) {
throw new RuntimeException("failed ro read image: " + mission.getPicture(), ex);
}
});
int missionPosition = numberColumns * numberRows - entry.getKey().intValue() - 1;
int x1 = DISTANCE_CIRCLES + (missionPosition % numberColumns) * MISSIONSIZE;
int y1 = DISTANCE_CIRCLES + (missionPosition / numberColumns) * MISSIONSIZE;
int x2 = x1 + DIAMETER;
int y2 = y1 + DIAMETER;
synchronized (graphics) {
graphics.drawImage(missionImage, x1, y1, x2, y2, 0, 0, missionImage.getWidth(),
missionImage.getHeight(), null);
BufferedImage maskImage = entry.getValue().getStatus() == MissionStatus.published ? maskImageOnline
: maskImageOffline;
optionalMissionImage.ifPresent(missionImage -> {
graphics.drawImage(missionImage, x1, y1, x2, y2, 0, 0, missionImage.getWidth(),
missionImage.getHeight(), null);
});
MissionStatus status = entry.getValue().map(Mission::getStatus).orElse(MissionStatus.submitted);
BufferedImage maskImage = status == MissionStatus.published ? maskImageOnline : maskImageOffline;
graphics.drawImage(maskImage, x1, y1, x2, y2, 0, 0, maskImage.getWidth(), maskImage.getHeight(),
null);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.bannergress.backend.services.impl;

import com.bannergress.backend.dto.BannerDto;
import com.bannergress.backend.dto.MissionDto;
import com.bannergress.backend.entities.Banner;
import com.bannergress.backend.entities.Mission;
import com.bannergress.backend.entities.MissionStep;
import com.bannergress.backend.entities.Place;
import com.bannergress.backend.enums.BannerListType;
import com.bannergress.backend.enums.BannerSortOrder;
import com.bannergress.backend.enums.MissionStatus;
import com.bannergress.backend.exceptions.MissionAlreadyUsedException;
import com.bannergress.backend.repositories.BannerRepository;
import com.bannergress.backend.repositories.BannerSpecifications;
Expand Down Expand Up @@ -201,8 +201,10 @@ private String deriveSlug(Banner banner) {

private Banner createTransient(BannerDto bannerDto, List<String> acceptableBannerSlugs)
throws MissionAlreadyUsedException {
Collection<String> missionIds = Collections2.transform(bannerDto.missions.values(),
missionDto -> missionDto.id);
Map<Integer, MissionDto> missions = Maps.filterValues(bannerDto.missions, missionDto -> missionDto.id != null);
Map<Integer, MissionDto> placeholders = Maps.filterValues(bannerDto.missions,
missionDto -> missionDto.id == null);
Collection<String> missionIds = Collections2.transform(missions.values(), missionDto -> missionDto.id);
missionService.assertNotAlreadyUsedInBanners(missionIds, acceptableBannerSlugs);
Banner banner = new Banner();
banner.setTitle(bannerDto.title);
Expand All @@ -212,7 +214,8 @@ private Banner createTransient(BannerDto bannerDto, List<String> acceptableBanne
banner.setType(bannerDto.type);
banner.getMissions().clear();
banner.getMissions()
.putAll(Maps.transformValues(bannerDto.missions, missionDto -> missionRepository.getOne(missionDto.id)));
.putAll(Maps.transformValues(missions, missionDto -> missionRepository.getOne(missionDto.id)));
banner.getPlaceholders().addAll(placeholders.keySet());
calculateData(banner);
pictureService.refresh(banner);
return banner;
Expand Down Expand Up @@ -255,13 +258,19 @@ public void deleteBySlug(String slug) {
@Override
public void calculateData(Banner banner) {
Point startPoint = null;
boolean complete = true;
boolean online = complete;
int numberOfSubmittedMissions = banner.getPlaceholders().size();
int numberOfDisabledMissions = 0;

for (Mission mission : banner.getMissions().values()) {
online &= mission.getStatus() == MissionStatus.published;
if (mission.getType() == null) {
complete = false;
switch (mission.getStatus()) {
case disabled:
numberOfDisabledMissions++;
break;
case submitted:
numberOfSubmittedMissions++;
break;
default:
break;
}
for (MissionStep step : mission.getSteps()) {
if (step.getPoi() != null) {
Expand All @@ -273,9 +282,10 @@ public void calculateData(Banner banner) {
}
}
banner.setStartPoint(startPoint);
banner.setComplete(complete);
banner.setOnline(online);
banner.setNumberOfMissions(banner.getMissions().size());
banner.setOnline(numberOfSubmittedMissions == 0 && numberOfDisabledMissions == 0);
banner.setNumberOfMissions(banner.getMissions().size() + banner.getPlaceholders().size());
banner.setNumberOfDisabledMissions(numberOfDisabledMissions);
banner.setNumberOfSubmittedMissions(numberOfSubmittedMissions);
if (startPoint != null && banner.getStartPlaces().isEmpty()) {
Collection<Place> startPlaces = placesService.getPlaces(startPoint);
banner.getStartPlaces().clear();
Expand Down
28 changes: 28 additions & 0 deletions src/main/resources/db/migration/V2_13__banner_status.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ALTER TABLE banner DROP COLUMN complete;
ALTER TABLE banner ADD COLUMN number_of_submitted_missions integer;
ALTER TABLE banner ADD COLUMN number_of_disabled_missions integer;
UPDATE banner b SET
number_of_submitted_missions = 0,
number_of_disabled_missions = (
SELECT COUNT(*) FROM banner_mission bm
JOIN mission m ON bm.mission = m.id
WHERE bm.banner = b.uuid AND m.status = 'disabled'
);
ALTER TABLE BANNER ALTER COLUMN number_of_submitted_missions SET NOT NULL;
ALTER TABLE BANNER ALTER COLUMN number_of_disabled_missions SET NOT NULL;

CREATE TABLE banner_placeholder (
banner uuid NOT NULL,
position integer NOT NULL,
PRIMARY KEY (banner, position),
FOREIGN KEY (banner) REFERENCES banner (uuid)
);

CREATE TABLE banner_placeholder_audit (
rev integer NOT NULL,
banner uuid NOT NULL,
position integer NOT NULL,
revtype smallint,
PRIMARY KEY (rev, banner, position),
FOREIGN KEY (rev) REFERENCES revision (id)
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.bannergress.backend.entities.Banner;
import com.bannergress.backend.entities.Mission;
import com.bannergress.backend.enums.MissionStatus;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;

import java.io.File;
Expand Down Expand Up @@ -37,6 +38,7 @@ void testCreatePicture() throws IOException {
}
banner.setMissions(missions);
banner.getMissions().get(3).setStatus(MissionStatus.disabled);
banner.setPlaceholders(Sets.newTreeSet(6, 11, 12, 17, 18, 19, 20, 21, 22, 23));

byte[] pngData = bannerPictureService.createPicture(banner);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public class EntityBuilder {
.withNumberOfMissions(a($Int()))
.withMissions(sortedMapWith(0, $Mission()))
.withLengthMeters(a($Int()))
.withComplete(a($Boolean()))
.withOnline(a($Boolean()))
.withPicture(a($BannerPicture()))
.withStartPlaces(setWith($Place()))
Expand Down

0 comments on commit 35d586e

Please sign in to comment.