Device, Property, Dummy, Events working
This commit is contained in:
commit
668c590306
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target/
|
||||
/.idea/
|
||||
/*.iml
|
||||
/*.db
|
||||
8
application.properties
Normal file
8
application.properties
Normal file
@ -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
|
||||
53
pom.xml
Normal file
53
pom.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.ph87</groupId>
|
||||
<artifactId>Home4</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.5</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
13
src/main/java/de/ph87/home/Backend.java
Normal file
13
src/main/java/de/ph87/home/Backend.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
39
src/main/java/de/ph87/home/device/Device.java
Normal file
39
src/main/java/de/ph87/home/device/Device.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
50
src/main/java/de/ph87/home/device/DeviceController.java
Normal file
50
src/main/java/de/ph87/home/device/DeviceController.java
Normal file
@ -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<DeviceDto> 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);
|
||||
}
|
||||
|
||||
}
|
||||
44
src/main/java/de/ph87/home/device/DeviceDto.java
Normal file
44
src/main/java/de/ph87/home/device/DeviceDto.java
Normal file
@ -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<Boolean> state;
|
||||
|
||||
public DeviceDto(@NonNull final Device device, @Nullable final State<Boolean> 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();
|
||||
}
|
||||
|
||||
}
|
||||
21
src/main/java/de/ph87/home/device/DeviceEvent.java
Normal file
21
src/main/java/de/ph87/home/device/DeviceEvent.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
39
src/main/java/de/ph87/home/device/DeviceFilter.java
Normal file
39
src/main/java/de/ph87/home/device/DeviceFilter.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/de/ph87/home/device/DeviceNotFound.java
Normal file
14
src/main/java/de/ph87/home/device/DeviceNotFound.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
15
src/main/java/de/ph87/home/device/DeviceRepository.java
Normal file
15
src/main/java/de/ph87/home/device/DeviceRepository.java
Normal file
@ -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<Device, String> {
|
||||
|
||||
Optional<Device> findByUuidOrSlug(@NonNull String uuid, @NonNull String slug);
|
||||
|
||||
List<Device> findAllByStateProperty(@NonNull String propertyId);
|
||||
|
||||
}
|
||||
78
src/main/java/de/ph87/home/device/DeviceService.java
Normal file
78
src/main/java/de/ph87/home/device/DeviceService.java
Normal file
@ -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<Boolean> 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<DeviceDto> 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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/de/ph87/home/dummy/DummyService.java
Normal file
30
src/main/java/de/ph87/home/dummy/DummyService.java
Normal file
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/de/ph87/home/property/Property.java
Normal file
55
src/main/java/de/ph87/home/property/Property.java
Normal file
@ -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<T> {
|
||||
|
||||
@NonNull
|
||||
private final String id;
|
||||
|
||||
@NonNull
|
||||
private final Class<T> type;
|
||||
|
||||
@Nullable
|
||||
private final BiConsumer<Property<T>, T> write;
|
||||
|
||||
@NonNull
|
||||
private final Consumer<Property<T>> onStateSet;
|
||||
|
||||
@Nullable
|
||||
private State<T> lastState = null;
|
||||
|
||||
@Nullable
|
||||
private State<T> state = null;
|
||||
|
||||
private boolean valueDifferent = false;
|
||||
|
||||
public void setState(@Nullable final State<T> 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));
|
||||
}
|
||||
|
||||
}
|
||||
34
src/main/java/de/ph87/home/property/PropertyDto.java
Normal file
34
src/main/java/de/ph87/home/property/PropertyDto.java
Normal file
@ -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<T> {
|
||||
|
||||
@NonNull
|
||||
private final String id;
|
||||
|
||||
@NonNull
|
||||
private final Class<T> type;
|
||||
|
||||
@Nullable
|
||||
private final State<T> lastState;
|
||||
|
||||
@Nullable
|
||||
private final State<T> state;
|
||||
|
||||
private final boolean valueDifferent;
|
||||
|
||||
public PropertyDto(@NonNull final Property<T> property) {
|
||||
this.id = property.getId();
|
||||
this.type = property.getType();
|
||||
this.state = property.getState();
|
||||
this.lastState = property.getLastState();
|
||||
this.valueDifferent = property.isValueDifferent();
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/de/ph87/home/property/PropertyNotFound.java
Normal file
14
src/main/java/de/ph87/home/property/PropertyNotFound.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/de/ph87/home/property/PropertyNotWritable.java
Normal file
14
src/main/java/de/ph87/home/property/PropertyNotWritable.java
Normal file
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
98
src/main/java/de/ph87/home/property/PropertyService.java
Normal file
98
src/main/java/de/ph87/home/property/PropertyService.java
Normal file
@ -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<Property<?>> propertyList = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
public <TYPE> State<TYPE> readSafe(final @NonNull String id, @NonNull final Class<TYPE> type) {
|
||||
try {
|
||||
return this.read(id, type);
|
||||
} catch (PropertyTypeMismatch | PropertyNotFound e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <TYPE> State<TYPE> read(@NonNull final String id, @NonNull final Class<TYPE> 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<TYPE>) 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 <TYPE> Property<?> byIdAndType(final String id, final Class<TYPE> 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<Property<?>> findById(final @NonNull String id) {
|
||||
final Optional<Property<?>> optional;
|
||||
synchronized (propertyList) {
|
||||
optional = propertyList.stream().filter(p -> p.getId().equals(id)).findFirst();
|
||||
}
|
||||
return optional;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public <TYPE> Property<TYPE> register(@NonNull final String id, final Class<TYPE> type, final BiConsumer<Property<TYPE>, TYPE> write) {
|
||||
if (id.isEmpty()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
final Property<TYPE> 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 <T> PropertyDto<T> toDto(@NonNull final Property<T> property) {
|
||||
return new PropertyDto<>(property);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
25
src/main/java/de/ph87/home/property/State.java
Normal file
25
src/main/java/de/ph87/home/property/State.java
Normal file
@ -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<T> {
|
||||
|
||||
@NonNull
|
||||
private final ZonedDateTime timestamp;
|
||||
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
public State(@Nullable final T value) {
|
||||
this.timestamp = ZonedDateTime.now();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
10
src/main/resources/application.properties
Normal file
10
src/main/resources/application.properties
Normal file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user