diff --git a/src/main/java/org/rutebanken/tiamat/model/DisplayTypeEnumeration.java b/src/main/java/org/rutebanken/tiamat/model/DisplayTypeEnumeration.java new file mode 100644 index 000000000..00700d44d --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/model/DisplayTypeEnumeration.java @@ -0,0 +1,31 @@ +package org.rutebanken.tiamat.model; + +public enum DisplayTypeEnumeration { + ELECTRIC_TFT("electricTFT"), + BATTERY_ONE_ROW("batteryOneRow"), + BATTERY_MULTI_ROW("batteryMultiRow"), + BATTERY_E_INK("batteryEInk"), + CHARGEABLE_E_INK("chargeableEInk"), + NONE("none"); + + private final String value; + + DisplayTypeEnumeration(String v) { + value = v; + } + + public String value() { + return value; + } + + public static DisplayTypeEnumeration fromValue(String v) { + + for (DisplayTypeEnumeration c : DisplayTypeEnumeration.values()) { + if (c.value.equals(v)) { + return c; + } + } + + throw new IllegalArgumentException(v); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/model/InfoSpot.java b/src/main/java/org/rutebanken/tiamat/model/InfoSpot.java new file mode 100644 index 000000000..5e6986ee2 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/model/InfoSpot.java @@ -0,0 +1,164 @@ +package org.rutebanken.tiamat.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +@Entity +@Table( + uniqueConstraints = { + @UniqueConstraint(name = "info_spot_netex_id_version_constraint", columnNames = {"netexId", "version"})} +) +public class InfoSpot extends DataManagedObjectStructure implements Serializable { + + protected String label; + protected String purpose; + protected String description; + @Enumerated(EnumType.STRING) + protected PosterPlaceTypeEnumeration posterPlaceType; + @Enumerated(EnumType.STRING) + protected PosterSizeEnumeration posterPlaceSize; + protected Boolean backlight; + protected String maintenance; + protected String zoneLabel; + protected String railInformation; + protected String floor; + protected Boolean speechProperty; + @Enumerated(EnumType.STRING) + protected DisplayTypeEnumeration displayType; + + @ElementCollection(targetClass = StopPlaceReference.class, fetch = FetchType.EAGER) + @CollectionTable(name = "info_spot_stop_place") + private Set stopPlaces = new HashSet<>(); + + @ElementCollection(targetClass = InfoSpotPoster.class, fetch = FetchType.EAGER) + @CollectionTable(name = "info_spot_poster") + private Set posters = new HashSet<>(); + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PosterPlaceTypeEnumeration getPosterPlaceType() { + return posterPlaceType; + } + + public void setPosterPlaceType(PosterPlaceTypeEnumeration posterPlaceType) { + this.posterPlaceType = posterPlaceType; + } + + public PosterSizeEnumeration getPosterPlaceSize() { + return posterPlaceSize; + } + + public void setPosterPlaceSize(PosterSizeEnumeration posterPlaceSize) { + this.posterPlaceSize = posterPlaceSize; + } + + public Boolean getBacklight() { + return backlight; + } + + public void setBacklight(Boolean backlight) { + this.backlight = backlight; + } + + public String getMaintenance() { + return maintenance; + } + + public void setMaintenance(String maintenance) { + this.maintenance = maintenance; + } + + public String getZoneLabel() { + return zoneLabel; + } + + public void setZoneLabel(String zoneLabel) { + this.zoneLabel = zoneLabel; + } + + public String getRailInformation() { + return railInformation; + } + + public void setRailInformation(String railInformation) { + this.railInformation = railInformation; + } + + public String getFloor() { + return floor; + } + + public void setFloor(String floor) { + this.floor = floor; + } + + public Boolean getSpeechProperty() { + return speechProperty; + } + + public void setSpeechProperty(Boolean speechProperty) { + this.speechProperty = speechProperty; + } + + public DisplayTypeEnumeration getDisplayType() { + return displayType; + } + + public void setDisplayType(DisplayTypeEnumeration displayType) { + this.displayType = displayType; + } + + public Set getStopPlaces() { + return stopPlaces; + } + + public void setStopPlaces(Set stopPlaces) { + this.stopPlaces = stopPlaces; + } + + public Set getPosters() { + return posters; + } + + public void setPosters(Set posters) { + this.posters = posters; + } +} diff --git a/src/main/java/org/rutebanken/tiamat/model/InfoSpotPoster.java b/src/main/java/org/rutebanken/tiamat/model/InfoSpotPoster.java new file mode 100644 index 000000000..2d3dcc7dc --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/model/InfoSpotPoster.java @@ -0,0 +1,48 @@ +package org.rutebanken.tiamat.model; + +import javax.persistence.Embeddable; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; + +@Embeddable +public class InfoSpotPoster { + + private String label; + private String posterType; + private String lines; + + @Enumerated(EnumType.STRING) + private PosterSizeEnumeration posterSize; + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getPosterType() { + return posterType; + } + + public void setPosterType(String posterType) { + this.posterType = posterType; + } + + public String getLines() { + return lines; + } + + public void setLines(String lines) { + this.lines = lines; + } + + public PosterSizeEnumeration getPosterSize() { + return posterSize; + } + + public void setPosterSize(PosterSizeEnumeration posterSize) { + this.posterSize = posterSize; + } +} diff --git a/src/main/java/org/rutebanken/tiamat/model/PosterPlaceTypeEnumeration.java b/src/main/java/org/rutebanken/tiamat/model/PosterPlaceTypeEnumeration.java new file mode 100644 index 000000000..bb6f61d45 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/model/PosterPlaceTypeEnumeration.java @@ -0,0 +1,28 @@ +package org.rutebanken.tiamat.model; + +public enum PosterPlaceTypeEnumeration { + STATIC("static"), + DYNAMIC("dynamic"), + SOUND_BEACON("soundBeacon"); + + private final String value; + + PosterPlaceTypeEnumeration(String v) { + value = v; + } + + public String value() { + return value; + } + + public static PosterPlaceTypeEnumeration fromValue(String v) { + + for (PosterPlaceTypeEnumeration c : PosterPlaceTypeEnumeration.values()) { + if (c.value.equals(v)) { + return c; + } + } + + throw new IllegalArgumentException(v); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/model/PosterSizeEnumeration.java b/src/main/java/org/rutebanken/tiamat/model/PosterSizeEnumeration.java new file mode 100644 index 000000000..ae959956c --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/model/PosterSizeEnumeration.java @@ -0,0 +1,28 @@ +package org.rutebanken.tiamat.model; + +public enum PosterSizeEnumeration { + A3("a3"), + A4("a4"), + CM80x120("cm80x120"); + + private final String value; + + PosterSizeEnumeration(String v) { + value = v; + } + + public String value() { + return value; + } + + public static PosterSizeEnumeration fromValue(String v) { + + for (PosterSizeEnumeration c : PosterSizeEnumeration.values()) { + if (c.value.equals(v)) { + return c; + } + } + + throw new IllegalArgumentException(v); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepository.java b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepository.java new file mode 100644 index 000000000..f1a7b14a1 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepository.java @@ -0,0 +1,6 @@ +package org.rutebanken.tiamat.repository; + +import org.rutebanken.tiamat.model.InfoSpot; + +public interface InfoSpotRepository extends InfoSpotRepositoryCustom, EntityInVersionRepository { +} diff --git a/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryCustom.java b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryCustom.java new file mode 100644 index 000000000..728b0d10e --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryCustom.java @@ -0,0 +1,6 @@ +package org.rutebanken.tiamat.repository; + +import org.rutebanken.tiamat.model.InfoSpot; + +public interface InfoSpotRepositoryCustom extends DataManagedObjectStructureRepository { +} diff --git a/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryImpl.java b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryImpl.java new file mode 100644 index 000000000..03318afe1 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/repository/InfoSpotRepositoryImpl.java @@ -0,0 +1,16 @@ +package org.rutebanken.tiamat.repository; + +import java.util.Set; +import javax.transaction.Transactional; +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.stereotype.Repository; + +@Repository +@Transactional +public class InfoSpotRepositoryImpl implements InfoSpotRepositoryCustom { + + @Override + public String findFirstByKeyValues(String key, Set originalIds) { + throw new NotImplementedException("findFirstByKeyValues not implemented for " + this.getClass().getSimpleName()); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/GraphQLNames.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/GraphQLNames.java index e13820513..bdd43c43f 100644 --- a/src/main/java/org/rutebanken/tiamat/rest/graphql/GraphQLNames.java +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/GraphQLNames.java @@ -511,4 +511,27 @@ public class GraphQLNames { public static final String LINE_SIGNAGE = "lineSignage"; public static final String MAIN_LINE_SIGN = "mainLineSign"; public static final String REPLACES_RAIL_SIGN = "replacesRailSign"; + + public static final String OUTPUT_TYPE_INFO_SPOT = "InfoSpot"; + public static final String INPUT_TYPE_INFO_SPOT = OUTPUT_TYPE_INFO_SPOT + INPUT_TYPE_POSTFIX; + + public static final String INFO_SPOTS = "infoSpots"; + public static final String MUTATE_INFO_SPOT = "mutateInfoSpot"; + + public static final String POSTER = "poster"; + public static final String PURPOSE = "purpose"; + public static final String POSTER_PLACE_TYPE = "posterPlaceType"; + public static final String POSTER_PLACE_SIZE = "posterPlaceSize"; + public static final String BACKLIGHT = "backlight"; + public static final String MAINTENANCE = "maintenance"; + public static final String ZONE_LABEL = "zoneLabel"; + public static final String RAIL_INFORMATION = "railInformation"; + public static final String FLOOR = "floor"; + public static final String SPEECH_PROPERTY = "speechProperty"; + public static final String DISPLAY_TYPE = "displayType"; + public static final String ON_STOP_PLACE = "onStopPlace"; + + public static final String POSTER_TYPE = "posterType"; + public static final String POSTER_SIZE = "posterSize"; + public static final String LINES = "lines"; } diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/StopPlaceRegisterGraphQLSchema.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/StopPlaceRegisterGraphQLSchema.java index eeb0f6e10..ad31b821e 100644 --- a/src/main/java/org/rutebanken/tiamat/rest/graphql/StopPlaceRegisterGraphQLSchema.java +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/StopPlaceRegisterGraphQLSchema.java @@ -46,6 +46,7 @@ import org.rutebanken.tiamat.rest.graphql.types.FareZoneObjectTypeCreator; import org.rutebanken.tiamat.rest.graphql.types.GroupOfStopPlacesObjectTypeCreator; import org.rutebanken.tiamat.rest.graphql.types.GroupOfTariffZonesObjectTypeCreator; +import org.rutebanken.tiamat.rest.graphql.types.InfoSpotObjectTypeCreator; import org.rutebanken.tiamat.rest.graphql.types.ParentStopPlaceInputObjectTypeCreator; import org.rutebanken.tiamat.rest.graphql.types.ParentStopPlaceObjectTypeCreator; import org.rutebanken.tiamat.rest.graphql.types.PathLinkEndObjectTypeCreator; @@ -171,6 +172,9 @@ public class StopPlaceRegisterGraphQLSchema { @Autowired private FareZoneObjectTypeCreator fareZoneObjectTypeCreator; + @Autowired + private InfoSpotObjectTypeCreator infoSpotObjectTypeCreator; + @Autowired private AuthorizationCheckDataFetcher authorizationCheckDataFetcher; @@ -225,6 +229,12 @@ public class StopPlaceRegisterGraphQLSchema { @Autowired DataFetcher parkingUpdater; + @Autowired + DataFetcher infoSpotsFetcher; + + @Autowired + DataFetcher infoSpotsUpdater; + @Autowired DateScalar dateScalar; @@ -319,6 +329,8 @@ public void init() { GraphQLObjectType parkingObjectType = createParkingObjectType(validBetweenObjectType); + GraphQLObjectType infoSpotObjectType = infoSpotObjectTypeCreator.createObjectType(stopPlaceInterface, validBetweenObjectType); + GraphQLArgument allVersionsArgument = GraphQLArgument.newArgument() .name(ALL_VERSIONS) .type(GraphQLBoolean) @@ -422,6 +434,12 @@ public void init() { .description("List all fare zone authorities.") .dataFetcher(fareZoneAuthoritiesFetcher) .build()) + .field(newFieldDefinition() + .name(INFO_SPOTS) + .type(new GraphQLList(infoSpotObjectType)) + .description("Info spots") + .dataFetcher(infoSpotsFetcher) + .build()) .build(); @@ -442,6 +460,8 @@ public void init() { GraphQLInputObjectType purposeOfGroupingInputObjectType =createPurposeOfGroupingInputObjectType(); + GraphQLInputObjectType infoSpotInputObjectType = infoSpotObjectTypeCreator.createInputObjectType(validBetweenInputObjectType); + GraphQLObjectType stopPlaceRegisterMutation = newObject() .name("StopPlaceMutation") .description("Create and edit stopplaces") @@ -514,6 +534,14 @@ public void init() { .type(new GraphQLNonNull(GraphQLString))) .description("Hard delete group of stop places by ID") .dataFetcher(groupOfStopPlacesDeleterFetcher)) + .field(newFieldDefinition() + .type(infoSpotObjectType) + .name(MUTATE_INFO_SPOT) + .description("Create new or update existing InfoSpots") + .argument(GraphQLArgument.newArgument() + .name(OUTPUT_TYPE_INFO_SPOT) + .type(infoSpotInputObjectType)) + .dataFetcher(infoSpotsUpdater)) .build(); stopPlaceRegisterSchema = GraphQLSchema.newSchema() diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotStopPlaceFetcher.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotStopPlaceFetcher.java new file mode 100644 index 000000000..3d33ef9a5 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotStopPlaceFetcher.java @@ -0,0 +1,21 @@ +package org.rutebanken.tiamat.rest.graphql.fetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import java.util.List; +import org.rutebanken.tiamat.model.InfoSpot; +import org.rutebanken.tiamat.model.StopPlace; +import org.rutebanken.tiamat.service.InfoSpotStopPlaceResolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +public class InfoSpotStopPlaceFetcher implements DataFetcher> { + + private InfoSpotStopPlaceResolver infoSpotStopPlaceResolver; + + @Override + public List get(DataFetchingEnvironment dataFetchingEnvironment) { + InfoSpot infoSpot = dataFetchingEnvironment.getSource(); + return infoSpotStopPlaceResolver.resolve(infoSpot); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsFetcher.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsFetcher.java new file mode 100644 index 000000000..e9a9578c0 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsFetcher.java @@ -0,0 +1,28 @@ +package org.rutebanken.tiamat.rest.graphql.fetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.rutebanken.tiamat.repository.InfoSpotRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ID; + +@Service("infoSpotsFetcher") +@Transactional +public class InfoSpotsFetcher implements DataFetcher { + + @Autowired + private InfoSpotRepository infoSpotRepository; + + @Override + public Object get(DataFetchingEnvironment environment) throws Exception { + + if (environment.containsArgument(ID)) { + String netexId = environment.getArgument(ID); + return infoSpotRepository.findByNetexId(netexId); + } + return infoSpotRepository.findAll(); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsUpdater.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsUpdater.java new file mode 100644 index 000000000..138947dff --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/fetchers/InfoSpotsUpdater.java @@ -0,0 +1,142 @@ +package org.rutebanken.tiamat.rest.graphql.fetchers; + +import com.google.api.client.util.Preconditions; +import graphql.language.Field; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; + +import java.util.List; +import java.util.Map; + +import org.rutebanken.tiamat.lock.MutateLock; +import org.rutebanken.tiamat.model.DisplayTypeEnumeration; +import org.rutebanken.tiamat.model.InfoSpot; +import org.rutebanken.tiamat.model.PosterPlaceTypeEnumeration; +import org.rutebanken.tiamat.model.PosterSizeEnumeration; +import org.rutebanken.tiamat.repository.InfoSpotRepository; +import org.rutebanken.tiamat.versioning.VersionCreator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.BACKLIGHT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.DESCRIPTION; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.DISPLAY_TYPE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.FLOOR; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ID; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.LABEL; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.MAINTENANCE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.MUTATE_INFO_SPOT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.OUTPUT_TYPE_INFO_SPOT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_PLACE_SIZE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_PLACE_TYPE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.PURPOSE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.RAIL_INFORMATION; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.SPEECH_PROPERTY; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ZONE_LABEL; + +@Service("infoSpotsUpdater") +@Transactional +public class InfoSpotsUpdater implements DataFetcher { + + private static final Logger logger = LoggerFactory.getLogger(PathLinkUpdater.class); + + @Autowired + private MutateLock mutateLock; + + @Autowired + private InfoSpotRepository infoSpotRepository; + + @Override + public InfoSpot get(DataFetchingEnvironment environment) throws Exception { + return mutateLock.executeInLock(() -> { + final List fields = environment.getFields(); + + logger.info("Got fields {}", fields); + + InfoSpot infoSpot = null; + for (Field field : fields) { + if (field.getName().equals(MUTATE_INFO_SPOT)) { + infoSpot = createOrUpdateInfoSpotInLock(environment); + } + } + + return infoSpot; + }); + } + + private InfoSpot createOrUpdateInfoSpotInLock(DataFetchingEnvironment environment) { + return mutateLock.executeInLock(() -> createOrUpdateInfoSpot(environment)); + } + + private InfoSpot createOrUpdateInfoSpot(DataFetchingEnvironment environment) { + Map input = environment.getArgument(OUTPUT_TYPE_INFO_SPOT); + if (input == null) { + return null; + } + + InfoSpot target; + String netexId = (String) input.get(ID); + if (netexId != null) { + target = findAndVerify(netexId); + } else { + target = new InfoSpot(); + } + + if (input.containsKey(LABEL)) { + target.setLabel((String) input.get(LABEL)); + } + if (input.containsKey(PURPOSE)) { + target.setPurpose((String) input.get(PURPOSE)); + } + if (input.containsKey(DESCRIPTION)) { + target.setDescription((String) input.get(DESCRIPTION)); + } + if (input.containsKey(POSTER_PLACE_TYPE)) { + target.setPosterPlaceType((PosterPlaceTypeEnumeration) input.get(POSTER_PLACE_TYPE)); + } + if (input.containsKey(POSTER_PLACE_SIZE)) { + target.setPosterPlaceSize((PosterSizeEnumeration) input.get(POSTER_PLACE_SIZE)); + } + if (input.containsKey(BACKLIGHT)) { + target.setBacklight((Boolean) input.get(BACKLIGHT)); + } + if (input.containsKey(MAINTENANCE)) { + target.setMaintenance((String) input.get(MAINTENANCE)); + } + if (input.containsKey(ZONE_LABEL)) { + target.setZoneLabel((String) input.get(ZONE_LABEL)); + } + if (input.containsKey(RAIL_INFORMATION)) { + target.setRailInformation((String) input.get(RAIL_INFORMATION)); + } + if (input.containsKey(FLOOR)) { + target.setFloor((String) input.get(FLOOR)); + } + if (input.containsKey(SPEECH_PROPERTY)) { + target.setSpeechProperty((Boolean) input.get(SPEECH_PROPERTY)); + } + if (input.containsKey(DISPLAY_TYPE)) { + target.setDisplayType((DisplayTypeEnumeration) input.get(DISPLAY_TYPE)); + } + + // TODO: Set posters + + // TODO: Set associated stops + + InfoSpot saved = infoSpotRepository.save(target); + return saved; + } + + private InfoSpot findAndVerify(String netexId) { + InfoSpot existingInfoSpot = infoSpotRepository.findFirstByNetexIdOrderByVersionDesc(netexId); + verifyInfoSpotNotNull(existingInfoSpot, netexId); + return existingInfoSpot; + } + + private void verifyInfoSpotNotNull(InfoSpot existingInfoSpot, String netexId) { + Preconditions.checkArgument(existingInfoSpot != null, "Attempting to update InfoSpot [id = %s], but InfoSpot does not exist.", netexId); + } +} diff --git a/src/main/java/org/rutebanken/tiamat/rest/graphql/types/InfoSpotObjectTypeCreator.java b/src/main/java/org/rutebanken/tiamat/rest/graphql/types/InfoSpotObjectTypeCreator.java new file mode 100644 index 000000000..5520b8223 --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/rest/graphql/types/InfoSpotObjectTypeCreator.java @@ -0,0 +1,198 @@ +package org.rutebanken.tiamat.rest.graphql.types; + +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLObjectType; +import org.rutebanken.tiamat.model.DisplayTypeEnumeration; +import org.rutebanken.tiamat.model.PosterPlaceTypeEnumeration; +import org.rutebanken.tiamat.model.PosterSizeEnumeration; +import org.springframework.stereotype.Component; + +import static graphql.Scalars.GraphQLBigInteger; +import static graphql.Scalars.GraphQLBoolean; +import static graphql.Scalars.GraphQLString; +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import static graphql.schema.GraphQLInputObjectField.newInputObjectField; +import static graphql.schema.GraphQLInputObjectType.newInputObject; +import static graphql.schema.GraphQLObjectType.newObject; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.BACKLIGHT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.DESCRIPTION; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.DISPLAY_TYPE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.FLOOR; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ID; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.INFO_SPOTS; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.INPUT_TYPE_INFO_SPOT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.LABEL; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.LINES; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.MAINTENANCE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.MAXIMUM_STAY; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ON_STOP_PLACE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.OUTPUT_TYPE_INFO_SPOT; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_PLACE_SIZE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_PLACE_TYPE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_SIZE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.POSTER_TYPE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.PURPOSE; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.RAIL_INFORMATION; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.SPEECH_PROPERTY; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.VALID_BETWEEN; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.VERSION; +import static org.rutebanken.tiamat.rest.graphql.GraphQLNames.ZONE_LABEL; +import static org.rutebanken.tiamat.rest.graphql.types.CustomGraphQLTypes.createCustomEnumType; +import static org.rutebanken.tiamat.rest.graphql.types.CustomGraphQLTypes.geometryFieldDefinition; +import static org.rutebanken.tiamat.rest.graphql.types.CustomGraphQLTypes.netexIdFieldDefinition; + +@Component +public class InfoSpotObjectTypeCreator { + + + public static GraphQLEnumType posterPlaceTypeEnum = createCustomEnumType(POSTER_PLACE_TYPE, PosterPlaceTypeEnumeration.class); + public static GraphQLEnumType posterSizeEnum = createCustomEnumType(POSTER_PLACE_SIZE, PosterSizeEnumeration.class); + public static GraphQLEnumType displayTypeEnum = createCustomEnumType(DISPLAY_TYPE, DisplayTypeEnumeration.class); + + public static GraphQLObjectType posterObjectType = + newObject() + .name(POSTER) + .field(newFieldDefinition() + .name(LABEL) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(POSTER_TYPE) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(POSTER_SIZE) + .type(posterSizeEnum)) + .field(newFieldDefinition() + .name(LINES) + .type(GraphQLString)) + .build(); + + public static GraphQLInputObjectType posterInputObjectType = + newInputObject() + .name(POSTER) + .field(newInputObjectField() + .name(LABEL) + .type(GraphQLString)) + .field(newInputObjectField() + .name(POSTER_TYPE) + .type(GraphQLString)) + .field(newInputObjectField() + .name(POSTER_SIZE) + .type(posterSizeEnum)) + .field(newInputObjectField() + .name(LINES) + .type(GraphQLString)) + .build(); + + public GraphQLObjectType createObjectType(GraphQLInterfaceType stopPlaceInterface, GraphQLObjectType validBetweenObjectType) { + return newObject() + .name(OUTPUT_TYPE_INFO_SPOT) + .field(netexIdFieldDefinition) + .field(newFieldDefinition() + .name(VERSION) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(VALID_BETWEEN) + .type(validBetweenObjectType)) + .field(geometryFieldDefinition) + .field(newFieldDefinition() + .name(LABEL) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(PURPOSE) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(POSTER_PLACE_TYPE) + .type(posterPlaceTypeEnum)) + .field(newFieldDefinition() + .name(POSTER_PLACE_SIZE) + .type(posterSizeEnum)) + .field(newFieldDefinition() + .name(DESCRIPTION) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(BACKLIGHT) + .type(GraphQLBoolean)) + .field(newFieldDefinition() + .name(MAINTENANCE) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(ZONE_LABEL) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(RAIL_INFORMATION) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(FLOOR) + .type(GraphQLString)) + .field(newFieldDefinition() + .name(SPEECH_PROPERTY) + .type(GraphQLBoolean)) + .field(newFieldDefinition() + .name(DISPLAY_TYPE) + .type(displayTypeEnum)) +// .field(newFieldDefinition() +// .name(POSTER) +// .type(new GraphQLList(posterObjectType))) +// .field(newFieldDefinition() +// .name(ON_STOP_PLACE) +// .type(new GraphQLList(GraphQLString))) + .build(); + } + + + public GraphQLInputObjectType createInputObjectType(GraphQLInputObjectType validBetweenInputObjectType) { + return newInputObject() + .name(INPUT_TYPE_INFO_SPOT) + .field(newInputObjectField() + .name(ID) + .type(GraphQLString)) + .field(newInputObjectField() + .name(LABEL) + .type(GraphQLString)) + .field(newInputObjectField() + .name(PURPOSE) + .type(GraphQLString)) + .field(newInputObjectField() + .name(POSTER_PLACE_TYPE) + .type(posterPlaceTypeEnum)) + .field(newInputObjectField() + .name(POSTER_PLACE_SIZE) + .type(posterSizeEnum)) + .field(newInputObjectField() + .name(DESCRIPTION) + .type(GraphQLString)) + .field(newInputObjectField() + .name(BACKLIGHT) + .type(GraphQLBoolean)) + .field(newInputObjectField() + .name(MAINTENANCE) + .type(GraphQLString)) + .field(newInputObjectField() + .name(ZONE_LABEL) + .type(GraphQLString)) + .field(newInputObjectField() + .name(RAIL_INFORMATION) + .type(GraphQLString)) + .field(newInputObjectField() + .name(FLOOR) + .type(GraphQLString)) + .field(newInputObjectField() + .name(SPEECH_PROPERTY) + .type(GraphQLBoolean)) + .field(newInputObjectField() + .name(DISPLAY_TYPE) + .type(displayTypeEnum)) +// .field(newInputObjectField() +// .name(POSTER) +// .type(new GraphQLList(posterObjectType))) +// .field(newInputObjectField() +// .name(ON_STOP_PLACE) +// .type(new GraphQLList(GraphQLString))) + .build(); + + } +} diff --git a/src/main/java/org/rutebanken/tiamat/service/InfoSpotStopPlaceResolver.java b/src/main/java/org/rutebanken/tiamat/service/InfoSpotStopPlaceResolver.java new file mode 100644 index 000000000..fe880622c --- /dev/null +++ b/src/main/java/org/rutebanken/tiamat/service/InfoSpotStopPlaceResolver.java @@ -0,0 +1,33 @@ +package org.rutebanken.tiamat.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.jvnet.hk2.annotations.Service; +import org.rutebanken.tiamat.model.InfoSpot; +import org.rutebanken.tiamat.model.StopPlace; +import org.rutebanken.tiamat.repository.reference.ReferenceResolver; +import org.springframework.beans.factory.annotation.Autowired; + +import static java.util.stream.Collectors.toList; + +public class InfoSpotStopPlaceResolver { + + + private ReferenceResolver referenceResolver; + + public List resolve(InfoSpot infoSpot) { + if(infoSpot.getStopPlaces() != null) { + + return infoSpot.getStopPlaces() + .stream() + .map(ref -> { + StopPlace stopPlace = referenceResolver.resolve(ref); + return stopPlace; + }) + .filter(Objects::nonNull) + .collect(toList()); + } + return new ArrayList<>(); + } +} diff --git a/src/main/resources/db/migration/V50__add_info_spots.sql b/src/main/resources/db/migration/V50__add_info_spots.sql new file mode 100644 index 000000000..701d40163 --- /dev/null +++ b/src/main/resources/db/migration/V50__add_info_spots.sql @@ -0,0 +1,58 @@ +create table info_spot +( + id bigint primary key, + netex_id character varying(255), + changed timestamp without time zone, + created timestamp without time zone, + from_date timestamp without time zone, + to_date timestamp without time zone, + version bigint not null, + version_comment character varying(255), + changed_by character varying(255), + + label character varying(255), + purpose character varying(255), + poster_place_type character varying(255), + poster_place_size character varying(255), + description text, + backlight boolean, + maintenance character varying(255), + zone_label character varying(255), + rail_information character varying(255), + floor character varying(255), + speech_property boolean, + display_type character varying(255) +); + +create table info_spot_poster +( + label character varying(255) primary key, + info_spot_id bigint not null, + poster_type character varying(255), + poster_size character varying(255), + lines character varying(255), + foreign key (info_spot_id) references info_spot (id) on delete cascade +); + +create table info_spot_stop_place +( + info_spot_id bigint not null, + ref character varying(255), + version character varying(255), + foreign key (info_spot_id) references info_spot (id) on delete cascade +); + +create table info_spot_key_values +( + info_spot_id bigint not null, + key_values_id bigint NOT NULL, + key_values_key character varying(255) NOT NULL, + foreign key (info_spot_id) references info_spot (id) on delete cascade +); + +CREATE SEQUENCE info_spot_seq + START WITH 1 + INCREMENT BY 10 + NO MINVALUE + NO MAXVALUE + CACHE 1; diff --git a/src/test/java/org/rutebanken/tiamat/TiamatIntegrationTest.java b/src/test/java/org/rutebanken/tiamat/TiamatIntegrationTest.java index 9a31b8bbc..2557fb285 100644 --- a/src/test/java/org/rutebanken/tiamat/TiamatIntegrationTest.java +++ b/src/test/java/org/rutebanken/tiamat/TiamatIntegrationTest.java @@ -25,6 +25,7 @@ import org.rutebanken.tiamat.repository.FareZoneRepository; import org.rutebanken.tiamat.repository.GroupOfStopPlacesRepository; import org.rutebanken.tiamat.repository.GroupOfTariffZonesRepository; +import org.rutebanken.tiamat.repository.InfoSpotRepository; import org.rutebanken.tiamat.repository.ParkingRepository; import org.rutebanken.tiamat.repository.PathJunctionRepository; import org.rutebanken.tiamat.repository.PathLinkRepository; @@ -113,6 +114,8 @@ public abstract class TiamatIntegrationTest { @Autowired protected FareZoneRepository fareZoneRepository; + @Autowired + protected InfoSpotRepository infoSpotRepository; @Autowired protected HazelcastInstance hazelcastInstance; @@ -182,6 +185,8 @@ public void clearRepositories() { fareZoneRepository.flush(); tariffZonesLookupService.resetFareZone(); + infoSpotRepository.deleteAll(); + infoSpotRepository.flush(); clearIdGeneration(); diff --git a/src/test/java/org/rutebanken/tiamat/rest/graphql/GraphQLResourceInfoSpotIntegrationTest.java b/src/test/java/org/rutebanken/tiamat/rest/graphql/GraphQLResourceInfoSpotIntegrationTest.java new file mode 100644 index 000000000..d8a7c9482 --- /dev/null +++ b/src/test/java/org/rutebanken/tiamat/rest/graphql/GraphQLResourceInfoSpotIntegrationTest.java @@ -0,0 +1,225 @@ +package org.rutebanken.tiamat.rest.graphql; + +import java.util.Set; +import org.junit.Test; +import org.rutebanken.tiamat.model.InfoSpot; +import org.rutebanken.tiamat.model.InfoSpotPoster; +import org.rutebanken.tiamat.model.PosterPlaceTypeEnumeration; +import org.rutebanken.tiamat.model.PosterSizeEnumeration; +import org.rutebanken.tiamat.model.StopPlaceReference; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.in; +import static org.hamcrest.Matchers.notNullValue; + +public class GraphQLResourceInfoSpotIntegrationTest extends AbstractGraphQLResourceIntegrationTest { + + @Test + public void listInfoSpots() throws Exception { + String testLabel = "I9876"; + + InfoSpot infoSpot = new InfoSpot(); + infoSpot.setLabel(testLabel); + infoSpot.setBacklight(true); + infoSpot.setFloor("2"); + infoSpot.setDescription("Descriptive"); + infoSpot.setMaintenance("Maintainer"); + infoSpot.setPosterPlaceSize(PosterSizeEnumeration.CM80x120); + infoSpot.setPosterPlaceType(PosterPlaceTypeEnumeration.STATIC); + infoSpot.setPurpose("Purpose of info"); + infoSpot.setRailInformation("Rail 1"); + infoSpot.setZoneLabel("A"); + + InfoSpotPoster poster = new InfoSpotPoster(); + poster.setLabel("posteri"); + poster.setPosterSize(PosterSizeEnumeration.A3); + poster.setPosterType("Map poster"); + poster.setLines("1, 2, 5"); + + infoSpot.setPosters(Set.of(poster)); + + StopPlaceReference stopPlaceReference = new StopPlaceReference(); + stopPlaceReference.setVersion("1"); + stopPlaceReference.setRef("HSL:StopPlace:1"); + infoSpot.setStopPlaces(Set.of(stopPlaceReference)); + + infoSpotRepository.save(infoSpot); + + String graphQlJsonQuery = "{" + + "\"query\":\"{" + + " infoSpots { " + + " id " + + " label " + + " backlight " + + " floor " + + " description " + + " maintenance " + + " posterPlaceSize " + + " posterPlaceType " + + " purpose " + + " railInformation " + + " zoneLabel " + +// " poster { " + +// " label " + +// " posterSize " + +// " posterType " + +// " lines " + +// " } " + +// " onStopPlace " + + " } " + + "}\"," + + "\"variables\":\"\"}"; + + executeGraphQL(graphQlJsonQuery) + .body("data.infoSpots[0].id", equalTo(infoSpot.getNetexId())) + .body("data.infoSpots[0].label", equalTo(testLabel)) + .body("data.infoSpots[0].backlight", equalTo(infoSpot.getBacklight())) + .body("data.infoSpots[0].floor", equalTo(infoSpot.getFloor())) + .body("data.infoSpots[0].description", equalTo(infoSpot.getDescription())) + .body("data.infoSpots[0].maintenance", equalTo(infoSpot.getMaintenance())) + .body("data.infoSpots[0].posterPlaceSize", equalTo(infoSpot.getPosterPlaceSize().value())) + .body("data.infoSpots[0].posterPlaceType", equalTo(infoSpot.getPosterPlaceType().value())) + .body("data.infoSpots[0].purpose", equalTo(infoSpot.getPurpose())) + .body("data.infoSpots[0].railInformation", equalTo(infoSpot.getRailInformation())) + .body("data.infoSpots[0].zoneLabel", equalTo(infoSpot.getZoneLabel())); + } + + @Test + public void createInfoSpot() throws Exception { + String graphQlJsonQuery = "{" + + "\"query\": \"mutation { " + + " mutateInfoSpot( InfoSpot: {"+ + " label: \\\"new label\\\"" + + " backlight: false" + + " floor: \\\"5\\\"" + + " description: \\\"new description\\\"" + + " maintenance: \\\"new maintainer\\\"" + + " posterPlaceSize: %s".formatted(PosterSizeEnumeration.A4.value()) + + " posterPlaceType: %s".formatted(PosterPlaceTypeEnumeration.SOUND_BEACON.value()) + + " purpose: \\\"new purpose\\\"" + + " railInformation: \\\"new rail info\\\"" + + " zoneLabel: \\\"N\\\"" + + // TODO: poster + // TODO: onStopPlace + " }) { " + + " id " + + " label " + + " backlight " + + " floor " + + " description " + + " maintenance " + + " posterPlaceSize " + + " posterPlaceType " + + " purpose " + + " railInformation " + + " zoneLabel " + +// " poster { " + +// " label " + +// " posterSize " + +// " posterType " + +// " lines " + +// " } " + +// " onStopPlace " + + " } " + + "}\"," + + "\"variables\":\"\"}"; + + executeGraphQL(graphQlJsonQuery) + .body("data.mutateInfoSpot.id", notNullValue()) +// .body("data.mutateInfoSpot.version", equalTo(1)) // TODO + .body("data.mutateInfoSpot.label", equalTo("new label")) + .body("data.mutateInfoSpot.backlight", equalTo(false)) + .body("data.mutateInfoSpot.floor", equalTo("5")) + .body("data.mutateInfoSpot.description", equalTo("new description")) + .body("data.mutateInfoSpot.maintenance", equalTo("new maintainer")) + .body("data.mutateInfoSpot.posterPlaceSize", equalTo(PosterSizeEnumeration.A4.value())) + .body("data.mutateInfoSpot.posterPlaceType", equalTo(PosterPlaceTypeEnumeration.SOUND_BEACON.value())) + .body("data.mutateInfoSpot.purpose", equalTo("new purpose")) + .body("data.mutateInfoSpot.railInformation", equalTo("new rail info")) + .body("data.mutateInfoSpot.zoneLabel", equalTo("N")); + } + + @Test + public void updateInfoSpot() throws Exception { + String testLabel = "I9876"; + + InfoSpot infoSpot = new InfoSpot(); + infoSpot.setLabel(testLabel); + infoSpot.setBacklight(true); + infoSpot.setFloor("2"); + infoSpot.setDescription("Descriptive"); + infoSpot.setMaintenance("Maintainer"); + infoSpot.setPosterPlaceSize(PosterSizeEnumeration.CM80x120); + infoSpot.setPosterPlaceType(PosterPlaceTypeEnumeration.STATIC); + infoSpot.setPurpose("Purpose of info"); + infoSpot.setRailInformation("Rail 1"); + infoSpot.setZoneLabel("A"); + + InfoSpotPoster poster = new InfoSpotPoster(); + poster.setLabel("posteri"); + poster.setPosterSize(PosterSizeEnumeration.A3); + poster.setPosterType("Map poster"); + poster.setLines("1, 2, 5"); + + infoSpot.setPosters(Set.of(poster)); + + StopPlaceReference stopPlaceReference = new StopPlaceReference(); + stopPlaceReference.setVersion("1"); + stopPlaceReference.setRef("HSL:StopPlace:1"); + infoSpot.setStopPlaces(Set.of(stopPlaceReference)); + + infoSpotRepository.save(infoSpot); + + String graphQlJsonQuery = "{" + + "\"query\": \"mutation { " + + " mutateInfoSpot( InfoSpot: {"+ + " id: \\\"%s\\\"".formatted(infoSpot.getNetexId()) + + " label: \\\"new label\\\"" + + " backlight: false" + + " floor: \\\"5\\\"" + + " description: \\\"new description\\\"" + + " maintenance: \\\"new maintainer\\\"" + + " posterPlaceSize: %s".formatted(PosterSizeEnumeration.A4.value()) + + " posterPlaceType: %s".formatted(PosterPlaceTypeEnumeration.SOUND_BEACON.value()) + + " purpose: \\\"new purpose\\\"" + + " railInformation: \\\"new rail info\\\"" + + " zoneLabel: \\\"N\\\"" + + // TODO: poster + // TODO: onStopPlace + " }) { " + + " id " + + " label " + + " backlight " + + " floor " + + " description " + + " maintenance " + + " posterPlaceSize " + + " posterPlaceType " + + " purpose " + + " railInformation " + + " zoneLabel " + +// " poster { " + +// " label " + +// " posterSize " + +// " posterType " + +// " lines " + +// " } " + +// " onStopPlace " + + " } " + + "}\"," + + "\"variables\":\"\"}"; + + executeGraphQL(graphQlJsonQuery) + .body("data.mutateInfoSpot.id", equalTo(infoSpot.getNetexId())) + .body("data.mutateInfoSpot.label", equalTo("new label")) + .body("data.mutateInfoSpot.backlight", equalTo(false)) + .body("data.mutateInfoSpot.floor", equalTo("5")) + .body("data.mutateInfoSpot.description", equalTo("new description")) + .body("data.mutateInfoSpot.maintenance", equalTo("new maintainer")) + .body("data.mutateInfoSpot.posterPlaceSize", equalTo(PosterSizeEnumeration.A4.value())) + .body("data.mutateInfoSpot.posterPlaceType", equalTo(PosterPlaceTypeEnumeration.SOUND_BEACON.value())) + .body("data.mutateInfoSpot.purpose", equalTo("new purpose")) + .body("data.mutateInfoSpot.railInformation", equalTo("new rail info")) + .body("data.mutateInfoSpot.zoneLabel", equalTo("N")); + } +}