diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java index 1204a60..4b69a07 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java @@ -4,6 +4,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.AutomationFastUpdateRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableState; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.State; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SwitchableState; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Trigger; import com.google.gson.*; import java.lang.reflect.Type; import org.springframework.context.annotation.Bean; @@ -27,6 +28,7 @@ public class GsonConfig { private static GsonBuilder configureBuilder() { final GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()); + @SuppressWarnings({"rawTypes"}) RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory.of(State.class, "kind") .registerSubtype(SwitchableState.class, "switchableState") @@ -43,6 +45,10 @@ public class GsonConfig { "rangeTrigger"); builder.registerTypeAdapterFactory(runtimeTypeAdapterFactory); builder.registerTypeAdapterFactory(runtimeTypeAdapterFactoryII); + builder.registerTypeAdapter( + Trigger.class, + (JsonSerializer>) + (src, typeOfSrc, context) -> context.serialize((Object) src)); return builder; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java index 6b13ca9..cf2cb56 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java @@ -31,6 +31,11 @@ public class ButtonDimmerController this.buttonDimmerRepository = inputRepository; } + @GetMapping("/{id}") + public ButtonDimmer findById(@PathVariable("id") long id) throws NotFoundException { + return buttonDimmerRepository.findById(id).orElseThrow(NotFoundException::new); + } + @PostMapping public ButtonDimmer create( @Valid @RequestBody final GenericDeviceSaveReguest bd, final Principal principal) @@ -62,7 +67,7 @@ public class ButtonDimmerController break; } - deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName(), false); + deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName()); return buttonDimmer.getOutputs(); } @@ -70,6 +75,6 @@ public class ButtonDimmerController @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/CurtainsController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/CurtainsController.java index 6644135..4065f12 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/CurtainsController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/CurtainsController.java @@ -49,7 +49,7 @@ public class CurtainsController { @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } @PostMapping("/{id}/state") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java index 60a4726..28648c1 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java @@ -66,7 +66,9 @@ public class DimmableLightController extends GuestEnabledController findAll() { - return toList(userRepository.findAll()); + public List findAll() { + return StreamSupport.stream(userRepository.findAll().spliterator(), false) + .map(UserResponse::fromUser) + .collect(Collectors.toList()); } - @PostMapping("/guest") - public User addUserAsGuest(@RequestParam("userId") long id, final Principal principal) + @GetMapping("/hosts") + public List findHosts(final Principal principal) { + final User u = userRepository.findByUsername(principal.getName()); + return u.getHosts().stream().map(UserResponse::fromUser).collect(Collectors.toList()); + } + + @GetMapping("/guests") + public List findGuests(final Principal principal) { + final User u = userRepository.findByUsername(principal.getName()); + return u.getGuests().stream().map(UserResponse::fromUser).collect(Collectors.toList()); + } + + @PutMapping("/guests") + public List setGuests( + @RequestBody @Valid GuestsUpdateRequest g, final Principal principal) throws NotFoundException { - User guest = userRepository.findById(id).orElseThrow(NotFoundException::new); + Iterable guests = userRepository.findAllById(g.ids); User host = userRepository.findByUsername(principal.getName()); - host.addGuest(guest); - guest.addHost(host); - userRepository.save(guest); - return userRepository.save(host); + for (final User oldGuest : host.getGuests()) { + oldGuest.getHosts().remove(host); + } + + final Set oldGuests = Set.copyOf(host.getGuests()); + + for (final User guest : guests) { + host.addGuest(guest); + guest.addHost(host); + } + + userRepository.saveAll(oldGuests); + userRepository.save(host); + return toList(userRepository.saveAll(guests)); } @PutMapping("/permissions") @@ -44,16 +74,4 @@ public class GuestController { currentUser.setCameraEnabled(g.isCameraEnabled()); return userRepository.save(currentUser); } - - @DeleteMapping("/guest") - public void removeUserAsGuest(@RequestParam("userId") long id, final Principal principal) - throws NotFoundException { - User guest = userRepository.findById(id).orElseThrow(NotFoundException::new); - User host = userRepository.findByUsername(principal.getName()); - - host.removeGuest(guest); - guest.getHosts().remove(host); - userRepository.save(host); - userRepository.save(guest); - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java index 4d1a371..51ea46e 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java @@ -91,7 +91,7 @@ public abstract class InputDeviceConnectionController< connector.connect(pair.input, o, true); } - deviceService.saveAllAsOwner(pair.outputs, username, false); + deviceService.saveAllAsOwner(pair.outputs, username); return pair.input.getOutputs(); } @@ -111,7 +111,7 @@ public abstract class InputDeviceConnectionController< connector.connect(pair.input, o, false); } - deviceService.saveAllAsOwner(pair.outputs, username, false); + deviceService.saveAllAsOwner(pair.outputs, username); return pair.input.getOutputs(); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java index 9b30535..1af7a99 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java @@ -27,6 +27,11 @@ public class KnobDimmerController extends InputDeviceConnectionController @PutMapping public RegularLight update( - @Valid @RequestBody SwitchableSaveRequest rl, final Principal principal, Long hostId) + @Valid @RequestBody SwitchableSaveRequest rl, + final Principal principal, + @RequestParam(value = "hostId", required = false) Long hostId) throws NotFoundException { return save( fetchIfOwnerOrGuest(principal, rl.getId(), hostId), @@ -92,7 +94,7 @@ public class RegularLightController extends GuestEnabledController @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } // the full url should be: "/regularLight/{id}/state?sceneId={sceneId} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java index c4540c5..8b7f648 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RoomController.java @@ -75,7 +75,7 @@ public class RoomController { final String username = principal.getName(); final Long userId = userRepository.findByUsername(username).getId(); final String img = r.getImage(); - final Room.Icon icon = r.getIcon(); + final Icon icon = r.getIcon(); final Room newRoom = new Room(); newRoom.setUserId(userId); @@ -95,7 +95,7 @@ public class RoomController { .findByIdAndUsername(id, principal.getName()) .orElseThrow(NotFoundException::new); final String img = r.getImage(); - final Room.Icon icon = r.getIcon(); + final Icon icon = r.getIcon(); if (r.getName() != null) { newRoom.setName(r.getName()); @@ -124,6 +124,11 @@ public class RoomController { roomRepository .findByIdAndUsername(id, principal.getName()) .orElseThrow(NotFoundException::new); + List devices = deviceService.findAll(r.getId(), null, principal.getName()); + for (Device d : devices) { + deviceService.deleteByIdAsOwner(d.getId(), principal.getName()); + } + roomRepository.delete(r); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SceneController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SceneController.java index 0cddba2..2eeaaef 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SceneController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SceneController.java @@ -6,20 +6,13 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SceneSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SceneService; +import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils; import java.security.Principal; import java.util.List; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @EnableAutoConfiguration @@ -27,21 +20,20 @@ import org.springframework.web.bind.annotation.RestController; public class SceneController { @Autowired private SceneRepository sceneRepository; + @Autowired private UserRepository userRepository; @Autowired private SceneService sceneService; - @Autowired private UserRepository userService; - @Autowired private StateRepository> stateService; + @Autowired private StateRepository> stateRepository; @GetMapping - public List findAll(Principal principal) { - return toList(sceneRepository.findByUsername(principal.getName())); - } - - @GetMapping("/{id}") - public @ResponseBody Scene findById(@PathVariable("id") long id, Principal principal) + public List findAll( + Principal principal, @RequestParam(value = "hostId", required = false) Long hostId) throws NotFoundException { - return sceneRepository - .findByIdAndUsername(id, principal.getName()) - .orElseThrow(NotFoundException::new); + if (hostId == null) { + return toList(sceneRepository.findByUsername(principal.getName())); + } else { + Utils.returnIfGuest(userRepository, null, hostId, principal); + return sceneRepository.findByHostId(hostId); + } } @PostMapping @@ -49,26 +41,58 @@ public class SceneController { @Valid @RequestBody SceneSaveRequest s, final Principal principal) { final String username = principal.getName(); - final Long userId = userService.findByUsername(username).getId(); + final Long userId = userRepository.findByUsername(username).getId(); final Scene newScene = new Scene(); newScene.setUserId(userId); newScene.setName(s.getName()); newScene.setGuestAccessEnabled(s.isGuestAccessEnabled()); + newScene.setIcon(s.getIcon()); return sceneRepository.save(newScene); } @PostMapping("/{id}/apply") - public @ResponseBody List apply(@PathVariable("id") long id, final Principal principal) + public @ResponseBody List apply( + @PathVariable("id") long id, + final Principal principal, + @RequestParam(value = "hostId", required = false) Long hostId) throws NotFoundException { - final Scene newScene = + if (hostId == null) { + return sceneService.apply( + sceneRepository + .findByIdAndUsername(id, principal.getName()) + .orElseThrow(NotFoundException::new), + principal.getName(), + false); + } else { + Utils.returnIfGuest(userRepository, null, hostId, principal); + return sceneService.applyAsGuest( + sceneRepository + .findByIdAndUserId(id, hostId) + .orElseThrow(NotFoundException::new), + principal.getName(), + hostId); + } + } + + @PostMapping("/{id}/copyFrom/{copyId}") + public @ResponseBody List> copy( + @PathVariable("id") long id, + @PathVariable("copyId") long copyId, + final Principal principal) + throws NotFoundException { + final Scene scene = sceneRepository .findByIdAndUsername(id, principal.getName()) .orElseThrow(NotFoundException::new); + final Scene copyFrom = + sceneRepository + .findByIdAndUsername(copyId, principal.getName()) + .orElseThrow(NotFoundException::new); - return sceneService.apply(newScene); + return sceneService.copyStates(scene, copyFrom); } @PutMapping("/{id}") @@ -84,6 +108,8 @@ public class SceneController { newScene.setName(s.getName()); } + newScene.setIcon(s.getIcon()); + newScene.setGuestAccessEnabled(s.isGuestAccessEnabled()); return sceneRepository.save(newScene); @@ -91,7 +117,7 @@ public class SceneController { @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") long id) { - stateService.deleteAllBySceneId(id); + stateRepository.deleteAllBySceneId(id); sceneRepository.deleteById(id); } @@ -101,7 +127,7 @@ public class SceneController { */ @GetMapping(path = "/{sceneId}/states") public List> getDevices(@PathVariable("sceneId") long sceneId) { - Iterable> states = stateService.findBySceneId(sceneId); + Iterable> states = stateRepository.findBySceneId(sceneId); return toList(states); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SecurityCameraController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SecurityCameraController.java index c063ebf..1554f0a 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SecurityCameraController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SecurityCameraController.java @@ -61,7 +61,7 @@ public class SecurityCameraController { @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } @PostMapping("/{id}/state") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java index 41b9af0..3833452 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SensorController.java @@ -57,6 +57,6 @@ public class SensorController { @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java index 468cd5d..790aee2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SmartPlugController.java @@ -63,7 +63,7 @@ public class SmartPlugController { @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") long id, final Principal principal) throws NotFoundException { - deviceService.delete(id, principal.getName()); + deviceService.deleteByIdAsOwner(id, principal.getName()); } @PostMapping("/{id}/state") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java index 0847c87..d92cd49 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java @@ -37,6 +37,11 @@ public class SwitchController extends InputDeviceConnectionController ids; +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java index 02a0e35..cf362ac 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RoomSaveRequest.java @@ -1,6 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon; import javax.persistence.Lob; import javax.validation.constraints.NotNull; @@ -9,7 +9,7 @@ public class RoomSaveRequest { /** Room identifier */ private long id; - @NotNull private Room.Icon icon; + @NotNull private Icon icon; /** * Image is to be given as byte[]. In order to get an encoded string from it, the @@ -38,11 +38,11 @@ public class RoomSaveRequest { this.name = name; } - public Room.Icon getIcon() { + public Icon getIcon() { return icon; } - public void setIcon(Room.Icon icon) { + public void setIcon(Icon icon) { this.icon = icon; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SceneSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SceneSaveRequest.java index 080ea2b..e7ce3ad 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SceneSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SceneSaveRequest.java @@ -1,5 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon; import com.sun.istack.NotNull; import javax.persistence.Column; @@ -11,6 +12,8 @@ public class SceneSaveRequest { /** The user given name of this room (e.g. 'Master bedroom') */ @NotNull private String name; + @NotNull private Icon icon; + /** Determines whether a guest can access this scene */ @Column @NotNull private boolean guestAccessEnabled; @@ -33,4 +36,12 @@ public class SceneSaveRequest { public void setName(String name) { this.name = name; } + + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + this.icon = icon; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ThermostatSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ThermostatSaveRequest.java index 3986996..5ac3402 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ThermostatSaveRequest.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ThermostatSaveRequest.java @@ -22,8 +22,6 @@ public class ThermostatSaveRequest { @NotNull private boolean useExternalSensors; - @NotNull private BigDecimal measuredTemperature; - /** State of this thermostat */ @NotNull private boolean turnOn; @@ -74,12 +72,4 @@ public class ThermostatSaveRequest { public void setId(long id) { this.id = id; } - - public BigDecimal getMeasuredTemperature() { - return measuredTemperature; - } - - public void setMeasuredTemperature(BigDecimal measuredTemperature) { - this.measuredTemperature = measuredTemperature; - } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java new file mode 100644 index 0000000..2ed2c09 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java @@ -0,0 +1,19 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; + +public class UserResponse { + private Long id; + private String username; + private String name; + + private UserResponse() {} + + public static UserResponse fromUser(User u) { + final UserResponse us = new UserResponse(); + us.name = u.getName(); + us.id = u.getId(); + us.username = u.getUsername(); + return us; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Automation.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Automation.java index e6b4d0b..e17e870 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Automation.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Automation.java @@ -27,7 +27,7 @@ public class Automation { @GsonExclude private Long userId; - @OneToMany(mappedBy = "automation", orphanRemoval = true) + @OneToMany(mappedBy = "automation", orphanRemoval = true, cascade = CascadeType.REMOVE) private Set> triggers = new HashSet<>(); @OneToMany(mappedBy = "automation", cascade = CascadeType.REMOVE) diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java index 397bb1e..d2231d8 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java @@ -71,14 +71,18 @@ public abstract class Device { @SocketGsonExclude private Set> states = new HashSet<>(); - @Transient @GsonExclude private boolean fromHost = false; + @Transient @GsonExclude private Long fromHostId = null; @Transient @GsonExclude private boolean fromGuest = false; @Transient @GsonExclude private boolean deleted = false; - public boolean isFromHost() { - return fromHost; + public Long getFromHostId() { + return fromHostId; + } + + public void setFromHostId(Long fromHostId) { + this.fromHostId = fromHostId; } public boolean isDeleted() { @@ -97,10 +101,6 @@ public abstract class Device { this.fromGuest = fromGuest; } - public void setFromHost(boolean fromHost) { - this.fromHost = fromHost; - } - public long getId() { return id; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableState.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableState.java index 7c0b8d2..dff0143 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableState.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableState.java @@ -25,4 +25,11 @@ public class DimmableState extends State { public void apply() { getDevice().readStateAndSet(this); } + + @Override + protected State copy() { + final DimmableState d = new DimmableState<>(); + d.setIntensity(intensity); + return d; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Icon.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Icon.java new file mode 100644 index 0000000..0ad9e4d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Icon.java @@ -0,0 +1,103 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import com.google.gson.annotations.SerializedName; + +/** A collection of Semantic UI icons */ +@SuppressWarnings("unused") +public enum Icon { + @SerializedName("home") + HOME("home"), + @SerializedName("coffee") + COFFEE("coffee"), + @SerializedName("beer") + BEER("beer"), + @SerializedName("glass martini") + GLASS_MARTINI("glass martini"), + @SerializedName("film") + FILM("film"), + @SerializedName("video") + VIDEO("video"), + @SerializedName("music") + MUSIC("music"), + @SerializedName("headphones") + HEADPHONES("headphones"), + @SerializedName("fax") + FAX("fax"), + @SerializedName("phone") + PHONE("phone"), + @SerializedName("laptop") + LAPTOP("laptop"), + @SerializedName("bath") + BATH("bath"), + @SerializedName("shower") + SHOWER("shower"), + @SerializedName("bed") + BED("bed"), + @SerializedName("child") + CHILD("child"), + @SerializedName("warehouse") + WAREHOUSE("warehouse"), + @SerializedName("car") + CAR("car"), + @SerializedName("bicycle") + BICYCLE("bicycle"), + @SerializedName("motorcycle") + MOTORCYCLE("motorcycle"), + @SerializedName("archive") + ARCHIVE("archive"), + @SerializedName("boxes") + BOXES("boxes"), + @SerializedName("cubes") + CUBES("cubes"), + @SerializedName("chess") + CHESS("chess"), + @SerializedName("gamepad") + GAMEPAD("gamepad"), + @SerializedName("futbol") + FUTBOL("futbol"), + @SerializedName("table tennis") + TABLE_TENNIS("table tennis"), + @SerializedName("server") + SERVER("server"), + @SerializedName("tv") + TV("tv"), + @SerializedName("heart") + HEART("heart"), + @SerializedName("camera") + CAMERA("camera"), + @SerializedName("trophy") + TROPHY("trophy"), + @SerializedName("wrench") + WRENCH("wrench"), + @SerializedName("image") + IMAGE("image"), + @SerializedName("book") + BOOK("book"), + @SerializedName("university") + UNIVERSITY("university"), + @SerializedName("medkit") + MEDKIT("medkit"), + @SerializedName("paw") + PAW("paw"), + @SerializedName("tree") + TREE("tree"), + @SerializedName("utensils") + UTENSILS("utensils"), + @SerializedName("male") + MALE("male"), + @SerializedName("female") + FEMALE("female"), + @SerializedName("life ring outline") + LIFE_RING_OUTLINE("life ring outline"); + + private String iconName; + + Icon(String s) { + this.iconName = s; + } + + @Override + public String toString() { + return iconName; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java index 34f3824..73d7794 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Room.java @@ -1,7 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; -import com.google.gson.annotations.SerializedName; import io.swagger.annotations.ApiModelProperty; import java.util.HashSet; import java.util.Set; @@ -12,106 +11,6 @@ import javax.validation.constraints.NotNull; @Entity public class Room { - /** A collection of Semantic UI icons */ - @SuppressWarnings("unused") - public enum Icon { - @SerializedName("home") - HOME("home"), - @SerializedName("coffee") - COFFEE("coffee"), - @SerializedName("beer") - BEER("beer"), - @SerializedName("glass martini") - GLASS_MARTINI("glass martini"), - @SerializedName("film") - FILM("film"), - @SerializedName("video") - VIDEO("video"), - @SerializedName("music") - MUSIC("music"), - @SerializedName("headphones") - HEADPHONES("headphones"), - @SerializedName("fax") - FAX("fax"), - @SerializedName("phone") - PHONE("phone"), - @SerializedName("laptop") - LAPTOP("laptop"), - @SerializedName("bath") - BATH("bath"), - @SerializedName("shower") - SHOWER("shower"), - @SerializedName("bed") - BED("bed"), - @SerializedName("child") - CHILD("child"), - @SerializedName("warehouse") - WAREHOUSE("warehouse"), - @SerializedName("car") - CAR("car"), - @SerializedName("bicycle") - BICYCLE("bicycle"), - @SerializedName("motorcycle") - MOTORCYCLE("motorcycle"), - @SerializedName("archive") - ARCHIVE("archive"), - @SerializedName("boxes") - BOXES("boxes"), - @SerializedName("cubes") - CUBES("cubes"), - @SerializedName("chess") - CHESS("chess"), - @SerializedName("gamepad") - GAMEPAD("gamepad"), - @SerializedName("futbol") - FUTBOL("futbol"), - @SerializedName("table tennis") - TABLE_TENNIS("table tennis"), - @SerializedName("server") - SERVER("server"), - @SerializedName("tv") - TV("tv"), - @SerializedName("heart") - HEART("heart"), - @SerializedName("camera") - CAMERA("camera"), - @SerializedName("trophy") - TROPHY("trophy"), - @SerializedName("wrench") - WRENCH("wrench"), - @SerializedName("image") - IMAGE("image"), - @SerializedName("book") - BOOK("book"), - @SerializedName("university") - UNIVERSITY("university"), - @SerializedName("medkit") - MEDKIT("medkit"), - @SerializedName("paw") - PAW("paw"), - @SerializedName("tree") - TREE("tree"), - @SerializedName("utensils") - UTENSILS("utensils"), - @SerializedName("male") - MALE("male"), - @SerializedName("female") - FEMALE("female"), - @SerializedName("life ring outline") - LIFE_RING_OUTLINE("life ring outline"); - - private String iconName; - - Icon(String s) { - this.iconName = s; - } - - @Override - public String toString() { - return iconName; - } - } - @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id", updatable = false, nullable = false, unique = true) diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Scene.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Scene.java index f35d54d..83a9367 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Scene.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Scene.java @@ -39,9 +39,21 @@ public class Scene { @Column(nullable = false) private String name; + @Column(nullable = false) + @NotNull + private Icon icon; + /** Determines whether a guest can access this scene */ @Column private boolean guestAccessEnabled; + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + this.icon = icon; + } + public boolean isGuestAccessEnabled() { return guestAccessEnabled; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SceneRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SceneRepository.java index a4aa68c..028f292 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SceneRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SceneRepository.java @@ -19,4 +19,9 @@ public interface SceneRepository extends CrudRepository { @Query("SELECT s FROM Scene s JOIN s.user u WHERE u.username = ?1") List findByUsername(String username); + + @Query("SELECT s FROM Scene s JOIN s.user u WHERE u.id = ?1 AND s.guestAccessEnabled = true") + List findByHostId(Long hostId); + + Optional findByIdAndUserId(Long id, Long userId); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/State.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/State.java index 01398b8..31cd69c 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/State.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/State.java @@ -45,6 +45,16 @@ public abstract class State { /** Sets the state of the connected device to the state represented by this object. */ public abstract void apply(); + /** Creates a perfect copy of this state, except for the id field and the sceneId/scene */ + protected abstract State copy(); + + public State copyToSceneId(Long sceneId) { + final State s = copy(); + s.setDeviceId(this.deviceId); + s.setSceneId(sceneId); + return s; + } + public long getId() { return id; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java index 4b9ef3a..7825eae 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switch.java @@ -10,7 +10,13 @@ import javax.persistence.*; @Entity public class Switch extends InputDevice implements BooleanTriggerable { - @ManyToMany(cascade = CascadeType.DETACH) + @ManyToMany( + cascade = { + CascadeType.DETACH, + CascadeType.MERGE, + CascadeType.REFRESH, + CascadeType.PERSIST + }) @GsonExclude @SocketGsonExclude @JoinTable( diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java index c7abc7e..9db6361 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Switchable.java @@ -14,7 +14,14 @@ public abstract class Switchable extends OutputDevice { public static final Connector SWITCH_SWITCHABLE_CONNECTOR = Connector.basic(Switch::getOutputs, Switchable::getSwitches); - @ManyToMany(mappedBy = "switchables", cascade = CascadeType.DETACH) + @ManyToMany( + mappedBy = "switchables", + cascade = { + CascadeType.DETACH, + CascadeType.MERGE, + CascadeType.REFRESH, + CascadeType.PERSIST + }) @GsonExclude @SocketGsonExclude private Set inputs = new HashSet<>(); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableState.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableState.java index 67b3118..be21b6e 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableState.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableState.java @@ -22,4 +22,11 @@ public class SwitchableState extends State { public void apply() { getDevice().readStateAndSet(this); } + + @Override + protected State copy() { + final SwitchableState d = new SwitchableState<>(); + d.setOn(on); + return d; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java index e8f0e86..632dfd7 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java @@ -6,9 +6,11 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Transient; import javax.validation.constraints.NotNull; +import org.springframework.stereotype.Component; /** A thermostat capable of controlling cooling and heating. */ @Entity +@Component public class Thermostat extends Switchable implements BooleanTriggerable { @Override @@ -22,32 +24,29 @@ public class Thermostat extends Switchable implements BooleanTriggerable { computeState(); } - /** - * Computes the new thermostat state, for when the thermostat is on; - * - * @return true if the state changed, false if not; - */ - public boolean computeState() { + /** Computes the new thermostat state, for when the thermostat is on */ + public void computeState() { if (mode == Thermostat.Mode.OFF) { - return false; + return; } BigDecimal measured = this.getMeasuredTemperature(); BigDecimal target = this.getTargetTemperature(); + + if (measured == null) { + this.setMode(Thermostat.Mode.IDLE); + return; + } + BigDecimal delta = target.subtract(measured); if (delta.abs().doubleValue() < 0.25) { - if (this.getMode() == Thermostat.Mode.IDLE) return false; this.setMode(Thermostat.Mode.IDLE); } else if (delta.signum() > 0) { - if (this.getMode() == Thermostat.Mode.HEATING) return false; this.setMode(Thermostat.Mode.HEATING); } else { - if (this.getMode() == Thermostat.Mode.COOLING) return false; this.setMode(Thermostat.Mode.COOLING); } - - return true; } @Override @@ -87,6 +86,18 @@ public class Thermostat extends Switchable implements BooleanTriggerable { this.mode = Mode.OFF; } + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Thermostat{"); + sb.append("targetTemperature=").append(targetTemperature); + sb.append(", internalSensorTemperature=").append(internalSensorTemperature); + sb.append(", mode=").append(mode); + sb.append(", measuredTemperature=").append(measuredTemperature); + sb.append(", useExternalSensors=").append(useExternalSensors); + sb.append('}'); + return sb.toString(); + } + public void setMode(Mode state) { this.mode = state; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java index e701166..ec0f43f 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java @@ -78,7 +78,7 @@ public class UpdateTasks { c.forEach( s -> sensorSocketEndpoint.queueDeviceUpdate( - s, sensorRepository.findUser(s.getId()))); + s, sensorRepository.findUser(s.getId()), false, null, false)); } /** Sends device updates through sensor socket in batch every one second */ diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java index 0bc2a6e..d2b02a6 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java @@ -9,6 +9,8 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -37,7 +39,7 @@ public class DeviceService { } } - private void triggerTriggers(Device device) { + private void triggerTriggers(Device device, final String username) { final long deviceId = device.getId(); @@ -57,7 +59,7 @@ public class DeviceService { sceneRepository .findById(t.getSceneId()) .orElseThrow(IllegalStateException::new)) - .forEach(sceneService::apply); + .forEach((s) -> sceneService.apply(s, username, true)); } public List findAll(Long hostId, String username) throws NotFoundException { @@ -68,6 +70,7 @@ public class DeviceService { throws NotFoundException { try { Iterable devices; + User host = null; if (hostId == null) { if (roomId != null) { roomRepository @@ -79,8 +82,7 @@ public class DeviceService { } } else { final User guest = userRepository.findByUsername(username); - final User host = - userRepository.findById(hostId).orElseThrow(NotFoundException::new); + host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); if (!guest.getHosts().contains(host)) { throw new NotFoundException(); @@ -97,98 +99,148 @@ public class DeviceService { } } - for (Device d : devices) { - if (d instanceof Thermostat) { - thermostatService.populateMeasuredTemperature((Thermostat) d); - } - } + populateComputedFields(devices); - return toList(devices); + if (host != null && !host.isCameraEnabled()) { + return StreamSupport.stream(devices.spliterator(), true) + .filter(d -> !(d instanceof SecurityCamera)) + .collect(Collectors.toList()); + } else { + return toList(devices); + } } catch (NotFoundException e) { e.printStackTrace(); throw e; } } + public void populateComputedFields(Iterable devices) { + for (Device d : devices) { + if (d instanceof Thermostat) { + thermostatService.populateMeasuredTemperature((Thermostat) d); + } + } + } + public T saveAsGuest(T device, String guestUsername, Long hostId) throws NotFoundException { final User currentUser = userRepository.findByUsername(guestUsername); final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); if (!host.getGuests().contains(currentUser)) throw new NotFoundException(); renameIfDuplicate(device, host.getUsername()); + device = deviceRepository.save(device); + propagateUpdateAsGuest(device, host, currentUser); + return device; + } + + private void propagateUpdateAsGuest(Device device, User host, User guest) { final Set guests = Set.copyOf(host.getGuests()); // We're telling the host that a guest has modified a device. Therefore, fromGuest becomes // true. - device.setFromHost(false); - device.setFromGuest(true); // broadcast device update to host - endpoint.queueDeviceUpdate(device, host); + endpoint.queueDeviceUpdate(device, host, true, null, false); // We're telling all guests that a higher entity has issued a device update. Therefore, // fromHost becomes true. - device.setFromHost(true); - device.setFromGuest(false); - for (final User guest : guests) { - if (guest.equals(currentUser)) { + for (final User aGuest : guests) { + if (aGuest.equals(guest)) { continue; } // enqueue all device updates for all other guests - endpoint.queueDeviceUpdate(device, guest); + endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false); } - - return device; } - private void propagateUpdateAsOwner(Device device, String username) { + List saveAllAsGuestSceneApplication( + List devices, String guestUsername, Long hostId) { + final User guest = userRepository.findByUsername(guestUsername); + final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new); + deviceRepository.saveAll(devices); + devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest)); + return devices; + } + + /** + * Propagates the update through the socket assuming that the user that modified the device is + * the owner of that device + * + * @param device the updated device + * @param username the username of the owner of that device + * @param causedByTrigger if true, send the update to the owner as well + */ + private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) { final User user = userRepository.findByUsername(username); final Set guests = user.getGuests(); // make sure we're broadcasting from host - device.setFromHost(true); - device.setFromGuest(false); for (final User guest : guests) { // broadcast to endpoint the object device, with receiving user set to guest - endpoint.queueDeviceUpdate(device, guest); + endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false); + } + + if (causedByTrigger) { + endpoint.queueDeviceUpdate(device, user, false, user.getId(), false); } } + /** + * Saves all the devices given in devices assuming that the owner updated them in one way or + * another. Takes care of the appropriate websocket updates and trigger checking as well. No + * checking is done to verify that the user whose username is given is in fact the owner of + * these devices + * + * @param devices the list of devices to save + * @param username the username of the owner of these devices + * @param fromScene true if the update comes from the a scene application side effect. Disables + * trigger checking to avoid recursive invocations of automations + * @param fromTrigger true if the update comes from a scene application executed by an + * automation. Propagates updated through socket to owner as well. No effect if fromScene is + * false. + * @param the type of device contained in the list + * @return the updated list of devices, ready to be fed to GSON + */ public List saveAllAsOwner( - Iterable devices, String username, boolean fromScene) { + Iterable devices, String username, boolean fromScene, boolean fromTrigger) { devices.forEach(d -> renameIfDuplicate(d, username)); devices = deviceRepository.saveAll(devices); - devices.forEach((d) -> propagateUpdateAsOwner(d, username)); + devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger)); if (!fromScene) { - devices.forEach(this::triggerTriggers); + devices.forEach((d) -> triggerTriggers(d, username)); } return toList(devices); } - public T saveAsOwner(T device, String username, boolean fromScene) { + public List saveAllAsOwner(Iterable devices, String username) { + return saveAllAsOwner(devices, username, false, false); + } + + public T saveAsOwner(T device, String username) { renameIfDuplicate(device, username); device = deviceRepository.save(device); - propagateUpdateAsOwner(device, username); + propagateUpdateAsOwner(device, username, false); - if (!fromScene) { - triggerTriggers(device); - } + triggerTriggers(device, username); return device; } - public T saveAsOwner(T device, String username) { - return saveAsOwner(device, username, false); - } - - public void delete(Long id, String username) throws NotFoundException { - Device device = + public void deleteByIdAsOwner(Long id, String username) throws NotFoundException { + Device d = deviceRepository .findByIdAndUsername(id, username) .orElseThrow(NotFoundException::new); - deviceRepository.delete(device); - propagateUpdateAsOwner(device, username); + final User user = userRepository.findByUsername(username); + final Set guests = user.getGuests(); + // make sure we're broadcasting from host + for (final User guest : guests) { + // broadcast to endpoint the object device, with receiving user set to guest + endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true); + } + + deviceRepository.delete(d); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/MotionSensorService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/MotionSensorService.java index 01a7401..f4288ee 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/MotionSensorService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/MotionSensorService.java @@ -14,7 +14,7 @@ public class MotionSensorService { @Autowired private MotionSensorRepository motionSensorRepository; /** - * Updates detection status of given motion sensor and propagates update throgh socket + * Updates detection status of given motion sensor and propagates update through socket * * @param sensor the motion sensor to update * @param detected the new detection status @@ -26,7 +26,7 @@ public class MotionSensorService { final MotionSensor toReturn = deviceService.saveAsOwner(sensor, username); sensorSocketEndpoint.queueDeviceUpdate( - sensor, motionSensorRepository.findUser(sensor.getId())); + sensor, motionSensorRepository.findUser(sensor.getId()), false, null, false); return toReturn; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java index 826d896..3c91277 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java @@ -1,9 +1,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.service; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Scene; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.State; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -13,16 +10,38 @@ import org.springframework.stereotype.Component; public class SceneService { @Autowired private DeviceRepository deviceRepository; + @Autowired private DeviceService deviceService; + @Autowired private StateRepository> stateRepository; - public List apply(Scene newScene) { + private List copyStatesToDevices(Scene fromScene) { final List updated = new ArrayList<>(); - for (final State s : newScene.getStates()) { + for (final State s : fromScene.getStates()) { s.apply(); updated.add(s.getDevice()); } - deviceRepository.saveAll(updated); + deviceService.populateComputedFields(updated); return updated; } + + public List apply(Scene newScene, String username, boolean fromTrigger) { + List updated = copyStatesToDevices(newScene); + deviceService.saveAllAsOwner(updated, username, true, fromTrigger); + return updated; + } + + public List applyAsGuest(Scene newScene, String username, Long hostId) { + List updated = copyStatesToDevices(newScene); + deviceService.saveAllAsGuestSceneApplication(updated, username, hostId); + return updated; + } + + public List> copyStates(Scene to, Scene from) { + final ArrayList> states = new ArrayList<>(); + for (final State s : from.getStates()) { + states.add(stateRepository.save(s.copyToSceneId(to.getId()))); + } + return states; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java index 0e4fbbd..532442b 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java @@ -43,7 +43,8 @@ public class SensorService { sensor = deviceService.saveAsOwner( sensor, sensorRepository.findUser(sensor.getId()).getUsername()); - endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId())); + endpoint.queueDeviceUpdate( + sensor, sensorRepository.findUser(sensor.getId()), false, null, false); return sensor; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java index e214e39..328d092 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java @@ -45,18 +45,16 @@ public class ThermostatService { return Utils.toList(all); } - public boolean computeState(Thermostat t) { + public void computeState(Thermostat t) { populateMeasuredTemperature(t); - return t.computeState(); + t.computeState(); } private void updateState(Thermostat t) { - boolean shouldUpdate = this.computeState(t); + this.computeState(t); - if (shouldUpdate) { - deviceService.saveAsOwner(t, thermostatRepository.findUser(t.getId()).getUsername()); - endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId())); - } + deviceService.saveAsOwner(t, thermostatRepository.findUser(t.getId()).getUsername()); + endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId()), false, null, false); } public void updateStates() { @@ -72,7 +70,6 @@ public class ThermostatService { populateMeasuredTemperature(u); t = Optional.of(u); } - return t; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java index b68cb25..4764f18 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java @@ -5,6 +5,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -21,6 +22,8 @@ public class SensorSocketEndpoint extends Endpoint { private Gson gson = GsonConfig.socketGson(); + @Autowired private DeviceService deviceService; + private UserRepository userRepository; private JWTTokenUtils jwtTokenUtils; @@ -28,7 +31,10 @@ public class SensorSocketEndpoint extends Endpoint { private Multimap authorizedClients = Multimaps.synchronizedMultimap(HashMultimap.create()); - private final Map> messages = new HashMap<>(); + // messages are now stored as strings as a "hack" to capture and clone the state of the device, + // since + // fromHost and fromGuest are just mutable properties and hibernate caches the object. + private final Map> messages = new HashMap<>(); @Autowired public SensorSocketEndpoint(UserRepository userRepository, JWTTokenUtils jwtTokenUtils) { @@ -41,18 +47,33 @@ public class SensorSocketEndpoint extends Endpoint { * * @param device the device update to be sent * @param u the user the device belongs + * @param fromGuest value for device.fromGuest. This will be put in the device passed. + * @param fromHostId value for device.fromHostId. This will be put in the device passed. + * @param deleted value for device.deleted. This will be put in the device passed. */ - public void queueDeviceUpdate(Device device, User u) { + public void queueDeviceUpdate( + Device device, User u, boolean fromGuest, Long fromHostId, boolean deleted) { synchronized (messages) { + device.setFromGuest(fromGuest); + device.setFromHostId(fromHostId); + device.setDeleted(deleted); + + // sort of an hack: force the population of thermostat measureTemperature and other + // possible + // computed fields in the future. This should already be done by the callers of this + // method but for + // whatever reason they don't do it. + deviceService.populateComputedFields(List.of(device)); + messages.putIfAbsent(u, new HashMap<>()); - messages.get(u).put(device.getId(), device); + messages.get(u).put(device.getId(), gson.toJson(device)); } } /** Sends all device updates queued to be sent in a unique WebSocket message */ public void flushDeviceUpdates() { synchronized (messages) { - for (Map.Entry> batchForUser : messages.entrySet()) { + for (Map.Entry> batchForUser : messages.entrySet()) { broadcast(batchForUser.getKey(), batchForUser.getValue().values()); batchForUser.getValue().clear(); } @@ -66,13 +87,13 @@ public class SensorSocketEndpoint extends Endpoint { * @param messages the message batch to send * @param u the user to which to send the message */ - private void broadcast(User u, Collection messages) { + private void broadcast(User u, Collection messages) { if (messages.isEmpty()) return; final HashSet sessions = new HashSet<>(authorizedClients.get(u)); for (Session s : sessions) { try { if (s.isOpen()) { - s.getBasicRemote().sendText(gson.toJson(messages)); + s.getBasicRemote().sendText("[" + String.join(",", messages) + "]"); } else { authorizedClients.remove(u, s); }