From 668c5903064c3caa0d91e752a58c9203d8638262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Tue, 19 Nov 2024 10:47:01 +0100 Subject: [PATCH] Device, Property, Dummy, Events working --- .gitignore | 4 + application.properties | 8 ++ pom.xml | 53 ++++++++++ src/main/java/de/ph87/home/Backend.java | 13 +++ src/main/java/de/ph87/home/device/Device.java | 39 ++++++++ .../de/ph87/home/device/DeviceController.java | 50 ++++++++++ .../java/de/ph87/home/device/DeviceDto.java | 44 +++++++++ .../java/de/ph87/home/device/DeviceEvent.java | 21 ++++ .../de/ph87/home/device/DeviceFilter.java | 39 ++++++++ .../de/ph87/home/device/DeviceNotFound.java | 14 +++ .../de/ph87/home/device/DeviceRepository.java | 15 +++ .../de/ph87/home/device/DeviceService.java | 78 +++++++++++++++ .../java/de/ph87/home/dummy/DummyService.java | 30 ++++++ .../java/de/ph87/home/property/Property.java | 55 +++++++++++ .../de/ph87/home/property/PropertyDto.java | 34 +++++++ .../ph87/home/property/PropertyNotFound.java | 14 +++ .../home/property/PropertyNotWritable.java | 14 +++ .../ph87/home/property/PropertyService.java | 98 +++++++++++++++++++ .../home/property/PropertyTypeMismatch.java | 18 ++++ .../java/de/ph87/home/property/State.java | 25 +++++ src/main/resources/application.properties | 10 ++ 21 files changed, 676 insertions(+) create mode 100644 .gitignore create mode 100644 application.properties create mode 100644 pom.xml create mode 100644 src/main/java/de/ph87/home/Backend.java create mode 100644 src/main/java/de/ph87/home/device/Device.java create mode 100644 src/main/java/de/ph87/home/device/DeviceController.java create mode 100644 src/main/java/de/ph87/home/device/DeviceDto.java create mode 100644 src/main/java/de/ph87/home/device/DeviceEvent.java create mode 100644 src/main/java/de/ph87/home/device/DeviceFilter.java create mode 100644 src/main/java/de/ph87/home/device/DeviceNotFound.java create mode 100644 src/main/java/de/ph87/home/device/DeviceRepository.java create mode 100644 src/main/java/de/ph87/home/device/DeviceService.java create mode 100644 src/main/java/de/ph87/home/dummy/DummyService.java create mode 100644 src/main/java/de/ph87/home/property/Property.java create mode 100644 src/main/java/de/ph87/home/property/PropertyDto.java create mode 100644 src/main/java/de/ph87/home/property/PropertyNotFound.java create mode 100644 src/main/java/de/ph87/home/property/PropertyNotWritable.java create mode 100644 src/main/java/de/ph87/home/property/PropertyService.java create mode 100644 src/main/java/de/ph87/home/property/PropertyTypeMismatch.java create mode 100644 src/main/java/de/ph87/home/property/State.java create mode 100644 src/main/resources/application.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..062ddf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.idea/ +/*.iml +/*.db \ No newline at end of file diff --git a/application.properties b/application.properties new file mode 100644 index 0000000..29f3977 --- /dev/null +++ b/application.properties @@ -0,0 +1,8 @@ +logging.level.de.ph87=DEBUG +#- +spring.datasource.url=jdbc:h2:./database;AUTO_SERVER=TRUE +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +#- +spring.jpa.hibernate.ddl-auto=create diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..81b2651 --- /dev/null +++ b/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + de.ph87 + Home4 + 1.0-SNAPSHOT + + + 21 + 21 + 21 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.projectlombok + lombok + + + + com.h2database + h2 + + + org.postgresql + postgresql + + + + \ No newline at end of file diff --git a/src/main/java/de/ph87/home/Backend.java b/src/main/java/de/ph87/home/Backend.java new file mode 100644 index 0000000..0ee04dc --- /dev/null +++ b/src/main/java/de/ph87/home/Backend.java @@ -0,0 +1,13 @@ +package de.ph87.home; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Backend { + + public static void main(String[] args) { + SpringApplication.run(Backend.class, args); + } + +} \ No newline at end of file diff --git a/src/main/java/de/ph87/home/device/Device.java b/src/main/java/de/ph87/home/device/Device.java new file mode 100644 index 0000000..0782ea6 --- /dev/null +++ b/src/main/java/de/ph87/home/device/Device.java @@ -0,0 +1,39 @@ +package de.ph87.home.device; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.*; + +import java.util.UUID; + +@Entity +@Getter +@ToString +@NoArgsConstructor +public class Device { + + @Id + @NonNull + private String uuid = UUID.randomUUID().toString(); + + @NonNull + @Column(nullable = false) + private String name; + + @NonNull + @Column(nullable = false, unique = true) + private String slug; + + @Setter + @NonNull + @Column(nullable = false) + private String stateProperty; + + public Device(@NonNull final String name, @NonNull final String slug, @NonNull final String stateProperty) { + this.name = name; + this.slug = slug; + this.stateProperty = stateProperty; + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceController.java b/src/main/java/de/ph87/home/device/DeviceController.java new file mode 100644 index 0000000..1c5d9f2 --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceController.java @@ -0,0 +1,50 @@ +package de.ph87.home.device; + +import de.ph87.home.property.PropertyNotFound; +import de.ph87.home.property.PropertyNotWritable; +import de.ph87.home.property.PropertyTypeMismatch; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("Device") +public class DeviceController { + + private final DeviceService deviceService; + + @NonNull + @RequestMapping(value = "list", method = {RequestMethod.GET, RequestMethod.POST}) + private List list(@RequestBody(required = false) @Nullable final DeviceFilter filter, @NonNull final HttpServletRequest request) { + log.debug("list: path={} filter={}", request.getServletPath(), filter); + return deviceService.list(filter); + } + + @NonNull + @GetMapping("get/{uuidOrSlug}") + private DeviceDto get(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) throws DeviceNotFound { + log.debug("get: path={}", request.getServletPath()); + return deviceService.toDto(uuidOrSlug); + } + + @Nullable + @GetMapping("getState/{uuidOrSlug}") + private Boolean getState(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) throws DeviceNotFound { + log.debug("getState: path={}", request.getServletPath()); + return deviceService.toDto(uuidOrSlug).getStateValue(); + } + + @GetMapping("setState/{uuidOrSlug}/{state}") + private void setState(@PathVariable @NonNull final String uuidOrSlug, @PathVariable final boolean state, @NonNull final HttpServletRequest request) throws PropertyNotFound, DeviceNotFound, PropertyNotWritable, PropertyTypeMismatch { + log.debug("setState: path={}", request.getServletPath()); + deviceService.setState(uuidOrSlug, state); + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceDto.java b/src/main/java/de/ph87/home/device/DeviceDto.java new file mode 100644 index 0000000..ebf2c3b --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceDto.java @@ -0,0 +1,44 @@ +package de.ph87.home.device; + +import de.ph87.home.property.State; +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +@Getter +@ToString +public class DeviceDto { + + @NonNull + private final String uuid; + + @NonNull + private final String name; + + @NonNull + private final String slug; + + @NonNull + private final String stateProperty; + + @Nullable + private final State state; + + public DeviceDto(@NonNull final Device device, @Nullable final State state) { + this.uuid = device.getUuid(); + this.name = device.getName(); + this.slug = device.getSlug(); + this.stateProperty = device.getStateProperty(); + this.state = state; + } + + @Nullable + public Boolean getStateValue() { + if (state == null) { + return null; + } + return state.getValue(); + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceEvent.java b/src/main/java/de/ph87/home/device/DeviceEvent.java new file mode 100644 index 0000000..92eb71e --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceEvent.java @@ -0,0 +1,21 @@ +package de.ph87.home.device; + +import de.ph87.home.property.PropertyDto; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@RequiredArgsConstructor +public class DeviceEvent { + + private final DeviceDto deviceDto; + + private final PropertyDto propertyDto; + + public boolean isValueDifferent() { + return propertyDto.isValueDifferent(); + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceFilter.java b/src/main/java/de/ph87/home/device/DeviceFilter.java new file mode 100644 index 0000000..1d85d1a --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceFilter.java @@ -0,0 +1,39 @@ +package de.ph87.home.device; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +@Getter +@ToString +public class DeviceFilter { + + @Nullable + @JsonProperty + private Boolean stateNull; + + @Nullable + @JsonProperty + private Boolean stateTrue; + + @Nullable + @JsonProperty + private Boolean stateFalse; + + @SuppressWarnings("RedundantIfStatement") + public boolean filter(@NonNull final DeviceDto dto) { + if (stateNull != null && stateNull != (dto.getState() == null)) { + return false; + } + if (stateTrue != null && (dto.getState() == null || stateTrue != dto.getState().getValue())) { + return false; + } + if (stateFalse != null && (dto.getState() == null || stateFalse == dto.getState().getValue())) { + return false; + } + return true; + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceNotFound.java b/src/main/java/de/ph87/home/device/DeviceNotFound.java new file mode 100644 index 0000000..26a16d5 --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceNotFound.java @@ -0,0 +1,14 @@ +package de.ph87.home.device; + +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class DeviceNotFound extends Exception { + + public DeviceNotFound(@NonNull final String key, final @NonNull String value) { + super("Device not found: %s=%s".formatted(key, value)); + } + +} diff --git a/src/main/java/de/ph87/home/device/DeviceRepository.java b/src/main/java/de/ph87/home/device/DeviceRepository.java new file mode 100644 index 0000000..f4a4c1b --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceRepository.java @@ -0,0 +1,15 @@ +package de.ph87.home.device; + +import lombok.NonNull; +import org.springframework.data.repository.ListCrudRepository; + +import java.util.List; +import java.util.Optional; + +public interface DeviceRepository extends ListCrudRepository { + + Optional findByUuidOrSlug(@NonNull String uuid, @NonNull String slug); + + List findAllByStateProperty(@NonNull String propertyId); + +} diff --git a/src/main/java/de/ph87/home/device/DeviceService.java b/src/main/java/de/ph87/home/device/DeviceService.java new file mode 100644 index 0000000..1527507 --- /dev/null +++ b/src/main/java/de/ph87/home/device/DeviceService.java @@ -0,0 +1,78 @@ +package de.ph87.home.device; + +import de.ph87.home.property.*; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class DeviceService { + + private final PropertyService propertyService; + + private final DeviceRepository deviceRepository; + + private final ApplicationEventPublisher applicationEventPublisher; + + @PostConstruct + public void postConstruct() { + deviceRepository.save(new Device("EG Ambiente", "eg_ambiente", "eg_ambiente")); + deviceRepository.save(new Device("Wohnzimmer Fernseher", "wohnzimmer_fernseher", "wohnzimmer_fernseher")); + deviceRepository.save(new Device("Wohnzimmer Verstärker", "wohnzimmer_verstaerker", "wohnzimmer_verstaerker")); + deviceRepository.save(new Device("Wohnzimmer Fensterdeko", "wohnzimmer_fensterdeko", "wohnzimmer_fensterdeko")); + deviceRepository.save(new Device("Wohnzimmer Hängelampe", "wohnzimmer_haengelampe", "wohnzimmer_haengelampe")); + } + + public void setState(@NonNull final String uuidOrSlug, final boolean state) throws DeviceNotFound, PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch { + log.debug("setState: uuidOrSlug={}, state={}", uuidOrSlug, state); + final Device device = byUuidOrSlug(uuidOrSlug); + log.debug("setState: device={}", device); + propertyService.write(device.getStateProperty(), state); + } + + @NonNull + public DeviceDto toDto(final @NonNull String uuidOrSlug) throws DeviceNotFound { + return toDto(byUuidOrSlug(uuidOrSlug)); + } + + @NonNull + public DeviceDto toDto(@NonNull final Device device) { + final State state = propertyService.readSafe(device.getStateProperty(), Boolean.class); + return new DeviceDto(device, state); + } + + @NonNull + private Device byUuidOrSlug(@NonNull final String uuidOrSlug) throws DeviceNotFound { + return deviceRepository.findByUuidOrSlug(uuidOrSlug, uuidOrSlug).orElseThrow(() -> new DeviceNotFound("uuidOrSlug", uuidOrSlug)); + } + + @NonNull + public List list(@Nullable final DeviceFilter filter) { + return deviceRepository.findAll().stream().map(this::toDto).filter(device -> filter == null || filter.filter(device)).toList(); + } + + @EventListener(PropertyDto.class) + public void onPropertyChange(@NonNull final PropertyDto dto) { + deviceRepository.findAllByStateProperty(dto.getId()) + .forEach(device -> { + final DeviceEvent deviceEvent = new DeviceEvent(toDto(device), dto); + log.debug("Device updated: {}", deviceEvent.getDeviceDto()); + if (deviceEvent.isValueDifferent()) { + log.info("Device changed: {}", deviceEvent.getDeviceDto()); + } + applicationEventPublisher.publishEvent(deviceEvent); + }); + } + +} diff --git a/src/main/java/de/ph87/home/dummy/DummyService.java b/src/main/java/de/ph87/home/dummy/DummyService.java new file mode 100644 index 0000000..4b021ac --- /dev/null +++ b/src/main/java/de/ph87/home/dummy/DummyService.java @@ -0,0 +1,30 @@ +package de.ph87.home.dummy; + +import de.ph87.home.property.PropertyService; +import de.ph87.home.property.State; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DummyService { + + private final PropertyService propertyService; + + @PostConstruct + public void postConstruct() { + register("eg_ambiente"); + register("wohnzimmer_fernseher"); + register("wohnzimmer_verstaerker"); + register("wohnzimmer_fensterdeko"); + register("wohnzimmer_haengelampe"); + } + + private void register(final String id) { + propertyService.register(id, Boolean.class, (property, value) -> property.setState(new State<>(value))); + } + +} diff --git a/src/main/java/de/ph87/home/property/Property.java b/src/main/java/de/ph87/home/property/Property.java new file mode 100644 index 0000000..78c27df --- /dev/null +++ b/src/main/java/de/ph87/home/property/Property.java @@ -0,0 +1,55 @@ +package de.ph87.home.property; + +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@Getter +@ToString +@RequiredArgsConstructor +public class Property { + + @NonNull + private final String id; + + @NonNull + private final Class type; + + @Nullable + private final BiConsumer, T> write; + + @NonNull + private final Consumer> onStateSet; + + @Nullable + private State lastState = null; + + @Nullable + private State state = null; + + private boolean valueDifferent = false; + + public void setState(@Nullable final State state) { + this.lastState = this.state; + this.state = state; + this.valueDifferent = (lastState == null) == (state == null) && (lastState == null || Objects.equals(lastState.getValue(), state.getValue())); + this.onStateSet.accept(this); + } + + public void write(@NonNull final Object value) throws PropertyNotWritable, PropertyTypeMismatch { + if (!type.isInstance(value)) { + throw new PropertyTypeMismatch(this, value); + } + if (write == null) { + throw new PropertyNotWritable(this); + } + write.accept(this, type.cast(value)); + } + +} diff --git a/src/main/java/de/ph87/home/property/PropertyDto.java b/src/main/java/de/ph87/home/property/PropertyDto.java new file mode 100644 index 0000000..798d959 --- /dev/null +++ b/src/main/java/de/ph87/home/property/PropertyDto.java @@ -0,0 +1,34 @@ +package de.ph87.home.property; + +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +@Getter +@ToString +public class PropertyDto { + + @NonNull + private final String id; + + @NonNull + private final Class type; + + @Nullable + private final State lastState; + + @Nullable + private final State state; + + private final boolean valueDifferent; + + public PropertyDto(@NonNull final Property property) { + this.id = property.getId(); + this.type = property.getType(); + this.state = property.getState(); + this.lastState = property.getLastState(); + this.valueDifferent = property.isValueDifferent(); + } + +} diff --git a/src/main/java/de/ph87/home/property/PropertyNotFound.java b/src/main/java/de/ph87/home/property/PropertyNotFound.java new file mode 100644 index 0000000..f0f4503 --- /dev/null +++ b/src/main/java/de/ph87/home/property/PropertyNotFound.java @@ -0,0 +1,14 @@ +package de.ph87.home.property; + +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class PropertyNotFound extends Exception { + + public PropertyNotFound(@NonNull final String id) { + super("Property not found: id=" + id); + } + +} diff --git a/src/main/java/de/ph87/home/property/PropertyNotWritable.java b/src/main/java/de/ph87/home/property/PropertyNotWritable.java new file mode 100644 index 0000000..f6534aa --- /dev/null +++ b/src/main/java/de/ph87/home/property/PropertyNotWritable.java @@ -0,0 +1,14 @@ +package de.ph87.home.property; + +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class PropertyNotWritable extends Exception { + + public PropertyNotWritable(@NonNull final Property property) { + super("Property not writable: id=" + property.getId()); + } + +} diff --git a/src/main/java/de/ph87/home/property/PropertyService.java b/src/main/java/de/ph87/home/property/PropertyService.java new file mode 100644 index 0000000..72d92b8 --- /dev/null +++ b/src/main/java/de/ph87/home/property/PropertyService.java @@ -0,0 +1,98 @@ +package de.ph87.home.property; + +import jakarta.annotation.Nullable; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PropertyService { + + private final ApplicationEventPublisher applicationEventPublisher; + + private final List> propertyList = new ArrayList<>(); + + @Nullable + public State readSafe(final @NonNull String id, @NonNull final Class type) { + try { + return this.read(id, type); + } catch (PropertyTypeMismatch | PropertyNotFound e) { + log.error(e.getMessage()); + return null; + } + } + + @Nullable + public State read(@NonNull final String id, @NonNull final Class type) throws PropertyNotFound, PropertyTypeMismatch { + log.debug("read: id={}", id); + final Property property = byIdAndType(id, type); + if (property.getState() == null) { + return null; + } + if (type.isInstance(property.getState().getValue())) { + //noinspection unchecked + return (State) property.getState(); + } + throw new PropertyTypeMismatch(property, type); + } + + public void write(@NonNull final String id, @NonNull final Object value) throws PropertyNotFound, PropertyTypeMismatch, PropertyNotWritable { + log.debug("write: id={}, type={}, value={}", id, value.getClass().getSimpleName(), value); + final Property property = byIdAndType(id, value.getClass()); + property.write(value); + } + + @NonNull + private Property byIdAndType(final String id, final Class type) throws PropertyNotFound, PropertyTypeMismatch { + final Property property = findById(id).orElseThrow(() -> new PropertyNotFound(id)); + if (type != property.getType()) { + throw new PropertyTypeMismatch(property, type); + } + return property; + } + + @NonNull + private Optional> findById(final @NonNull String id) { + final Optional> optional; + synchronized (propertyList) { + optional = propertyList.stream().filter(p -> p.getId().equals(id)).findFirst(); + } + return optional; + } + + @SuppressWarnings("UnusedReturnValue") + public Property register(@NonNull final String id, final Class type, final BiConsumer, TYPE> write) { + if (id.isEmpty()) { + throw new RuntimeException(); + } + final Property property = new Property<>(id, type, write, this::onStateSet); + synchronized (propertyList) { + propertyList.add(property); + } + return property; + } + + private void onStateSet(@NonNull final Property property) { + final PropertyDto dto = toDto(property); + log.debug("Property updated: {}", dto); + if (dto.isValueDifferent()) { + log.info("Property changed: {}", dto); + } + applicationEventPublisher.publishEvent(dto); + } + + @NonNull + private PropertyDto toDto(@NonNull final Property property) { + return new PropertyDto<>(property); + } + +} diff --git a/src/main/java/de/ph87/home/property/PropertyTypeMismatch.java b/src/main/java/de/ph87/home/property/PropertyTypeMismatch.java new file mode 100644 index 0000000..a5e04a2 --- /dev/null +++ b/src/main/java/de/ph87/home/property/PropertyTypeMismatch.java @@ -0,0 +1,18 @@ +package de.ph87.home.property; + +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class PropertyTypeMismatch extends Exception { + + public PropertyTypeMismatch(@NonNull final Property property, @NonNull final Class type) { + super("Property type mismatch: id=%s, expected=%s, given=%s".formatted(property.getId(), property.getType().getSimpleName(), type.getSimpleName())); + } + + public PropertyTypeMismatch(@NonNull final Property property, @NonNull final Object value) { + super("Property type mismatch: id=%s, expected=%s, given=%s, value=%s".formatted(property.getId(), property.getType().getSimpleName(), value.getClass().getSimpleName(), value)); + } + +} diff --git a/src/main/java/de/ph87/home/property/State.java b/src/main/java/de/ph87/home/property/State.java new file mode 100644 index 0000000..6c539fc --- /dev/null +++ b/src/main/java/de/ph87/home/property/State.java @@ -0,0 +1,25 @@ +package de.ph87.home.property; + +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +import java.time.ZonedDateTime; + +@Getter +@ToString +public class State { + + @NonNull + private final ZonedDateTime timestamp; + + @Nullable + private final T value; + + public State(@Nullable final T value) { + this.timestamp = ZonedDateTime.now(); + this.value = value; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..f9fffb6 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,10 @@ +logging.level.root=WARN +logging.level.de.ph87=INFO +#- +spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl +spring.jpa.hibernate.ddl-auto=update +spring.jpa.open-in-view=false +#- +spring.jackson.serialization.indent_output=true +#- +spring.main.banner-mode=off