+ runtimeTypeAdapterFactoryII =
+ RuntimeTypeAdapterFactory.of(
+ AutomationFastUpdateRequest.TriggerDTO.class, "kind")
+ .registerSubtype(
+ AutomationFastUpdateRequest.BooleanTriggerDTO.class,
+ "booleanTrigger")
+ .registerSubtype(
+ AutomationFastUpdateRequest.RangeTriggerDTO.class,
+ "rangeTrigger");
+ builder.registerTypeAdapterFactory(runtimeTypeAdapterFactory);
+ builder.registerTypeAdapterFactory(runtimeTypeAdapterFactoryII);
+ return builder;
+ }
+
+ public static Gson gson() {
+ final GsonBuilder builder = configureBuilder();
builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy());
return builder.create();
}
+
+ public static Gson socketGson() {
+ final GsonBuilder builder = configureBuilder();
+ builder.addSerializationExclusionStrategy(new SocketAnnotationExclusionStrategy());
+ return builder.create();
+ }
}
/** GSON type adapter needed to avoid serializing twice Springfox Swagger JSON output */
@@ -48,3 +79,16 @@ class AnnotationExclusionStrategy implements ExclusionStrategy {
return false;
}
}
+
+/** GSON exclusion strategy to exclude attributes with @SocketGsonExclude */
+class SocketAnnotationExclusionStrategy implements ExclusionStrategy {
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ return f.getAnnotation(SocketGsonExclude.class) != null;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class> clazz) {
+ return false;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java
index e0cbb6a..503f7cd 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java
@@ -1,6 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.JWTUserDetailsService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import io.jsonwebtoken.ExpiredJwtException;
import java.io.IOException;
import javax.servlet.FilterChain;
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java
new file mode 100644
index 0000000..e4a9107
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java
@@ -0,0 +1,315 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
+
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Adapts values whose runtime type may differ from their declaration type. This is necessary when a
+ * field's type is not the same type that GSON should create when deserializing that field. For
+ * example, consider these types:
+ *
+ * {@code
+ * abstract class Shape {
+ * int x;
+ * int y;
+ * }
+ * class Circle extends Shape {
+ * int radius;
+ * }
+ * class Rectangle extends Shape {
+ * int width;
+ * int height;
+ * }
+ * class Diamond extends Shape {
+ * int width;
+ * int height;
+ * }
+ * class Drawing {
+ * Shape bottomShape;
+ * Shape topShape;
+ * }
+ * }
+ *
+ * Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
+ * this drawing a rectangle or a diamond?
+ *
+ *
{@code
+ * {
+ * "bottomShape": {
+ * "width": 10,
+ * "height": 5,
+ * "x": 0,
+ * "y": 0
+ * },
+ * "topShape": {
+ * "radius": 2,
+ * "x": 4,
+ * "y": 1
+ * }
+ * }
+ * }
+ *
+ * This class addresses this problem by adding type information to the serialized JSON and honoring
+ * that type information when the JSON is deserialized:
+ *
+ * {@code
+ * {
+ * "bottomShape": {
+ * "type": "Diamond",
+ * "width": 10,
+ * "height": 5,
+ * "x": 0,
+ * "y": 0
+ * },
+ * "topShape": {
+ * "type": "Circle",
+ * "radius": 2,
+ * "x": 4,
+ * "y": 1
+ * }
+ * }
+ * }
+ *
+ * Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
+ * configurable.
+ *
+ * Registering Types
+ *
+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
+ * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
+ * be used.
+ *
+ * {@code
+ * RuntimeTypeAdapterFactory shapeAdapterFactory
+ * = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ *
+ * Next register all of your subtypes. Every subtype must be explicitly registered. This protects
+ * your application from injection attacks. If you don't supply an explicit type label, the type's
+ * simple name will be used.
+ *
+ * {@code
+ * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }
+ *
+ * Finally, register the type adapter factory in your application's GSON builder:
+ *
+ * {@code
+ * Gson gson = new GsonBuilder()
+ * .registerTypeAdapterFactory(shapeAdapterFactory)
+ * .create();
+ * }
+ *
+ * Like {@code GsonBuilder}, this API supports chaining:
+ *
+ * {@code
+ * RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ * .registerSubtype(Rectangle.class)
+ * .registerSubtype(Circle.class)
+ * .registerSubtype(Diamond.class);
+ * }
+ *
+ * Serialization and deserialization
+ *
+ * In order to serialize and deserialize a polymorphic object, you must specify the base type
+ * explicitly.
+ *
+ * {@code
+ * Diamond diamond = new Diamond();
+ * String json = gson.toJson(diamond, Shape.class);
+ * }
+ *
+ * And then:
+ *
+ * {@code
+ * Shape shape = gson.fromJson(json, Shape.class);
+ * }
+ */
+public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory {
+ private final Class> baseType;
+ private final String typeFieldName;
+ private final Map> labelToSubtype = new LinkedHashMap>();
+ private final Map, String> subtypeToLabel = new LinkedHashMap, String>();
+ private final boolean maintainType;
+
+ private RuntimeTypeAdapterFactory(
+ Class> baseType, String typeFieldName, boolean maintainType) {
+ if (typeFieldName == null || baseType == null) {
+ throw new NullPointerException();
+ }
+ this.baseType = baseType;
+ this.typeFieldName = typeFieldName;
+ this.maintainType = maintainType;
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as
+ * the type field name. Type field names are case sensitive. {@code maintainType} flag decide if
+ * the type will be stored in pojo or not.
+ */
+ public static RuntimeTypeAdapterFactory of(
+ Class baseType, String typeFieldName, boolean maintainType) {
+ return new RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType);
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as
+ * the type field name. Type field names are case sensitive.
+ */
+ public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) {
+ return new RuntimeTypeAdapterFactory(baseType, typeFieldName, false);
+ }
+
+ /**
+ * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type
+ * field name.
+ */
+ public static RuntimeTypeAdapterFactory of(Class baseType) {
+ return new RuntimeTypeAdapterFactory(baseType, "type", false);
+ }
+
+ /**
+ * Registers {@code type} identified by {@code label}. Labels are case sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or {@code label} have already been
+ * registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory registerSubtype(Class extends T> type, String label) {
+ if (type == null || label == null) {
+ throw new NullPointerException();
+ }
+ if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
+ throw new IllegalArgumentException("types and labels must be unique");
+ }
+ labelToSubtype.put(label, type);
+ subtypeToLabel.put(type, label);
+ return this;
+ }
+
+ /**
+ * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
+ * case sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or its simple name have already been
+ * registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory registerSubtype(Class extends T> type) {
+ return registerSubtype(type, type.getSimpleName());
+ }
+
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ if (type.getRawType() != baseType) {
+ return null;
+ }
+
+ final Map> labelToDelegate =
+ new LinkedHashMap>();
+ final Map, TypeAdapter>> subtypeToDelegate =
+ new LinkedHashMap, TypeAdapter>>();
+ for (Map.Entry> entry : labelToSubtype.entrySet()) {
+ TypeAdapter> delegate =
+ gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
+ labelToDelegate.put(entry.getKey(), delegate);
+ subtypeToDelegate.put(entry.getValue(), delegate);
+ }
+
+ return new TypeAdapter() {
+ @Override
+ public R read(JsonReader in) throws IOException {
+ JsonElement jsonElement = Streams.parse(in);
+ JsonElement labelJsonElement;
+ if (maintainType) {
+ labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
+ } else {
+ labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
+ }
+
+ if (labelJsonElement == null) {
+ throw new JsonParseException(
+ "cannot deserialize "
+ + baseType
+ + " because it does not define a field named "
+ + typeFieldName);
+ }
+ String label = labelJsonElement.getAsString();
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label);
+ if (delegate == null) {
+ throw new JsonParseException(
+ "cannot deserialize "
+ + baseType
+ + " subtype named "
+ + label
+ + "; did you forget to register a subtype?");
+ }
+ return delegate.fromJsonTree(jsonElement);
+ }
+
+ @Override
+ public void write(JsonWriter out, R value) throws IOException {
+ Class> srcType = value.getClass();
+ String label = subtypeToLabel.get(srcType);
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType);
+ if (delegate == null) {
+ throw new JsonParseException(
+ "cannot serialize "
+ + srcType.getName()
+ + "; did you forget to register a subtype?");
+ }
+ JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
+
+ if (maintainType) {
+ Streams.write(jsonObject, out);
+ return;
+ }
+
+ JsonObject clone = new JsonObject();
+
+ if (jsonObject.has(typeFieldName)) {
+ throw new JsonParseException(
+ "cannot serialize "
+ + srcType.getName()
+ + " because it already defines a field named "
+ + typeFieldName);
+ }
+ clone.add(typeFieldName, new JsonPrimitive(label));
+
+ for (Map.Entry e : jsonObject.entrySet()) {
+ clone.add(e.getKey(), e.getValue());
+ }
+ Streams.write(clone, out);
+ }
+ }.nullSafe();
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java
new file mode 100644
index 0000000..e9806bc
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java
@@ -0,0 +1,10 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SocketGsonExclude {}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java
index 971a7fb..3f6e6f9 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SpringFoxConfig.java
@@ -68,13 +68,21 @@ public class SpringFoxConfig {
.or(PathSelectors.regex("/room.*")::apply)
.or(PathSelectors.regex("/device.*")::apply)
.or(PathSelectors.regex("/buttonDimmer.*")::apply)
+ .or(PathSelectors.regex("/thermostat.*")::apply)
.or(PathSelectors.regex("/dimmableLight.*")::apply)
.or(PathSelectors.regex("/knobDimmer.*")::apply)
.or(PathSelectors.regex("/regularLight.*")::apply)
+ .or(PathSelectors.regex("/securityCamera.*")::apply)
.or(PathSelectors.regex("/sensor.*")::apply)
.or(PathSelectors.regex("/smartPlug.*")::apply)
+ .or(PathSelectors.regex("/scene.*")::apply)
+ .or(PathSelectors.regex("/switchableState.*")::apply)
+ .or(PathSelectors.regex("/dimmableState.*")::apply)
.or(PathSelectors.regex("/switch.*")::apply)
.or(PathSelectors.regex("/motionSensor.*")::apply)
+ .or(PathSelectors.regex("/curtains.*")::apply)
+ .or(PathSelectors.regex("/user.*")::apply)
+ .or(PathSelectors.regex("/automation.*")::apply)
.or(PathSelectors.regex("/auth/profile.*")::apply);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
index ec116c3..4d30bff 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java
@@ -1,6 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.JWTUserDetailsService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -51,6 +51,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// dont authenticate this particular request
.authorizeRequests()
.antMatchers(
+ "/security_camera_videos/**",
"/sensor-socket",
"/auth/login",
"/swagger-ui.html",
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
index ad48da2..f806f01 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java
@@ -8,6 +8,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.security.Principal;
import javax.validation.Valid;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AutomationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AutomationController.java
new file mode 100644
index 0000000..cfad787
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AutomationController.java
@@ -0,0 +1,118 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.AutomationFastUpdateRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.AutomationSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import java.security.Principal;
+import java.util.List;
+import java.util.stream.Collectors;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/automation")
+public class AutomationController {
+
+ @Autowired private AutomationRepository automationRepository;
+ @Autowired private ScenePriorityRepository sceneRepository;
+ @Autowired private TriggerRepository> triggerRepository;
+ @Autowired private UserRepository userService;
+
+ @GetMapping
+ public List getAll(
+ @RequestParam(value = "hostId", required = false) Long hostId,
+ final Principal principal)
+ throws NotFoundException {
+ final Long userId = userService.findByUsername(principal.getName()).getId();
+ return automationRepository.findAllByUserId(userId);
+ }
+
+ @GetMapping("/{id}")
+ public Automation get(@PathVariable long id) throws NotFoundException {
+ return automationRepository.findById(id).orElseThrow(NotFoundException::new);
+ }
+
+ private Automation save(Automation newRL, AutomationSaveRequest s, Principal principal) {
+
+ final Long userId = userService.findByUsername(principal.getName()).getId();
+ newRL.setName(s.getName());
+ newRL.setUserId(userId);
+
+ return automationRepository.save(newRL);
+ }
+
+ @PostMapping
+ public Automation create(
+ @Valid @RequestBody AutomationSaveRequest automationSaveRequest, Principal principal) {
+ return save(new Automation(), automationSaveRequest, principal);
+ }
+
+ @PutMapping
+ public Automation update(
+ @Valid @RequestBody AutomationSaveRequest automation, Principal principal)
+ throws NotFoundException {
+ return save(
+ automationRepository
+ .findById(automation.getId())
+ .orElseThrow(NotFoundException::new),
+ automation,
+ principal);
+ }
+
+ @PutMapping("/fast")
+ public Automation fastUpdate(
+ @Valid @RequestBody AutomationFastUpdateRequest req, Principal principal)
+ throws NotFoundException {
+ final Automation a =
+ automationRepository
+ .findByIdAndUserId(
+ req.getId(),
+ userService.findByUsername(principal.getName()).getId())
+ .orElseThrow(NotFoundException::new);
+
+ a.setName(req.getName());
+ automationRepository.save(a);
+
+ triggerRepository.deleteAllByAutomationId(a.getId());
+ sceneRepository.deleteAllByAutomationId(a.getId());
+
+ Iterable> tt =
+ triggerRepository.saveAll(
+ req.getTriggers()
+ .stream()
+ .map(AutomationFastUpdateRequest.TriggerDTO::toModel)
+ .map(t -> t.setAutomationId(a.getId()))
+ .collect(Collectors.toList()));
+ Iterable ss =
+ sceneRepository.saveAll(
+ req.getScenes()
+ .stream()
+ .map(AutomationFastUpdateRequest.ScenePriorityDTO::toModel)
+ .map(t -> t.setAutomationId(a.getId()))
+ .collect(Collectors.toList()));
+
+ a.getScenes().clear();
+ a.getTriggers().clear();
+ ss.forEach(t -> a.getScenes().add(t));
+ tt.forEach(t -> a.getTriggers().add(t));
+
+ return a;
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable long id) {
+ automationRepository.deleteById(id);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/BooleanTriggerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/BooleanTriggerController.java
new file mode 100644
index 0000000..9331b56
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/BooleanTriggerController.java
@@ -0,0 +1,61 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.BooleanTriggerSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.BooleanTrigger;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.BooleanTriggerRepository;
+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.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/booleanTrigger")
+public class BooleanTriggerController {
+
+ @Autowired BooleanTriggerRepository booleanTriggerRepository;
+
+ @GetMapping("/{automationId}")
+ public List> getAll(@PathVariable long automationId) {
+ return booleanTriggerRepository.findAllByAutomationId(automationId);
+ }
+
+ private BooleanTrigger> save(BooleanTrigger> newRL, BooleanTriggerSaveRequest s) {
+ newRL.setDeviceId(s.getDeviceId());
+ newRL.setAutomationId(s.getAutomationId());
+ newRL.setOn(s.isOn());
+
+ return booleanTriggerRepository.save(newRL);
+ }
+
+ @PostMapping
+ public BooleanTrigger> create(
+ @Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest) {
+ return save(new BooleanTrigger<>(), booleanTriggerSaveRequest);
+ }
+
+ @PutMapping
+ public BooleanTrigger> update(
+ @Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest)
+ throws NotFoundException {
+ return save(
+ booleanTriggerRepository
+ .findById(booleanTriggerSaveRequest.getId())
+ .orElseThrow(NotFoundException::new),
+ booleanTriggerSaveRequest);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable long id) {
+ booleanTriggerRepository.deleteById(id);
+ }
+}
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 944bd8e..6b13ca9 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
@@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ButtonDimmerDimRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
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.DeviceService;
import java.security.Principal;
-import java.util.List;
import java.util.Set;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,42 +16,36 @@ import org.springframework.web.bind.annotation.*;
@EnableAutoConfiguration
@RequestMapping("/buttonDimmer")
public class ButtonDimmerController
- extends InputDeviceConnectionController {
+ extends InputDeviceConnectionController {
+
+ private DeviceService deviceService;
private ButtonDimmerRepository buttonDimmerRepository;
- private DimmableLightRepository dimmableLightRepository;
@Autowired
protected ButtonDimmerController(
- ButtonDimmerRepository inputRepository, DimmableLightRepository outputRepository) {
- super(
- inputRepository,
- outputRepository,
- DimmableLight.BUTTON_DIMMER_DIMMABLE_LIGHT_CONNECTOR);
+ ButtonDimmerRepository inputRepository,
+ DimmableRepository outputRepository,
+ DeviceService deviceService) {
+ super(inputRepository, outputRepository, DimmableLight.BUTTON_DIMMER_DIMMABLE_CONNECTOR);
+ this.deviceService = deviceService;
this.buttonDimmerRepository = inputRepository;
- this.dimmableLightRepository = outputRepository;
- }
-
- @GetMapping
- public List findAll() {
- return toList(buttonDimmerRepository.findAll());
- }
-
- @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) {
+ public ButtonDimmer create(
+ @Valid @RequestBody final GenericDeviceSaveReguest bd, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(bd.getRoomId(), principal.getName());
+
ButtonDimmer newBD = new ButtonDimmer();
newBD.setName(bd.getName());
newBD.setRoomId(bd.getRoomId());
- return buttonDimmerRepository.save(newBD);
+ return deviceService.saveAsOwner(newBD, principal.getName());
}
@PutMapping("/dim")
- public Set dim(
+ public Set dim(
@Valid @RequestBody final ButtonDimmerDimRequest bd, final Principal principal)
throws NotFoundException {
final ButtonDimmer buttonDimmer =
@@ -70,27 +62,14 @@ public class ButtonDimmerController
break;
}
- dimmableLightRepository.saveAll(buttonDimmer.getOutputs());
+ deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName(), false);
return buttonDimmer.getOutputs();
}
- @PostMapping("/{id}/lights")
- public Set extends OutputDevice> addLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
- throws NotFoundException {
- return addOutput(inputId, lightId);
- }
-
- @DeleteMapping("/{id}/lights")
- public Set extends OutputDevice> removeLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
- throws NotFoundException {
- return removeOutput(inputId, lightId);
- }
-
@DeleteMapping("/{id}")
- public void delete(@PathVariable("id") long id) {
- buttonDimmerRepository.deleteById(id);
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(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
new file mode 100644
index 0000000..6644135
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/CurtainsController.java
@@ -0,0 +1,73 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
+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.DeviceService;
+import java.security.Principal;
+import javax.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/curtains")
+public class CurtainsController {
+ @Autowired private DeviceService deviceService;
+ @Autowired private CurtainsRepository curtainsService;
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private StateRepository> stateRepository;
+
+ private Curtains save(Curtains newRL, DimmableSaveRequest s, final Principal principal) {
+ newRL.setName(s.getName());
+ newRL.setRoomId(s.getRoomId());
+ newRL.setIntensity(s.getIntensity());
+
+ return deviceService.saveAsOwner(newRL, principal.getName());
+ }
+
+ @PostMapping
+ public Curtains create(
+ @Valid @RequestBody DimmableSaveRequest curtain, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(curtain.getRoomId(), principal.getName());
+ return save(new Curtains(), curtain, principal);
+ }
+
+ @PutMapping
+ public Curtains update(
+ @Valid @RequestBody DimmableSaveRequest curtain, final Principal principal)
+ throws NotFoundException {
+ return save(
+ curtainsService.findById(curtain.getId()).orElseThrow(NotFoundException::new),
+ curtain,
+ principal);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ @PostMapping("/{id}/state")
+ public State extends Dimmable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+
+ Curtains c =
+ curtainsService
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Dimmable> s = c.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java
index 17bdec7..b53b0af 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DeviceController.java
@@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
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.RoomRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import java.security.Principal;
import java.util.List;
import javax.validation.Valid;
@@ -18,12 +19,15 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/device")
public class DeviceController {
+ @Autowired private DeviceService deviceService;
@Autowired private DeviceRepository deviceRepository;
@Autowired private RoomRepository roomRepository;
@GetMapping
- public List getAll(final Principal user) {
- return deviceRepository.findAllByUsername(user.getName());
+ public List getAll(
+ @RequestParam(value = "hostId", required = false) Long hostId, final Principal user)
+ throws NotFoundException {
+ return deviceService.findAll(hostId, user.getName());
}
@PutMapping
@@ -43,7 +47,7 @@ public class DeviceController {
d.setRoomId(deviceSaveRequest.getRoomId());
d.setName(deviceSaveRequest.getName());
- deviceRepository.save(d);
+ deviceService.saveAsOwner(d, principal.getName());
return d;
}
}
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 944b6c2..60a4726 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
@@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
-import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableLightSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLightRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
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;
@@ -16,46 +14,92 @@ import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
@RequestMapping("/dimmableLight")
-public class DimmableLightController {
+public class DimmableLightController extends GuestEnabledController {
- @Autowired private DimmableLightRepository dimmableLightService;
+ private DimmableLightRepository dimmableLightRepository;
+ private SceneRepository sceneRepository;
+ private StateRepository> stateRepository;
+ private DeviceService deviceService;
- @GetMapping
- public List findAll() {
- return toList(dimmableLightService.findAll());
+ @Autowired
+ public DimmableLightController(
+ UserRepository userRepository,
+ DimmableLightRepository dimmableLightRepository,
+ SceneRepository sceneRepository,
+ StateRepository> stateRepository,
+ DeviceService deviceService) {
+ super(userRepository, dimmableLightRepository);
+ this.dimmableLightRepository = dimmableLightRepository;
+ this.sceneRepository = sceneRepository;
+ this.stateRepository = stateRepository;
+ this.deviceService = deviceService;
}
- @GetMapping("/{id}")
- public DimmableLight findById(@PathVariable("id") long id) throws NotFoundException {
- return dimmableLightService.findById(id).orElseThrow(NotFoundException::new);
- }
-
- private DimmableLight save(DimmableLight initial, DimmableLightSaveRequest dl) {
+ private DimmableLight save(
+ DimmableLight initial, DimmableSaveRequest dl, String username, Long hostId)
+ throws NotFoundException {
initial.setIntensity(dl.getIntensity());
initial.setName(dl.getName());
initial.setRoomId(dl.getRoomId());
- return dimmableLightService.save(initial);
+ if (hostId == null) {
+ return deviceService.saveAsOwner(initial, username);
+ } else {
+ return deviceService.saveAsGuest(initial, username, hostId);
+ }
}
+ /*
+ Assume that only the host can create a device
+ Here save always as host, but remember to propagate change to guests (DeviceService.saveAsOwner())
+ */
@PostMapping
- public DimmableLight create(@Valid @RequestBody DimmableLightSaveRequest dl) {
- return save(new DimmableLight(), dl);
+ public DimmableLight create(
+ @Valid @RequestBody DimmableSaveRequest dl, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(dl.getRoomId(), principal.getName());
+ return save(new DimmableLight(), dl, principal.getName(), null);
}
+ /*
+ Logic for saving either as owner or guest is handled in method save of this controller
+ */
@PutMapping
public DimmableLight update(
- @Valid @RequestBody DimmableLightSaveRequest sp, final Principal principal)
+ @Valid @RequestBody DimmableSaveRequest sp, final Principal principal, Long hostId)
throws NotFoundException {
+
return save(
- dimmableLightService
- .findByIdAndUsername(sp.getId(), principal.getName())
- .orElseThrow(NotFoundException::new),
- sp);
+ fetchIfOwnerOrGuest(principal, sp.getId(), hostId),
+ sp,
+ principal.getName(),
+ hostId);
}
@DeleteMapping("/{id}")
- public void delete(@PathVariable("id") long id) {
- dimmableLightService.deleteById(id);
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ // the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId}
+ // however it is not necessary to specify the query in the mapping
+ @PostMapping("/{id}/state")
+ public State extends Dimmable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+
+ DimmableLight d =
+ dimmableLightRepository
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Dimmable> s = d.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableStateController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableStateController.java
new file mode 100644
index 0000000..d866d78
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableStateController.java
@@ -0,0 +1,33 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableStateSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableState;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableStateRepository;
+import javax.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/dimmableState")
+public class DimmableStateController {
+
+ @Autowired private DimmableStateRepository dimmableStateRepository;
+
+ @PutMapping
+ public DimmableState> update(@Valid @RequestBody DimmableStateSaveRequest ss)
+ throws NotFoundException {
+ final DimmableState> initial =
+ dimmableStateRepository.findById(ss.getId()).orElseThrow(NotFoundException::new);
+ initial.setIntensity(ss.getIntensity());
+ dimmableStateRepository.save(initial);
+ return initial;
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable("id") long id) {
+ dimmableStateRepository.deleteById(id);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/GuestController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/GuestController.java
new file mode 100644
index 0000000..b0994a1
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/GuestController.java
@@ -0,0 +1,59 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GuestPermissionsRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
+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.*;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/user")
+public class GuestController {
+
+ @Autowired private UserRepository userRepository;
+
+ @GetMapping
+ public List findAll() {
+ return toList(userRepository.findAll());
+ }
+
+ @PostMapping("/guest")
+ public User addUserAsGuest(@RequestParam("userId") long id, final Principal principal)
+ throws NotFoundException {
+ User guest = userRepository.findById(id).orElseThrow(NotFoundException::new);
+ User host = userRepository.findByUsername(principal.getName());
+
+ host.addGuest(guest);
+ guest.addHost(host);
+ userRepository.save(guest);
+ return userRepository.save(host);
+ }
+
+ @PutMapping("/permissions")
+ public User updatePermissions(
+ @Valid @RequestBody GuestPermissionsRequest g, final Principal principal) {
+ final User currentUser = userRepository.findByUsername(principal.getName());
+ 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/GuestEnabledController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/GuestEnabledController.java
new file mode 100644
index 0000000..1aa2e7c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/GuestEnabledController.java
@@ -0,0 +1,37 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.returnIfGuest;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import java.security.Principal;
+
+public abstract class GuestEnabledController {
+
+ private UserRepository userRepository;
+ private DeviceRepository deviceRepository;
+
+ public GuestEnabledController(
+ final UserRepository userRepository, final DeviceRepository deviceRepository) {
+ this.userRepository = userRepository;
+ this.deviceRepository = deviceRepository;
+ }
+
+ protected T fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId)
+ throws NotFoundException {
+ if (hostId == null) {
+ return deviceRepository
+ .findByIdAndUsername(id, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ } else {
+ /*
+ * Slightly less extremely verbose check through various repositories to control user/guest authorization.
+ */
+ T device =
+ deviceRepository
+ .findByIdAndUserId(id, hostId)
+ .orElseThrow(NotFoundException::new);
+ return returnIfGuest(userRepository, device, hostId, principal);
+ }
+ }
+}
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 52f3483..4d1a371 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
@@ -1,8 +1,19 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
+
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.DeviceService;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
/**
* An abstract controller for an input device that has output connected to it. Aids to create the
@@ -14,16 +25,18 @@ import java.util.Set;
public abstract class InputDeviceConnectionController<
I extends InputDevice, O extends OutputDevice> {
- private class IOPair {
+ private class Connection {
private final I input;
- private final O output;
+ private final List outputs;
- private IOPair(I input, O output) {
+ private Connection(I input, List outputs) {
this.input = input;
- this.output = output;
+ this.outputs = outputs;
}
}
+ @Autowired private DeviceService deviceService;
+
private DeviceRepository inputRepository;
private DeviceRepository outputReposiory;
@@ -46,31 +59,39 @@ public abstract class InputDeviceConnectionController<
this.connector = connector;
}
- private IOPair checkConnectionIDs(Long inputId, Long outputId) throws NotFoundException {
+ private Connection checkConnectionIDs(Long inputId, List outputs, String username)
+ throws NotFoundException {
final I input =
inputRepository
- .findById(inputId)
+ .findByIdAndUsername(inputId, username)
.orElseThrow(() -> new NotFoundException("input device"));
- final O output =
- outputReposiory
- .findById(outputId)
- .orElseThrow(() -> new NotFoundException("output device"));
- return new IOPair(input, output);
+ final List outputDevices = new ArrayList<>();
+ for (final Long outputId : outputs) {
+ outputDevices.add(
+ outputReposiory
+ .findByIdAndUsername(outputId, username)
+ .orElseThrow(() -> new NotFoundException("output device")));
+ }
+ return new Connection(input, outputDevices);
}
/**
* Implements the output device connection creation (add) route
*
* @param inputId input device id
- * @param outputId output device id
+ * @param outputs output device id list
* @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid
*/
- protected Set extends OutputDevice> addOutput(Long inputId, Long outputId)
- throws NotFoundException {
- final IOPair pair = checkConnectionIDs(inputId, outputId);
- connector.connect(pair.input, pair.output, true);
- outputReposiory.save(pair.output);
+ protected Set extends OutputDevice> addOutput(
+ Long inputId, List outputs, String username) throws NotFoundException {
+ final Connection pair = checkConnectionIDs(inputId, outputs, username);
+
+ for (final O o : pair.outputs) {
+ connector.connect(pair.input, o, true);
+ }
+
+ deviceService.saveAllAsOwner(pair.outputs, username, false);
return pair.input.getOutputs();
}
@@ -78,15 +99,37 @@ public abstract class InputDeviceConnectionController<
* Implements the output device connection destruction (remove) route
*
* @param inputId input device id
- * @param outputId output device id
+ * @param outputs output device id list
* @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid
*/
- protected Set extends OutputDevice> removeOutput(Long inputId, Long outputId)
- throws NotFoundException {
- final IOPair pair = checkConnectionIDs(inputId, outputId);
- connector.connect(pair.input, pair.output, false);
- outputReposiory.save(pair.output);
+ protected Set extends OutputDevice> removeOutput(
+ Long inputId, List outputs, String username) throws NotFoundException {
+ final Connection pair = checkConnectionIDs(inputId, outputs, username);
+
+ for (final O o : pair.outputs) {
+ connector.connect(pair.input, o, false);
+ }
+
+ deviceService.saveAllAsOwner(pair.outputs, username, false);
return pair.input.getOutputs();
}
+
+ @PostMapping("/{id}/lights")
+ public List addLight(
+ @PathVariable("id") long inputId,
+ @RequestBody List lightId,
+ final Principal principal)
+ throws NotFoundException {
+ return toList(addOutput(inputId, lightId, principal.getName()));
+ }
+
+ @DeleteMapping("/{id}/lights")
+ public List removeLight(
+ @PathVariable("id") long inputId,
+ @RequestBody List lightId,
+ final Principal principal)
+ throws NotFoundException {
+ return toList(removeOutput(inputId, lightId, principal.getName()));
+ }
}
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 c15d867..9b30535 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
@@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.KnobDimmerDimRequest;
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.DeviceService;
import java.security.Principal;
-import java.util.List;
import java.util.Set;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
@@ -17,44 +15,32 @@ import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
@RequestMapping("/knobDimmer")
-public class KnobDimmerController
- extends InputDeviceConnectionController {
+public class KnobDimmerController extends InputDeviceConnectionController {
+ @Autowired private DeviceService deviceService;
@Autowired private KnobDimmerRepository knobDimmerRepository;
- @Autowired private DimmableLightRepository dimmableLightRepository;
@Autowired
protected KnobDimmerController(
- KnobDimmerRepository inputRepository, DimmableLightRepository outputRepository) {
- super(
- inputRepository,
- outputRepository,
- DimmableLight.KNOB_DIMMER_DIMMABLE_LIGHT_CONNECTOR);
+ KnobDimmerRepository inputRepository, DimmableRepository outputRepository) {
+ super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR);
this.knobDimmerRepository = inputRepository;
- this.dimmableLightRepository = outputRepository;
- }
-
- @GetMapping
- public List findAll() {
- return toList(knobDimmerRepository.findAll());
- }
-
- @GetMapping("/{id}")
- public KnobDimmer findById(@PathVariable("id") long id) throws NotFoundException {
- return knobDimmerRepository.findById(id).orElseThrow(NotFoundException::new);
}
@PostMapping
- public KnobDimmer create(@Valid @RequestBody GenericDeviceSaveReguest kd) {
+ public KnobDimmer create(
+ @Valid @RequestBody GenericDeviceSaveReguest kd, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(kd.getRoomId(), principal.getName());
KnobDimmer newKD = new KnobDimmer();
newKD.setName(kd.getName());
newKD.setRoomId(kd.getRoomId());
- return knobDimmerRepository.save(newKD);
+ return deviceService.saveAsOwner(newKD, principal.getName());
}
@PutMapping("/dimTo")
- public Set dimTo(
+ public Set dimTo(
@Valid @RequestBody final KnobDimmerDimRequest bd, final Principal principal)
throws NotFoundException {
final KnobDimmer dimmer =
@@ -63,27 +49,14 @@ public class KnobDimmerController
.orElseThrow(NotFoundException::new);
dimmer.setLightIntensity(bd.getIntensity());
- dimmableLightRepository.saveAll(dimmer.getOutputs());
+ deviceService.saveAllAsOwner(dimmer.getOutputs(), principal.getName(), false);
return dimmer.getOutputs();
}
- @PostMapping("/{id}/lights")
- public Set extends OutputDevice> addLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
- throws NotFoundException {
- return addOutput(inputId, lightId);
- }
-
- @DeleteMapping("/{id}/lights")
- public Set extends OutputDevice> removeLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
- throws NotFoundException {
- return removeOutput(inputId, lightId);
- }
-
@DeleteMapping("/{id}")
- public void delete(@PathVariable("id") long id) {
- knobDimmerRepository.deleteById(id);
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
index 59c0343..e0d10d9 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
@@ -1,14 +1,13 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensor;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.MotionSensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
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;
@@ -19,43 +18,21 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/motionSensor")
public class MotionSensorController {
- @Autowired private MotionSensorRepository motionSensorService;
-
+ @Autowired private DeviceService deviceService;
+ @Autowired private MotionSensorService motionSensorService;
+ @Autowired private MotionSensorRepository motionSensorRepository;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
- @GetMapping
- public List findAll() {
- return toList(motionSensorService.findAll());
- }
-
- @GetMapping("/{id}")
- public MotionSensor findById(@PathVariable("id") long id) throws NotFoundException {
- return motionSensorService.findById(id).orElseThrow(NotFoundException::new);
- }
-
@PostMapping
- public MotionSensor create(@Valid @RequestBody GenericDeviceSaveReguest ms) {
+ public MotionSensor create(
+ @Valid @RequestBody GenericDeviceSaveReguest ms, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(ms.getRoomId(), principal.getName());
MotionSensor newMS = new MotionSensor();
newMS.setName(ms.getName());
newMS.setRoomId(ms.getRoomId());
- return motionSensorService.save(newMS);
- }
-
- /**
- * Updates detection status of given motion sensor and propagates update throgh socket
- *
- * @param sensor the motion sensor to update
- * @param detected the new detection status
- * @return the updated motion sensor
- */
- public MotionSensor updateDetectionFromMotionSensor(MotionSensor sensor, boolean detected) {
- sensor.setDetected(detected);
- final MotionSensor toReturn = motionSensorService.save(sensor);
-
- sensorSocketEndpoint.broadcast(sensor, motionSensorService.findUser(sensor.getId()));
-
- return toReturn;
+ return deviceService.saveAsOwner(newMS, principal.getName());
}
@PutMapping("/{id}/detect")
@@ -65,15 +42,17 @@ public class MotionSensorController {
final Principal principal)
throws NotFoundException {
- return updateDetectionFromMotionSensor(
- motionSensorService
+ return motionSensorService.updateDetectionFromMotionSensor(
+ motionSensorRepository
.findByIdAndUsername(sensorId, principal.getName())
.orElseThrow(NotFoundException::new),
- detected);
+ detected,
+ principal.getName());
}
@DeleteMapping("/{id}")
- public void delete(@PathVariable("id") long id) {
- motionSensorService.deleteById(id);
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RangeTriggerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RangeTriggerController.java
new file mode 100644
index 0000000..655ce4b
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RangeTriggerController.java
@@ -0,0 +1,62 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RangeTriggerSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RangeTrigger;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RangeTriggerRepository;
+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.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/rangeTrigger")
+public class RangeTriggerController {
+
+ @Autowired RangeTriggerRepository rangeTriggerRepository;
+
+ @GetMapping("/{automationId}")
+ public List> getAll(@PathVariable long automationId) {
+ return rangeTriggerRepository.findAllByAutomationId(automationId);
+ }
+
+ private RangeTrigger> save(RangeTrigger> newRL, RangeTriggerSaveRequest s) {
+ newRL.setDeviceId(s.getDeviceId());
+ newRL.setAutomationId(s.getAutomationId());
+ newRL.setOperator(s.getOperator());
+ newRL.setRange(s.getRange());
+
+ return rangeTriggerRepository.save(newRL);
+ }
+
+ @PostMapping
+ public RangeTrigger> create(
+ @Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest) {
+ return save(new RangeTrigger<>(), booleanTriggerSaveRequest);
+ }
+
+ @PutMapping
+ public RangeTrigger> update(
+ @Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest)
+ throws NotFoundException {
+ return save(
+ rangeTriggerRepository
+ .findById(booleanTriggerSaveRequest.getId())
+ .orElseThrow(NotFoundException::new),
+ booleanTriggerSaveRequest);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable long id) {
+ rangeTriggerRepository.deleteById(id);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java
index e033555..aef798d 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/RegularLightController.java
@@ -2,10 +2,11 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RegularLightSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLight;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RegularLightRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import java.security.Principal;
import java.util.List;
import javax.validation.Valid;
@@ -18,51 +19,99 @@ 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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableAutoConfiguration
@RequestMapping("/regularLight")
-public class RegularLightController {
+public class RegularLightController extends GuestEnabledController {
- @Autowired private RegularLightRepository regularLightService;
+ private RegularLightRepository regularLightRepository;
+ private SceneRepository sceneRepository;
+ private StateRepository> stateRepository;
+ private DeviceService deviceService;
+
+ @Autowired
+ public RegularLightController(
+ UserRepository userRepository,
+ RegularLightRepository regularLightRepository,
+ SceneRepository sceneRepository,
+ StateRepository> stateRepository,
+ DeviceService deviceService) {
+ super(userRepository, regularLightRepository);
+ this.regularLightRepository = regularLightRepository;
+ this.sceneRepository = sceneRepository;
+ this.stateRepository = stateRepository;
+ this.deviceService = deviceService;
+ }
@GetMapping
public List findAll() {
- return toList(regularLightService.findAll());
+ return toList(regularLightRepository.findAll());
}
@GetMapping("/{id}")
public RegularLight findById(@PathVariable("id") long id) throws NotFoundException {
- return regularLightService.findById(id).orElseThrow(NotFoundException::new);
+ return regularLightRepository.findById(id).orElseThrow(NotFoundException::new);
}
- private RegularLight save(RegularLight newRL, RegularLightSaveRequest rl) {
- newRL.setName(rl.getName());
- newRL.setRoomId(rl.getRoomId());
- newRL.setOn(rl.isOn());
+ private RegularLight save(
+ RegularLight initial, SwitchableSaveRequest rl, String username, Long hostId)
+ throws NotFoundException {
+ initial.setName(rl.getName());
+ initial.setRoomId(rl.getRoomId());
+ initial.setOn(rl.isOn());
- return regularLightService.save(newRL);
+ if (hostId == null) {
+ return deviceService.saveAsOwner(initial, username);
+ } else {
+ return deviceService.saveAsGuest(initial, username, hostId);
+ }
}
@PostMapping
- public RegularLight create(@Valid @RequestBody RegularLightSaveRequest rl) {
- return save(new RegularLight(), rl);
+ public RegularLight create(
+ @Valid @RequestBody SwitchableSaveRequest rl, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(rl.getRoomId(), principal.getName());
+ return save(new RegularLight(), rl, principal.getName(), null);
}
@PutMapping
public RegularLight update(
- @Valid @RequestBody RegularLightSaveRequest rl, final Principal principal)
+ @Valid @RequestBody SwitchableSaveRequest rl, final Principal principal, Long hostId)
throws NotFoundException {
return save(
- regularLightService
- .findByIdAndUsername(rl.getId(), principal.getName())
- .orElseThrow(NotFoundException::new),
- rl);
+ fetchIfOwnerOrGuest(principal, rl.getId(), hostId),
+ rl,
+ principal.getName(),
+ hostId);
}
@DeleteMapping("/{id}")
- public void delete(@PathVariable("id") long id) {
- regularLightService.deleteById(id);
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ // the full url should be: "/regularLight/{id}/state?sceneId={sceneId}
+ // however it is not necessary to specify the query in the mapping
+ @PostMapping("/{id}/state")
+ public State extends Switchable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+ RegularLight d =
+ regularLightRepository
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Switchable> s = d.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
}
}
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 2bfa440..c4540c5 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
@@ -5,6 +5,9 @@ import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.RoomSaveRequest;
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.DeviceService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils;
import java.security.Principal;
import java.util.*;
import javax.validation.Valid;
@@ -21,7 +24,7 @@ public class RoomController {
@Autowired private UserRepository userRepository;
- @Autowired private DeviceRepository deviceRepository;
+ @Autowired private DeviceService deviceService;
@Autowired private SwitchRepository switchRepository;
@@ -29,14 +32,40 @@ public class RoomController {
@Autowired private KnobDimmerRepository knobDimmerRepository;
+ @Autowired private ThermostatService thermostatService;
+
+ private List fetchOwnerOrGuest(
+ final List list, Long hostId, final Principal principal) throws NotFoundException {
+ if (hostId == null) {
+ return list;
+ } else {
+ return Utils.returnIfGuest(userRepository, list, hostId, principal);
+ }
+ }
+
@GetMapping
- public List findAll() {
- return toList(roomRepository.findAll());
+ public List findAll(
+ @RequestParam(value = "hostId", required = false) Long hostId,
+ final Principal principal)
+ throws NotFoundException {
+
+ List rooms =
+ toList(
+ hostId != null
+ ? roomRepository.findByUserId(hostId)
+ : roomRepository.findByUsername(principal.getName()));
+ return fetchOwnerOrGuest(rooms, hostId, principal);
}
@GetMapping("/{id}")
- public @ResponseBody Room findById(@PathVariable("id") long id) throws NotFoundException {
- return roomRepository.findById(id).orElseThrow(NotFoundException::new);
+ public @ResponseBody Room findById(
+ @PathVariable("id") long id,
+ final Principal principal,
+ @RequestParam(value = "hostId", required = false) Long hostId)
+ throws NotFoundException {
+ Room room = roomRepository.findById(id).orElseThrow(NotFoundException::new);
+ fetchOwnerOrGuest(null, hostId, principal);
+ return room;
}
@PostMapping
@@ -86,11 +115,16 @@ public class RoomController {
}
@DeleteMapping("/{id}")
- public void deleteById(@PathVariable("id") long id) {
+ public void deleteById(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
switchRepository.deleteAllByRoomId(id);
knobDimmerRepository.deleteAllByRoomId(id);
buttonDimmerRepository.deleteAllByRoomId(id);
- roomRepository.deleteById(id);
+ final Room r =
+ roomRepository
+ .findByIdAndUsername(id, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ roomRepository.delete(r);
}
/**
@@ -98,7 +132,11 @@ public class RoomController {
* id).
*/
@GetMapping(path = "/{roomId}/devices")
- public List getDevices(@PathVariable("roomId") long roomid) {
- return deviceRepository.findByRoomId(roomid);
+ public List getDevices(
+ @PathVariable("roomId") long roomId,
+ final Principal principal,
+ @RequestParam(value = "hostId", required = false) Long hostId)
+ throws NotFoundException {
+ return deviceService.findAll(roomId, hostId, principal.getName());
}
}
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
new file mode 100644
index 0000000..0cddba2
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SceneController.java
@@ -0,0 +1,107 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
+
+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 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;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/scene")
+public class SceneController {
+
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private SceneService sceneService;
+ @Autowired private UserRepository userService;
+ @Autowired private StateRepository> stateService;
+
+ @GetMapping
+ public List findAll(Principal principal) {
+ return toList(sceneRepository.findByUsername(principal.getName()));
+ }
+
+ @GetMapping("/{id}")
+ public @ResponseBody Scene findById(@PathVariable("id") long id, Principal principal)
+ throws NotFoundException {
+ return sceneRepository
+ .findByIdAndUsername(id, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ }
+
+ @PostMapping
+ public @ResponseBody Scene create(
+ @Valid @RequestBody SceneSaveRequest s, final Principal principal) {
+
+ final String username = principal.getName();
+ final Long userId = userService.findByUsername(username).getId();
+
+ final Scene newScene = new Scene();
+
+ newScene.setUserId(userId);
+ newScene.setName(s.getName());
+ newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
+
+ return sceneRepository.save(newScene);
+ }
+
+ @PostMapping("/{id}/apply")
+ public @ResponseBody List apply(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ final Scene newScene =
+ sceneRepository
+ .findByIdAndUsername(id, principal.getName())
+ .orElseThrow(NotFoundException::new);
+
+ return sceneService.apply(newScene);
+ }
+
+ @PutMapping("/{id}")
+ public @ResponseBody Scene update(
+ @PathVariable("id") long id, @RequestBody SceneSaveRequest s, final Principal principal)
+ throws NotFoundException {
+ final Scene newScene =
+ sceneRepository
+ .findByIdAndUsername(id, principal.getName())
+ .orElseThrow(NotFoundException::new);
+
+ if (s.getName() != null) {
+ newScene.setName(s.getName());
+ }
+
+ newScene.setGuestAccessEnabled(s.isGuestAccessEnabled());
+
+ return sceneRepository.save(newScene);
+ }
+
+ @DeleteMapping("/{id}")
+ public void deleteById(@PathVariable("id") long id) {
+ stateService.deleteAllBySceneId(id);
+ sceneRepository.deleteById(id);
+ }
+
+ /**
+ * Returns a List of all Devices that are associated to a given scene (identified by its
+ * id).
+ */
+ @GetMapping(path = "/{sceneId}/states")
+ public List> getDevices(@PathVariable("sceneId") long sceneId) {
+ Iterable> states = stateService.findBySceneId(sceneId);
+ return toList(states);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ScenePriorityController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ScenePriorityController.java
new file mode 100644
index 0000000..6e95f75
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ScenePriorityController.java
@@ -0,0 +1,63 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ScenePrioritySaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ScenePriority;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ScenePriorityRepository;
+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.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/scenePriority")
+public class ScenePriorityController {
+
+ @Autowired ScenePriorityRepository scenePriorityRepository;
+
+
+ @GetMapping("/{automationId}")
+ public List getByAutomationId(@PathVariable long automationId)
+ throws NotFoundException {
+ return scenePriorityRepository.findAllByAutomationId(automationId);
+ }
+
+ private ScenePriority save(ScenePriority newRL, ScenePrioritySaveRequest s) {
+ newRL.setPriority(s.getPriority());
+ newRL.setAutomationId(s.getAutomationId());
+ newRL.setSceneId(s.getSceneId());
+
+ return scenePriorityRepository.save(newRL);
+ }
+
+ @PostMapping
+ public ScenePriority create(
+ @Valid @RequestBody ScenePrioritySaveRequest scenePrioritySaveRequest) {
+ return save(new ScenePriority(), scenePrioritySaveRequest);
+ }
+
+ @PutMapping
+ public ScenePriority update(
+ @Valid @RequestBody ScenePrioritySaveRequest scenePrioritySaveRequest)
+ throws NotFoundException {
+ return save(
+ scenePriorityRepository
+ .findById(scenePrioritySaveRequest.getSceneId())
+ .orElseThrow(NotFoundException::new),
+ scenePrioritySaveRequest);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable long id) {
+ scenePriorityRepository.deleteBySceneId(id);
+ }
+}
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
new file mode 100644
index 0000000..c063ebf
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SecurityCameraController.java
@@ -0,0 +1,85 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
+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.DeviceService;
+import java.security.Principal;
+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.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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/securityCamera")
+public class SecurityCameraController {
+
+ @Autowired private DeviceService deviceService;
+ @Autowired private SecurityCameraRepository securityCameraService;
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private StateRepository> stateRepository;
+ @Autowired private RoomRepository roomRepository;
+
+ private SecurityCamera save(
+ SecurityCamera newSC, SwitchableSaveRequest sc, final Principal principal) {
+ newSC.setName(sc.getName());
+ newSC.setRoomId(sc.getRoomId());
+ newSC.setOn(sc.isOn());
+
+ return deviceService.saveAsOwner(newSC, principal.getName());
+ }
+
+ @PostMapping
+ public SecurityCamera create(
+ @Valid @RequestBody SwitchableSaveRequest sc, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(sc.getRoomId(), principal.getName());
+ return save(new SecurityCamera(), sc, principal);
+ }
+
+ @PutMapping
+ public SecurityCamera update(
+ @Valid @RequestBody SwitchableSaveRequest sc, final Principal principal)
+ throws NotFoundException {
+ return save(
+ securityCameraService
+ .findByIdAndUsername(sc.getId(), principal.getName())
+ .orElseThrow(NotFoundException::new),
+ sc,
+ principal);
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ @PostMapping("/{id}/state")
+ public State extends Switchable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+
+ SecurityCamera d =
+ securityCameraService
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Switchable> s = d.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
+ }
+}
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 ee1be81..41b9af0 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
@@ -1,15 +1,14 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SensorSaveRequest;
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.DeviceService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.math.BigDecimal;
import java.security.Principal;
import java.util.*;
-import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.*;
@@ -20,45 +19,26 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/sensor")
public class SensorController {
+ @Autowired private DeviceService deviceService;
+
@Autowired private SensorRepository sensorRepository;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
- @GetMapping
- public List findAll() {
- return toList(sensorRepository.findAll());
- }
-
- @GetMapping("/{id}")
- public Sensor findById(@PathVariable("id") long id) throws NotFoundException {
- return sensorRepository.findById(id).orElseThrow(NotFoundException::new);
- }
+ @Autowired private SensorService sensorService;
@PostMapping
- public Sensor create(@Valid @RequestBody SensorSaveRequest s) {
+ public Sensor create(@Valid @RequestBody SensorSaveRequest s, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(s.getRoomId(), principal.getName());
+
Sensor newSensor = new Sensor();
newSensor.setSensor(s.getSensor());
newSensor.setName(s.getName());
newSensor.setRoomId(s.getRoomId());
newSensor.setValue(s.getValue());
- return sensorRepository.save(newSensor);
- }
-
- /**
- * Updates the sensor with new measurement and propagates update through websocket
- *
- * @param sensor the sensor to update
- * @param value the new measurement
- * @return the updated sensor
- */
- public Sensor updateValueFromSensor(Sensor sensor, BigDecimal value) {
- sensor.setValue(value);
- final Sensor toReturn = sensorRepository.save(sensor);
-
- sensorSocketEndpoint.broadcast(sensor, sensorRepository.findUser(sensor.getId()));
-
- return toReturn;
+ return deviceService.saveAsOwner(newSensor, principal.getName());
}
@PutMapping("/{id}/value")
@@ -67,7 +47,7 @@ public class SensorController {
@RequestParam("value") BigDecimal value,
final Principal principal)
throws NotFoundException {
- return updateValueFromSensor(
+ return sensorService.updateValueFromSensor(
sensorRepository
.findByIdAndUsername(sensorId, principal.getName())
.orElseThrow(NotFoundException::new),
@@ -75,7 +55,8 @@ public class SensorController {
}
@DeleteMapping("/{id}")
- public void deleteById(@PathVariable("id") long id) {
- sensorRepository.deleteById(id);
+ public void deleteById(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(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 5ef4eed..468cd5d 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
@@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
-import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SmartPlugSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
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.DeviceService;
import java.security.Principal;
-import java.util.*;
-import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.*;
@@ -18,40 +16,36 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/smartPlug")
public class SmartPlugController {
+ @Autowired private DeviceService deviceService;
@Autowired private SmartPlugRepository smartPlugRepository;
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private StateRepository> stateRepository;
- @GetMapping
- public List findAll() {
- return toList(smartPlugRepository.findAll());
- }
-
- @GetMapping("/{id}")
- public SmartPlug findById(@PathVariable("id") long id) throws NotFoundException {
- return smartPlugRepository.findById(id).orElseThrow(NotFoundException::new);
- }
-
- private SmartPlug save(SmartPlug newSP, SmartPlugSaveRequest sp) {
+ private SmartPlug save(SmartPlug newSP, SwitchableSaveRequest sp, final Principal principal) {
newSP.setOn(sp.isOn());
newSP.setId(sp.getId());
newSP.setName(sp.getName());
newSP.setRoomId(sp.getRoomId());
- return smartPlugRepository.save(newSP);
+ return deviceService.saveAsOwner(newSP, principal.getName());
}
@PostMapping
- public SmartPlug create(@Valid @RequestBody SmartPlugSaveRequest sp) {
- return save(new SmartPlug(), sp);
+ public SmartPlug create(@Valid @RequestBody SwitchableSaveRequest sp, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(sp.getRoomId(), principal.getName());
+ return save(new SmartPlug(), sp, principal);
}
@PutMapping
- public SmartPlug update(@Valid @RequestBody SmartPlugSaveRequest sp, final Principal principal)
+ public SmartPlug update(@Valid @RequestBody SwitchableSaveRequest sp, final Principal principal)
throws NotFoundException {
return save(
smartPlugRepository
.findByIdAndUsername(sp.getId(), principal.getName())
.orElseThrow(NotFoundException::new),
- sp);
+ sp,
+ principal);
}
@DeleteMapping("/{id}/meter")
@@ -67,7 +61,27 @@ public class SmartPlugController {
}
@DeleteMapping("/{id}")
- public void deleteById(@PathVariable("id") long id) {
- smartPlugRepository.deleteById(id);
+ public void deleteById(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ @PostMapping("/{id}/state")
+ public State extends Switchable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+
+ SmartPlug d =
+ smartPlugRepository
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Switchable> s = d.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
}
}
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 fc64ccb..0847c87 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
@@ -1,13 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
-import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GenericDeviceSaveReguest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchOperationRequest;
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.DeviceService;
import java.security.Principal;
-import java.util.*;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +19,7 @@ public class SwitchController extends InputDeviceConnectionController switchableRepository;
+ private DeviceService deviceService;
/**
* Contstructs the controller by requiring essential object for the controller implementation
@@ -30,33 +29,27 @@ public class SwitchController extends InputDeviceConnectionController outputRepository) {
+ SwitchRepository inputRepository,
+ SwitchableRepository outputRepository,
+ DeviceService deviceService) {
super(inputRepository, outputRepository, Switchable.SWITCH_SWITCHABLE_CONNECTOR);
+ this.deviceService = deviceService;
this.switchRepository = inputRepository;
- this.switchableRepository = outputRepository;
- }
-
- @GetMapping
- public List findAll() {
- return toList(switchRepository.findAll());
- }
-
- @GetMapping("/{id}")
- public Switch findById(@PathVariable("id") long id) throws NotFoundException {
- return switchRepository.findById(id).orElseThrow(NotFoundException::new);
}
@PostMapping
- public Switch create(@Valid @RequestBody GenericDeviceSaveReguest s) {
+ public Switch create(@Valid @RequestBody GenericDeviceSaveReguest s, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(s.getRoomId(), principal.getName());
Switch newSwitch = new Switch();
newSwitch.setName(s.getName());
newSwitch.setRoomId(s.getRoomId());
- return switchRepository.save(newSwitch);
+ return deviceService.saveAsOwner(newSwitch, principal.getName());
}
@PutMapping("/operate")
- public Set operate(
+ public List operate(
@Valid @RequestBody final SwitchOperationRequest sr, final Principal principal)
throws NotFoundException {
final Switch s =
@@ -76,27 +69,13 @@ public class SwitchController extends InputDeviceConnectionController addSwitchable(
- @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId)
- throws NotFoundException {
- return addOutput(inputId, switchableId);
- }
-
- @DeleteMapping("/{id}/lights")
- public Set extends OutputDevice> removeSwitchable(
- @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId)
- throws NotFoundException {
- return removeOutput(inputId, switchableId);
+ deviceService.saveAsOwner(s, principal.getName());
+ return deviceService.saveAllAsOwner(s.getOutputs(), principal.getName(), false);
}
@DeleteMapping("/{id}")
- public void deleteById(@PathVariable("id") long id) {
- switchRepository.deleteById(id);
+ public void deleteById(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchableStateController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchableStateController.java
new file mode 100644
index 0000000..ed6f30c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchableStateController.java
@@ -0,0 +1,38 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableStateSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SwitchableState;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SwitchableStateRepository;
+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.PathVariable;
+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.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/switchableState")
+public class SwitchableStateController {
+
+ @Autowired private SwitchableStateRepository switchableStateRepository;
+
+ @PutMapping
+ public SwitchableState> update(@Valid @RequestBody SwitchableStateSaveRequest ss)
+ throws NotFoundException {
+ final SwitchableState> initial =
+ switchableStateRepository.findById(ss.getId()).orElseThrow(NotFoundException::new);
+ initial.setOn(ss.isOn());
+ switchableStateRepository.save(initial);
+ return initial;
+ }
+
+ @DeleteMapping("/{id}")
+ public void delete(@PathVariable("id") long id) {
+ switchableStateRepository.deleteById(id);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java
new file mode 100644
index 0000000..6761ee5
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java
@@ -0,0 +1,82 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.ThermostatSaveRequest;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
+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.DeviceService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
+import java.security.Principal;
+import javax.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.*;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("/thermostat")
+public class ThermostatController {
+
+ @Autowired private DeviceService deviceService;
+ @Autowired private ThermostatRepository thermostatRepository;
+ @Autowired private ThermostatService thermostatService;
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private StateRepository> stateRepository;
+
+ private Thermostat save(Thermostat newT, ThermostatSaveRequest t, final Principal principal) {
+ newT.setTargetTemperature(t.getTargetTemperature());
+ newT.setId(t.getId());
+ newT.setName(t.getName());
+ newT.setRoomId(t.getRoomId());
+ newT.setMeasuredTemperature(t.getMeasuredTemperature());
+ newT.setUseExternalSensors(t.isUseExternalSensors());
+ newT.setOn(t.isTurnOn());
+
+ newT = deviceService.saveAsOwner(newT, principal.getName());
+ thermostatService.populateMeasuredTemperature(newT);
+ return newT;
+ }
+
+ @PostMapping
+ public Thermostat create(@Valid @RequestBody ThermostatSaveRequest t, final Principal principal)
+ throws NotFoundException {
+ deviceService.throwIfRoomNotOwned(t.getRoomId(), principal.getName());
+ return save(new Thermostat(), t, principal);
+ }
+
+ @PutMapping
+ public Thermostat update(@Valid @RequestBody ThermostatSaveRequest t, final Principal principal)
+ throws NotFoundException {
+ return save(
+ thermostatRepository
+ .findByIdAndUsername(t.getId(), principal.getName())
+ .orElseThrow(NotFoundException::new),
+ t,
+ principal);
+ }
+
+ @DeleteMapping("/{id}")
+ public void deleteById(@PathVariable("id") long id, final Principal principal)
+ throws NotFoundException {
+ deviceService.delete(id, principal.getName());
+ }
+
+ @PostMapping("/{id}/state")
+ public State extends Switchable> sceneBinding(
+ @PathVariable("id") long deviceId,
+ @RequestParam long sceneId,
+ final Principal principal)
+ throws NotFoundException, DuplicateStateException {
+
+ Thermostat d =
+ thermostatRepository
+ .findByIdAndUsername(deviceId, principal.getName())
+ .orElseThrow(NotFoundException::new);
+ State extends Switchable> s = d.cloneState();
+ sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
+ s.setSceneId(sceneId);
+ if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
+ throw new DuplicateStateException();
+ return stateRepository.save(s);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationFastUpdateRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationFastUpdateRequest.java
new file mode 100644
index 0000000..58ce46c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationFastUpdateRequest.java
@@ -0,0 +1,97 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.BooleanTrigger;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RangeTrigger;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ScenePriority;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Trigger;
+import java.util.List;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+public class AutomationFastUpdateRequest {
+ public abstract static class TriggerDTO {
+ @NotNull public long deviceId;
+
+ public abstract Trigger> toModel();
+ }
+
+ public static class BooleanTriggerDTO extends TriggerDTO {
+ @NotNull public boolean on;
+
+ @Override
+ public Trigger> toModel() {
+ BooleanTrigger> t = new BooleanTrigger<>();
+ t.setDeviceId(this.deviceId);
+ t.setOn(this.on);
+ return t;
+ }
+ }
+
+ public static class RangeTriggerDTO extends TriggerDTO {
+ @NotNull RangeTrigger.Operator operator;
+ @NotNull double range;
+
+ @Override
+ public Trigger> toModel() {
+ RangeTrigger> t = new RangeTrigger<>();
+ t.setDeviceId(this.deviceId);
+ t.setOperator(this.operator);
+ t.setRange(this.range);
+ return t;
+ }
+ }
+
+ public static class ScenePriorityDTO {
+ @NotNull public long sceneId;
+
+ @NotNull
+ @Min(0)
+ public Integer priority;
+
+ public ScenePriority toModel() {
+ ScenePriority s = new ScenePriority();
+ s.setSceneId(sceneId);
+ s.setPriority(priority);
+ return s;
+ }
+ }
+
+ @NotNull private List scenes;
+ @NotNull private List triggers;
+ @NotNull private long id;
+
+ @NotNull @NotEmpty private String name;
+
+ public long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getScenes() {
+ return scenes;
+ }
+
+ public void setScenes(List scenes) {
+ this.scenes = scenes;
+ }
+
+ public List getTriggers() {
+ return triggers;
+ }
+
+ public void setTriggers(List triggers) {
+ this.triggers = triggers;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationSaveRequest.java
new file mode 100644
index 0000000..bcd1f8b
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/AutomationSaveRequest.java
@@ -0,0 +1,23 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+public class AutomationSaveRequest {
+
+ private long id;
+
+ @NotNull @NotEmpty private String name;
+
+ public long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/BooleanTriggerSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/BooleanTriggerSaveRequest.java
new file mode 100644
index 0000000..2c08bc0
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/BooleanTriggerSaveRequest.java
@@ -0,0 +1,42 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.validation.constraints.NotNull;
+
+public class BooleanTriggerSaveRequest {
+
+ private long id;
+
+ @NotNull private Long deviceId;
+
+ @NotNull private Long automationId;
+
+ private boolean on;
+
+ public long getId() {
+ return id;
+ }
+
+ public Long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(Long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public Long getAutomationId() {
+ return automationId;
+ }
+
+ public void setAutomationId(Long automationId) {
+ this.automationId = automationId;
+ }
+
+ public boolean isOn() {
+ return on;
+ }
+
+ public void setOn(boolean on) {
+ this.on = on;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableSaveRequest.java
similarity index 90%
rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java
rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableSaveRequest.java
index 74e911b..acefa72 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableLightSaveRequest.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableSaveRequest.java
@@ -4,10 +4,10 @@ import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
-public class DimmableLightSaveRequest {
+public class DimmableSaveRequest {
/** Device id (used only for update requests) */
- private Long id;
+ private long id;
/** The light intensity value. Goes from 0 (off) to 100 (on) */
@NotNull
@@ -48,11 +48,11 @@ public class DimmableLightSaveRequest {
this.intensity = intensity;
}
- public Long getId() {
+ public long getId() {
return id;
}
- public void setId(Long id) {
+ public void setId(long id) {
this.id = id;
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableStateSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableStateSaveRequest.java
new file mode 100644
index 0000000..ebf99eb
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/DimmableStateSaveRequest.java
@@ -0,0 +1,28 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class DimmableStateSaveRequest {
+
+ /** Device id (used only for update requests) */
+ @NotNull private Long id;
+
+ @NotNull
+ @Min(0)
+ @Max(100)
+ private Integer intensity = 0;
+
+ public Integer getIntensity() {
+ return intensity;
+ }
+
+ public void setIntensity(Integer intensity) {
+ this.intensity = intensity;
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GuestPermissionsRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GuestPermissionsRequest.java
new file mode 100644
index 0000000..8c1a2c4
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/GuestPermissionsRequest.java
@@ -0,0 +1,13 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+public class GuestPermissionsRequest {
+ private boolean cameraEnabled;
+
+ public boolean isCameraEnabled() {
+ return cameraEnabled;
+ }
+
+ public void setCameraEnabled(boolean cameraEnabled) {
+ this.cameraEnabled = cameraEnabled;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RangeTriggerSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RangeTriggerSaveRequest.java
new file mode 100644
index 0000000..567c035
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RangeTriggerSaveRequest.java
@@ -0,0 +1,57 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RangeTrigger;
+import javax.validation.constraints.NotNull;
+
+public class RangeTriggerSaveRequest {
+
+ private long id;
+
+ @NotNull private Long deviceId;
+
+ @NotNull private Long automationId;
+
+ @NotNull private RangeTrigger.Operator operator;
+
+ @NotNull private double range;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(Long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public Long getAutomationId() {
+ return automationId;
+ }
+
+ public void setAutomationId(Long automationId) {
+ this.automationId = automationId;
+ }
+
+ public RangeTrigger.Operator getOperator() {
+ return operator;
+ }
+
+ public void setOperator(RangeTrigger.Operator operator) {
+ this.operator = operator;
+ }
+
+ public double getRange() {
+ return range;
+ }
+
+ public void setRange(Double range) {
+ this.range = range;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ScenePrioritySaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ScenePrioritySaveRequest.java
new file mode 100644
index 0000000..14a1873
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ScenePrioritySaveRequest.java
@@ -0,0 +1,38 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class ScenePrioritySaveRequest {
+
+ @NotNull private Long automationId;
+
+ @Min(0)
+ private Integer priority;
+
+ @NotNull private Long sceneId;
+
+ public Long getAutomationId() {
+ return automationId;
+ }
+
+ public void setAutomationId(Long automationId) {
+ this.automationId = automationId;
+ }
+
+ public Integer getPriority() {
+ return priority;
+ }
+
+ public void setPriority(Integer priority) {
+ this.priority = priority;
+ }
+
+ public Long getSceneId() {
+ return sceneId;
+ }
+
+ public void setSceneId(Long sceneId) {
+ this.sceneId = sceneId;
+ }
+}
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
new file mode 100644
index 0000000..080ea2b
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SceneSaveRequest.java
@@ -0,0 +1,36 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import com.sun.istack.NotNull;
+import javax.persistence.Column;
+
+public class SceneSaveRequest {
+
+ /** Room identifier */
+ private long id;
+
+ /** The user given name of this room (e.g. 'Master bedroom') */
+ @NotNull private String name;
+
+ /** Determines whether a guest can access this scene */
+ @Column @NotNull private boolean guestAccessEnabled;
+
+ public boolean isGuestAccessEnabled() {
+ return guestAccessEnabled;
+ }
+
+ public void setGuestAccessEnabled(boolean guestAccessEnabled) {
+ this.guestAccessEnabled = guestAccessEnabled;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java
deleted file mode 100644
index 6b2f9b5..0000000
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SmartPlugSaveRequest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
-
-import javax.validation.constraints.NotNull;
-
-public class SmartPlugSaveRequest {
- /** Whether the smart plug is on */
- @NotNull private boolean on;
-
- /** Device identifier */
- private long id;
-
- /**
- * The room this device belongs in, as a foreign key id. To use when updating and inserting from
- * a REST call.
- */
- @NotNull private Long roomId;
-
- /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
- @NotNull private String name;
-
- public void setRoomId(Long roomId) {
- this.roomId = roomId;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public long getId() {
- return id;
- }
-
- public Long getRoomId() {
- return roomId;
- }
-
- public String getName() {
- return name;
- }
-
- public boolean isOn() {
- return on;
- }
-
- public void setOn(boolean on) {
- this.on = on;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableSaveRequest.java
similarity index 96%
rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java
rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableSaveRequest.java
index 99211e5..16caaed 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/RegularLightSaveRequest.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableSaveRequest.java
@@ -2,7 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
import javax.validation.constraints.NotNull;
-public class RegularLightSaveRequest {
+public class SwitchableSaveRequest {
/** The state of this switch */
private boolean on;
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableStateSaveRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableStateSaveRequest.java
new file mode 100644
index 0000000..ab03f27
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/SwitchableStateSaveRequest.java
@@ -0,0 +1,23 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import javax.validation.constraints.NotNull;
+
+public class SwitchableStateSaveRequest {
+
+ /** Device id (used only for update requests) */
+ @NotNull private Long id;
+
+ @NotNull private boolean on;
+
+ public boolean isOn() {
+ return on;
+ }
+
+ public void setOn(boolean on) {
+ this.on = on;
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
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
new file mode 100644
index 0000000..3986996
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/ThermostatSaveRequest.java
@@ -0,0 +1,85 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
+
+import java.math.BigDecimal;
+import javax.validation.constraints.NotNull;
+
+public class ThermostatSaveRequest {
+
+ /** Device identifier */
+ private long id;
+
+ /**
+ * The room this device belongs in, as a foreign key id. To use when updating and inserting from
+ * a REST call.
+ */
+ @NotNull private Long roomId;
+
+ /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
+ @NotNull private String name;
+
+ /** Temperature to be reached */
+ @NotNull private BigDecimal targetTemperature;
+
+ @NotNull private boolean useExternalSensors;
+
+ @NotNull private BigDecimal measuredTemperature;
+
+ /** State of this thermostat */
+ @NotNull private boolean turnOn;
+
+ public boolean isTurnOn() {
+ return turnOn;
+ }
+
+ public void setTurnOn(boolean turnOn) {
+ this.turnOn = turnOn;
+ }
+
+ public boolean isUseExternalSensors() {
+ return useExternalSensors;
+ }
+
+ public void setUseExternalSensors(boolean useExternalSensors) {
+ this.useExternalSensors = useExternalSensors;
+ }
+
+ public BigDecimal getTargetTemperature() {
+ return this.targetTemperature;
+ }
+
+ public void setTargetTemperature(BigDecimal targetTemperature) {
+ this.targetTemperature = targetTemperature;
+ }
+
+ public void setRoomId(Long roomId) {
+ this.roomId = roomId;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public Long getRoomId() {
+ return roomId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ 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/error/DuplicateStateException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/DuplicateStateException.java
new file mode 100644
index 0000000..4e9f3d5
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/DuplicateStateException.java
@@ -0,0 +1,12 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.error;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.BAD_REQUEST)
+public class DuplicateStateException extends Exception {
+ public DuplicateStateException() {
+ super(
+ "Cannot create state since it has already been created for this scene and this device");
+ }
+}
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
new file mode 100644
index 0000000..e6b4d0b
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Automation.java
@@ -0,0 +1,81 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.HashSet;
+import java.util.Set;
+import javax.persistence.*;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Entity
+public class Automation {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
+ private long id;
+
+ @ManyToOne
+ @JoinColumn(name = "user_id", updatable = false, insertable = false)
+ @GsonExclude
+ private User user;
+
+ @NotNull
+ @Column(name = "user_id", nullable = false)
+ @GsonExclude
+ private Long userId;
+
+ @OneToMany(mappedBy = "automation", orphanRemoval = true)
+ private Set> triggers = new HashSet<>();
+
+ @OneToMany(mappedBy = "automation", cascade = CascadeType.REMOVE)
+ private Set scenes = new HashSet<>();
+
+ @NotNull @NotEmpty private String name;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Set> getStates() {
+ return triggers;
+ }
+
+ public Set getScenes() {
+ return scenes;
+ }
+
+ public Set> getTriggers() {
+ return triggers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/AutomationRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/AutomationRepository.java
new file mode 100644
index 0000000..b874146
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/AutomationRepository.java
@@ -0,0 +1,15 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.EntityGraph;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+
+public interface AutomationRepository extends CrudRepository {
+ @EntityGraph(attributePaths = {"scenes", "triggers"})
+ List findAllByUserId(@Param("userId") long userId);
+
+ @EntityGraph(attributePaths = {"scenes", "triggers"})
+ Optional findByIdAndUserId(@Param("id") long id, @Param("userId") long userId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTrigger.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTrigger.java
new file mode 100644
index 0000000..f6e493e
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTrigger.java
@@ -0,0 +1,32 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+@Entity
+public class BooleanTrigger extends Trigger {
+
+ @Column(name = "switchable_on")
+ private boolean on;
+
+ public BooleanTrigger() {
+ super("booleanTrigger");
+ }
+
+ public boolean isOn() {
+ return on;
+ }
+
+ public void setOn(boolean on) {
+ this.on = on;
+ }
+
+ public boolean check(boolean on) {
+ return this.on == on;
+ }
+
+ @Override
+ public boolean triggered() {
+ return getDevice().readTriggerState() == isOn();
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerRepository.java
new file mode 100644
index 0000000..08b8898
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerRepository.java
@@ -0,0 +1,10 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import org.springframework.data.repository.query.Param;
+
+public interface BooleanTriggerRepository
+ extends TriggerRepository> {
+
+ List> findAllByAutomationId(@Param("automationId") long automationId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerable.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerable.java
new file mode 100644
index 0000000..6ff219f
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/BooleanTriggerable.java
@@ -0,0 +1,5 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface BooleanTriggerable {
+ boolean readTriggerState();
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java
index da9af1c..e41265a 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ButtonDimmer.java
@@ -18,14 +18,14 @@ public class ButtonDimmer extends Dimmer {
/** Increases the current intensity level of the dimmable light by DIM_INCREMENT */
public void increaseIntensity() {
- for (DimmableLight dl : getOutputs()) {
+ for (Dimmable dl : getOutputs()) {
dl.setIntensity(dl.getIntensity() + DIM_INCREMENT);
}
}
/** Decreases the current intensity level of the dimmable light by DIM_INCREMENT */
public void decreaseIntensity() {
- for (DimmableLight dl : getOutputs()) {
+ for (Dimmable dl : getOutputs()) {
dl.setIntensity(dl.getIntensity() - DIM_INCREMENT);
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java
index 91b1e88..701a010 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Connector.java
@@ -23,25 +23,25 @@ public interface Connector {
void connect(I input, O output, boolean connect);
/**
- * Produces a basic implementation of a connector, assuming there is a OneToMany relationship
+ * Produces a basic implementation of a connector, assuming there is a ManyToMany relationship
* between J and K
*
* @param outputsGetter the getter method of the set of outputs on the input class
- * @param inputSetter the setter method for the input id on the output class
+ * @param inputsGetter the getter method of the set of outputs on the input class
* @param the input device type
* @param the output device type
* @return a Connector implementation for the pair of types J and K
*/
static Connector basic(
- Function> outputsGetter, BiConsumer inputSetter) {
+ Function> outputsGetter, Function> inputsGetter) {
return (i, o, connect) -> {
if (connect) {
outputsGetter.apply(i).add(o);
+ inputsGetter.apply(o).add(i);
} else {
outputsGetter.apply(i).remove(o);
+ inputsGetter.apply(o).remove(i);
}
-
- inputSetter.accept(o, connect ? i.getId() : null);
};
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Curtains.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Curtains.java
new file mode 100644
index 0000000..d9e8d98
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Curtains.java
@@ -0,0 +1,14 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import javax.persistence.Entity;
+
+/**
+ * Represents a curtain. The intensity represents how much the curtains are opened,
+ * 0 is completely closed 100 is completely open
+ */
+@Entity
+public class Curtains extends Dimmable {
+ public Curtains() {
+ super("curtains");
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/CurtainsRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/CurtainsRepository.java
new file mode 100644
index 0000000..39821f1
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/CurtainsRepository.java
@@ -0,0 +1,3 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface CurtainsRepository extends DimmableRepository {}
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 295fe37..397bb1e 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
@@ -1,14 +1,17 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
import com.google.gson.annotations.SerializedName;
import io.swagger.annotations.ApiModelProperty;
+import java.util.HashSet;
+import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
/** Generic abstraction for a smart home device */
@Entity
-@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Device {
/** Ways a device can behave in the automation flow. For now only input/output */
@@ -30,8 +33,14 @@ public abstract class Device {
@ManyToOne
@JoinColumn(name = "room_id", updatable = false, insertable = false)
@GsonExclude
+ @SocketGsonExclude
private Room room;
+ @OneToMany(mappedBy = "device", orphanRemoval = true)
+ @GsonExclude
+ @SocketGsonExclude
+ private Set> triggers = new HashSet<>();
+
/**
* The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call.
@@ -57,6 +66,41 @@ public abstract class Device {
*/
@Transient private final FlowType flowType;
+ @OneToMany(mappedBy = "device", orphanRemoval = true)
+ @GsonExclude
+ @SocketGsonExclude
+ private Set> states = new HashSet<>();
+
+ @Transient @GsonExclude private boolean fromHost = false;
+
+ @Transient @GsonExclude private boolean fromGuest = false;
+
+ @Transient @GsonExclude private boolean deleted = false;
+
+ public boolean isFromHost() {
+ return fromHost;
+ }
+
+ public boolean isDeleted() {
+ return deleted;
+ }
+
+ public void setDeleted(boolean deleted) {
+ this.deleted = deleted;
+ }
+
+ public boolean isFromGuest() {
+ return fromGuest;
+ }
+
+ public void setFromGuest(boolean fromGuest) {
+ 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/DeviceRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
index ef57a88..d1f828d 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DeviceRepository.java
@@ -2,12 +2,11 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List;
import java.util.Optional;
+import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
-import javax.transaction.Transactional;
-
/**
* DeviceRepository acts as a superclass for the other repositories so to mirror in the database the
* class inheritance present among the various devices.
@@ -15,6 +14,10 @@ import javax.transaction.Transactional;
public interface DeviceRepository extends CrudRepository {
List findByRoomId(@Param("roomId") long roomId);
+ @Query(
+ "SELECT COUNT(d) FROM Device d JOIN d.room r JOIN r.user u WHERE d.name = ?1 AND u.username = ?2")
+ Integer findDuplicates(String name, String username);
+
/**
* Finds devices by their id and a username
*
@@ -26,6 +29,16 @@ public interface DeviceRepository extends CrudRepository findByIdAndUsername(Long id, String username);
+ /**
+ * Finds devices by their id and a user id
+ *
+ * @param id the device id
+ * @param userId a User's id
+ * @return an optional device, empty if none found
+ */
+ @Query("SELECT d FROM Device d JOIN d.room r JOIN r.user u WHERE d.id = ?1 AND u.id = ?2")
+ Optional findByIdAndUserId(Long id, Long userId);
+
/**
* Finds all devices belonging to a user
*
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmable.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmable.java
new file mode 100644
index 0000000..6ae3150
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmable.java
@@ -0,0 +1,95 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
+import java.util.Set;
+import javax.persistence.*;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+@Entity
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+public class Dimmable extends Switchable implements RangeTriggerable {
+
+ public static final Connector KNOB_DIMMER_DIMMABLE_CONNECTOR =
+ Connector.basic(KnobDimmer::getOutputs, Dimmable::getDimmers);
+
+ public static final Connector BUTTON_DIMMER_DIMMABLE_CONNECTOR =
+ Connector.basic(ButtonDimmer::getOutputs, Dimmable::getDimmers);
+
+ protected Dimmable(String kind) {
+ super(kind);
+ }
+
+ @ManyToMany(mappedBy = "dimmables", cascade = CascadeType.DETACH)
+ @GsonExclude
+ @SocketGsonExclude
+ private Set dimmers;
+
+ /** The light intensity value. Goes from 0 (off) to 100 (on) */
+ @NotNull
+ @Column(nullable = false)
+ @Min(0)
+ @Max(100)
+ private Integer intensity = 0;
+
+ @NotNull
+ @Column(nullable = false)
+ private Integer oldIntensity = 100;
+
+ public Integer getIntensity() {
+ return intensity;
+ }
+
+ /**
+ * Sets the intensity to a certain level. Out of bound values are corrected to the respective
+ * extremums. An intensity level of 0 turns the light off, but keeps the old intensity level
+ * stored.
+ *
+ * @param intensity the intensity level (may be out of bounds)
+ */
+ public void setIntensity(Integer intensity) {
+ if (intensity <= 0) {
+ this.intensity = 0;
+ } else if (intensity > 100) {
+ this.intensity = 100;
+ this.oldIntensity = 100;
+ } else {
+ this.intensity = intensity;
+ this.oldIntensity = intensity;
+ }
+ }
+
+ @Override
+ public boolean isOn() {
+ return intensity != 0;
+ }
+
+ @Override
+ public void setOn(boolean on) {
+ intensity = on ? oldIntensity : 0;
+ }
+
+ public Set getDimmers() {
+ return this.dimmers;
+ }
+
+ public void readStateAndSet(DimmableState extends Dimmable> state) {
+ setIntensity(state.getIntensity());
+ }
+
+ @Override
+ public State extends Dimmable> cloneState() {
+ final DimmableState newState = new DimmableState<>();
+ newState.setDeviceId(getId());
+ newState.setDevice(this);
+ newState.setIntensity(getIntensity());
+ return newState;
+ }
+
+ @Override
+ public double readTriggerState() {
+ return intensity;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java
index 6b537c6..89806a7 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLight.java
@@ -1,89 +1,11 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.validation.constraints.Max;
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
+import javax.persistence.*;
/** Represent a dimmable light */
@Entity
-public class DimmableLight extends Switchable {
-
- public static final Connector
- BUTTON_DIMMER_DIMMABLE_LIGHT_CONNECTOR =
- Connector.basic(ButtonDimmer::getOutputs, DimmableLight::setDimmerId);
-
- public static final Connector KNOB_DIMMER_DIMMABLE_LIGHT_CONNECTOR =
- Connector.basic(KnobDimmer::getOutputs, DimmableLight::setDimmerId);
-
+public class DimmableLight extends Dimmable {
public DimmableLight() {
super("dimmableLight");
}
-
- @ManyToOne
- @GsonExclude
- @JoinColumn(name = "dimmer_id", updatable = false, insertable = false)
- private Dimmer dimmer;
-
- @Column(name = "dimmer_id")
- private Long dimmerId;
-
- /** The light intensity value. Goes from 0 (off) to 100 (on) */
- @NotNull
- @Column(nullable = false)
- @Min(0)
- @Max(100)
- private Integer intensity = 0;
-
- @NotNull
- @Column(nullable = false)
- private Integer oldIntensity = 100;
-
- public Integer getIntensity() {
- return intensity;
- }
-
- /**
- * Sets the intensity to a certain level. Out of bound values are corrected to the respective
- * extremums. An intensity level of 0 turns the light off, but keeps the old intensity level
- * stored.
- *
- * @param intensity the intensity level (may be out of bounds)
- */
- public void setIntensity(Integer intensity) {
- if (intensity <= 0) {
- this.intensity = 0;
- } else if (intensity > 100) {
- this.intensity = 100;
- this.oldIntensity = 100;
- } else {
- this.intensity = intensity;
- this.oldIntensity = intensity;
- }
- }
-
- @Override
- public boolean isOn() {
- return intensity != 0;
- }
-
- @Override
- public void setOn(boolean on) {
- intensity = on ? oldIntensity : 0;
- }
-
- public void setDimmerId(Long dimmerId) {
- this.dimmerId = dimmerId;
- super.setSwitchId(null);
- }
-
- @Override
- public void setSwitchId(Long switchId) {
- super.setSwitchId(switchId);
- this.dimmerId = null;
- }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java
index a32b3c6..5a45949 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableLightRepository.java
@@ -1,3 +1,3 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
-public interface DimmableLightRepository extends SwitchableRepository {}
+public interface DimmableLightRepository extends DimmableRepository {}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableRepository.java
new file mode 100644
index 0000000..be791c9
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableRepository.java
@@ -0,0 +1,4 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface DimmableRepository extends SwitchableRepository {
+}
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
new file mode 100644
index 0000000..7c0b8d2
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableState.java
@@ -0,0 +1,28 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import javax.persistence.Entity;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
+/** Represent a state for an IDimmable device */
+@Entity
+public class DimmableState extends State {
+
+ /** The light intensity value. Goes from 0 (off) to 100 (on) */
+ @Min(0)
+ @Max(100)
+ private int intensity = 0;
+
+ public int getIntensity() {
+ return intensity;
+ }
+
+ public void setIntensity(int dimAmount) {
+ this.intensity = dimAmount;
+ }
+
+ @Override
+ public void apply() {
+ getDevice().readStateAndSet(this);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableStateRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableStateRepository.java
new file mode 100644
index 0000000..00edb96
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/DimmableStateRepository.java
@@ -0,0 +1,3 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface DimmableStateRepository extends StateRepository> {}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java
index d06bece..e920d2f 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Dimmer.java
@@ -1,12 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
import java.util.HashSet;
import java.util.Set;
-import javax.persistence.Entity;
-import javax.persistence.Inheritance;
-import javax.persistence.InheritanceType;
-import javax.persistence.OneToMany;
-import javax.persistence.PreRemove;
+import javax.persistence.*;
/** Represents a generic dimmer input device */
@Entity
@@ -16,8 +14,14 @@ public abstract class Dimmer extends InputDevice {
super(kind);
}
- @OneToMany(mappedBy = "dimmer")
- private Set lights = new HashSet<>();
+ @ManyToMany(cascade = CascadeType.DETACH)
+ @GsonExclude
+ @SocketGsonExclude
+ @JoinTable(
+ name = "dimmer_dimmable",
+ joinColumns = @JoinColumn(name = "dimmer_id"),
+ inverseJoinColumns = @JoinColumn(name = "dimmable_id"))
+ private Set dimmables = new HashSet<>();
/**
* Get the lights connected to this dimmer
@@ -25,19 +29,12 @@ public abstract class Dimmer extends InputDevice {
* @return duh
*/
@Override
- public Set getOutputs() {
- return this.lights;
+ public Set getOutputs() {
+ return this.dimmables;
}
/** Add a light to be controller by this dimmer */
- public void addDimmableLight(DimmableLight dimmableLight) {
- lights.add(dimmableLight);
- }
-
- @PreRemove
- private void removeLightsFromDimmer() {
- for (DimmableLight dl : getOutputs()) {
- dl.setDimmerId(null);
- }
+ public void addDimmable(Dimmable dimmable) {
+ dimmables.add(dimmable);
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/EagerUserRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/EagerUserRepository.java
new file mode 100644
index 0000000..ac68fa3
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/EagerUserRepository.java
@@ -0,0 +1,13 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.Optional;
+import org.springframework.data.jpa.repository.EntityGraph;
+import org.springframework.data.repository.CrudRepository;
+
+public interface EagerUserRepository extends CrudRepository {
+ @EntityGraph(attributePaths = {"guests"})
+ User findByUsername(String username);
+
+ @EntityGraph(attributePaths = {"guests"})
+ Optional findById(Long userId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java
index da45b67..2acf0c2 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/InputDevice.java
@@ -10,7 +10,7 @@ import javax.persistence.InheritanceType;
* environment (sensor) or the user (switch / dimmer).
*/
@Entity
-@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Inheritance(strategy = InheritanceType.JOINED)
public abstract class InputDevice extends Device {
public InputDevice(String kind) {
super(kind, FlowType.INPUT);
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
index c116165..89273f8 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import javax.persistence.Column;
import javax.persistence.Entity;
/**
@@ -7,7 +8,9 @@ import javax.persistence.Entity;
* value, like a knob)
*/
@Entity
-public class KnobDimmer extends Dimmer {
+public class KnobDimmer extends Dimmer implements RangeTriggerable {
+
+ @Column private int intensity = 0;
public KnobDimmer() {
super("knobDimmer");
@@ -19,8 +22,14 @@ public class KnobDimmer extends Dimmer {
* @param intensity the intensity (must be from 0 to 100)
*/
public void setLightIntensity(int intensity) {
- for (DimmableLight dl : getOutputs()) {
+ this.intensity = intensity;
+ for (Dimmable dl : getOutputs()) {
dl.setIntensity(intensity);
}
}
+
+ @Override
+ public double readTriggerState() {
+ return intensity;
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java
index 6c5baf7..4998922 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/MotionSensor.java
@@ -5,7 +5,7 @@ import javax.persistence.Entity;
/** Represents a motion sensor device */
@Entity
-public class MotionSensor extends InputDevice {
+public class MotionSensor extends InputDevice implements BooleanTriggerable {
@Column(nullable = false)
private boolean detected;
@@ -21,4 +21,9 @@ public class MotionSensor extends InputDevice {
public MotionSensor() {
super("motionSensor");
}
+
+ @Override
+ public boolean readTriggerState() {
+ return detected;
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java
index c5b401f..ba1813e 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/OutputDevice.java
@@ -7,9 +7,17 @@ import javax.persistence.*;
* ...).
*/
@Entity
-@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Inheritance(strategy = InheritanceType.JOINED)
public abstract class OutputDevice extends Device {
public OutputDevice(String kind) {
super(kind, FlowType.OUTPUT);
}
+
+ /**
+ * Creates a State> object initialized to point at this device and with values copied from
+ * this device's state
+ *
+ * @return a new State> object
+ */
+ public abstract State extends OutputDevice> cloneState();
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTrigger.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTrigger.java
new file mode 100644
index 0000000..19e179c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTrigger.java
@@ -0,0 +1,69 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import com.google.gson.annotations.SerializedName;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.validation.constraints.NotNull;
+
+@Entity
+public class RangeTrigger extends Trigger {
+
+ public RangeTrigger() {
+ super("rangeTrigger");
+ }
+
+ @Override
+ public boolean triggered() {
+ double value = getDevice().readTriggerState();
+ switch (operator) {
+ case EQUAL:
+ return value == range;
+ case LESS:
+ return value < range;
+ case GREATER:
+ return value > range;
+ case GREATER_EQUAL:
+ return value >= range;
+ case LESS_EQUAL:
+ return value <= range;
+ }
+ throw new IllegalStateException();
+ }
+
+ public enum Operator {
+ @SerializedName("EQUAL")
+ EQUAL,
+ @SerializedName("LESS")
+ LESS,
+ @SerializedName("GREATER")
+ GREATER,
+ @SerializedName("LESS_EQUAL")
+ LESS_EQUAL,
+ @SerializedName("GREATER_EQUAL")
+ GREATER_EQUAL
+ }
+
+ @NotNull
+ @Column(nullable = false)
+ private Operator operator;
+
+ @NotNull
+ @Column(nullable = false)
+ private double range;
+
+ public Operator getOperator() {
+ return operator;
+ }
+
+ public void setOperator(Operator operator) {
+ this.operator = operator;
+ }
+
+ public double getRange() {
+ return range;
+ }
+
+ public void setRange(Double range) {
+ this.range = range;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerRepository.java
new file mode 100644
index 0000000..8ef6772
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerRepository.java
@@ -0,0 +1,9 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import org.springframework.data.repository.query.Param;
+
+public interface RangeTriggerRepository extends TriggerRepository> {
+
+ List> findAllByAutomationId(@Param("automationId") long automationId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerable.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerable.java
new file mode 100644
index 0000000..489a763
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RangeTriggerable.java
@@ -0,0 +1,5 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface RangeTriggerable {
+ double readTriggerState();
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java
index 2dcff1b..2d06c59 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RegularLight.java
@@ -6,7 +6,7 @@ import javax.validation.constraints.NotNull;
/** Represents a standard non-dimmable light */
@Entity
-public class RegularLight extends Switchable {
+public class RegularLight extends Switchable implements BooleanTriggerable {
/** Whether the light is on or not */
@Column(name = "light_on", nullable = false)
@@ -27,4 +27,9 @@ public class RegularLight extends Switchable {
public void setOn(boolean on) {
this.on = on;
}
+
+ @Override
+ public boolean readTriggerState() {
+ return on;
+ }
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java
index b02413d..7c27baa 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/RoomRepository.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
@@ -15,4 +16,10 @@ public interface RoomRepository extends CrudRepository {
*/
@Query("SELECT r FROM Room r JOIN r.user u WHERE r.id = ?1 AND u.username = ?2")
Optional findByIdAndUsername(Long id, String username);
+
+ @Query("SELECT r FROM Room r JOIN r.user u WHERE u.username = ?1")
+ List findByUsername(String username);
+
+ @Query("SELECT r FROM Room r JOIN r.user u WHERE u.id = ?1")
+ List findByUserId(Long hostId);
}
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
new file mode 100644
index 0000000..f35d54d
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Scene.java
@@ -0,0 +1,92 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.HashSet;
+import java.util.Set;
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Represent a collection of state changes to devices even in different rooms but belonging to the
+ * same user
+ */
+@Entity
+public class Scene {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
+ private long id;
+
+ @ManyToOne
+ @JoinColumn(name = "user_id", updatable = false, insertable = false)
+ @GsonExclude
+ private User user;
+
+ @NotNull
+ @Column(name = "user_id", nullable = false)
+ @GsonExclude
+ private Long userId;
+
+ @OneToMany(mappedBy = "scene", orphanRemoval = true)
+ @GsonExclude
+ private Set> states = new HashSet<>();
+
+ /** The user given name of this room (e.g. 'Master bedroom') */
+ @NotNull
+ @Column(nullable = false)
+ private String name;
+
+ /** Determines whether a guest can access this scene */
+ @Column private boolean guestAccessEnabled;
+
+ public boolean isGuestAccessEnabled() {
+ return guestAccessEnabled;
+ }
+
+ public void setGuestAccessEnabled(boolean guestAccessEnabled) {
+ this.guestAccessEnabled = guestAccessEnabled;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public Set> getStates() {
+ return states;
+ }
+
+ public void setStates(Set> states) {
+ this.states = states;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriority.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriority.java
new file mode 100644
index 0000000..64e755b
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriority.java
@@ -0,0 +1,97 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import io.swagger.annotations.ApiModelProperty;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.PreRemove;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+@Entity
+public class ScenePriority {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
+ private long id;
+
+ @ManyToOne
+ @JoinColumn(name = "automation_id", updatable = false, insertable = false)
+ @GsonExclude
+ private Automation automation;
+
+ @Column(name = "automation_id", nullable = false)
+ @NotNull
+ private Long automationId;
+
+ @NotNull
+ @Min(0)
+ @Column(nullable = false)
+ private Integer priority;
+
+ @ManyToOne
+ @JoinColumn(name = "scene_id", updatable = false, insertable = false)
+ @GsonExclude
+ private Scene scene;
+
+ @Column(name = "scene_id", nullable = false, updatable = false)
+ @NotNull
+ private Long sceneId;
+
+ public Integer getPriority() {
+ return priority;
+ }
+
+ public void setPriority(Integer priority) {
+ this.priority = priority;
+ }
+
+ public Automation getAutomation() {
+ return automation;
+ }
+
+ public void setAutomation(Automation automation) {
+ this.automation = automation;
+ }
+
+ public Long getAutomationId() {
+ return automationId;
+ }
+
+ public ScenePriority setAutomationId(Long automationId) {
+ this.automationId = automationId;
+ return this;
+ }
+
+ public Scene getScene() {
+ return scene;
+ }
+
+ public void setScene(Scene scene) {
+ this.scene = scene;
+ }
+
+ public Long getSceneId() {
+ return sceneId;
+ }
+
+ public void setSceneId(Long sceneId) {
+ this.sceneId = sceneId;
+ }
+
+ @PreRemove
+ public void preRemove() {
+ this.setAutomation(null);
+ this.setAutomationId(null);
+
+ this.setScene(null);
+ this.setSceneId(null);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriorityRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriorityRepository.java
new file mode 100644
index 0000000..67e14e6
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ScenePriorityRepository.java
@@ -0,0 +1,19 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+
+public interface ScenePriorityRepository extends CrudRepository {
+
+ List findAllBySceneId(@Param("sceneId") long sceneId);
+
+ @Transactional
+ void deleteBySceneId(@Param("sceneId") long sceneId);
+
+ List findAllByAutomationId(@Param("automationId") long automationId);
+
+ @Transactional
+ void deleteAllByAutomationId(Long automationId);
+}
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
new file mode 100644
index 0000000..a4aa68c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SceneRepository.java
@@ -0,0 +1,22 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.CrudRepository;
+
+public interface SceneRepository extends CrudRepository {
+
+ /**
+ * Finds a room by their id and a username
+ *
+ * @param id the scene id
+ * @param username a User's username
+ * @return an optional scene, empty if none found
+ */
+ @Query("SELECT s FROM Scene s JOIN s.user u WHERE s.id = ?1 AND u.username = ?2")
+ Optional findByIdAndUsername(Long id, String username);
+
+ @Query("SELECT s FROM Scene s JOIN s.user u WHERE u.username = ?1")
+ List findByUsername(String username);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCamera.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCamera.java
new file mode 100644
index 0000000..3d4cef9
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCamera.java
@@ -0,0 +1,41 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.validation.constraints.NotNull;
+
+@Entity
+public class SecurityCamera extends Switchable implements BooleanTriggerable {
+
+ public SecurityCamera() {
+ super("securityCamera");
+ this.on = false;
+ }
+
+ @Column(name = "camera_on", nullable = false)
+ @NotNull
+ private boolean on;
+
+ @Column(name = "video", nullable = false)
+ @NotNull
+ private String path = "/security_camera_videos/security_camera_1.mp4";
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean isOn() {
+ return on;
+ }
+
+ @Override
+ public void setOn(boolean on) {
+ this.on = on;
+ }
+
+ @Override
+ public boolean readTriggerState() {
+ return on;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCameraRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCameraRepository.java
new file mode 100644
index 0000000..5899113
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityCameraRepository.java
@@ -0,0 +1,3 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface SecurityCameraRepository extends SwitchableRepository {}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
index 6b5e6b9..2fb5442 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java
@@ -11,7 +11,7 @@ import javax.validation.constraints.NotNull;
/** A sensor input device that measures a quantity in a continuous scale (e.g. temperature) */
@Entity
-public class Sensor extends InputDevice {
+public class Sensor extends InputDevice implements RangeTriggerable {
public static final Map TYPICAL_VALUES =
Map.of(
@@ -19,6 +19,11 @@ public class Sensor extends InputDevice {
SensorType.HUMIDITY, new BigDecimal(40.0),
SensorType.LIGHT, new BigDecimal(1000));
+ @Override
+ public double readTriggerState() {
+ return value.doubleValue();
+ }
+
/** Type of sensor, i.e. of the thing the sensor measures. */
public enum SensorType {
/** A sensor that measures temperature in degrees celsius */
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SensorRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SensorRepository.java
index b796277..dd040e8 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SensorRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SensorRepository.java
@@ -1,3 +1,7 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
-public interface SensorRepository extends DeviceRepository {}
+import java.util.List;
+
+public interface SensorRepository extends DeviceRepository {
+ List findAllBySensor(Sensor.SensorType sensor);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java
index 9b07e69..77f6c3d 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SmartPlug.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -7,7 +8,7 @@ import javax.validation.constraints.NotNull;
/** A smart plug that can be turned either on or off */
@Entity
-public class SmartPlug extends Switchable {
+public class SmartPlug extends Switchable implements BooleanTriggerable {
/** The average consumption of an active plug when on in Watt */
public static final Double AVERAGE_CONSUMPTION_KW = 200.0;
@@ -44,4 +45,9 @@ public class SmartPlug extends Switchable {
public SmartPlug() {
super("smartPlug");
}
+
+ @Override
+ public boolean readTriggerState() {
+ return on;
+ }
}
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
new file mode 100644
index 0000000..01398b8
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/State.java
@@ -0,0 +1,96 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import io.swagger.annotations.ApiModelProperty;
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Represents instructions on how to change the state of a particular device. Many states (plus
+ * other properties) form a Scene
+ */
+@Entity
+@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"device_id", "scene_id"})})
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+public abstract class State {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
+ private long id;
+
+ @ManyToOne(targetEntity = OutputDevice.class)
+ @JoinColumn(name = "device_id", updatable = false, insertable = false)
+ @GsonExclude
+ private D device;
+
+ /**
+ * The device this state belongs in, as a foreign key id. To use when updating and inserting
+ * from a REST call.
+ */
+ @Column(name = "device_id", nullable = false)
+ @NotNull
+ private Long deviceId;
+
+ @ManyToOne
+ @JoinColumn(name = "scene_id", updatable = false, insertable = false)
+ @GsonExclude
+ private Scene scene;
+
+ @Column(name = "scene_id", nullable = false)
+ @NotNull
+ private Long sceneId;
+
+ /** Sets the state of the connected device to the state represented by this object. */
+ public abstract void apply();
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public D getDevice() {
+ return device;
+ }
+
+ public void setDevice(D device) {
+ this.device = device;
+ }
+
+ public Long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(Long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public Scene getScene() {
+ return scene;
+ }
+
+ public void setScene(Scene scene) {
+ this.scene = scene;
+ }
+
+ public Long getSceneId() {
+ return sceneId;
+ }
+
+ public void setSceneId(Long sceneId) {
+ this.sceneId = sceneId;
+ }
+
+ @PreRemove
+ public void removeDeviceAndScene() {
+ this.setScene(null);
+ this.setSceneId(null);
+
+ this.setDevice(null);
+ this.setDeviceId(null);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/StateRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/StateRepository.java
new file mode 100644
index 0000000..d2d1278
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/StateRepository.java
@@ -0,0 +1,16 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+
+public interface StateRepository> extends CrudRepository {
+
+ @Transactional
+ void deleteAllBySceneId(long roomId);
+
+ List findBySceneId(@Param("sceneId") long sceneId);
+
+ Integer countByDeviceIdAndSceneId(long deviceId, long sceneId);
+}
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 8a8057c..4b9ef3a 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
@@ -1,17 +1,22 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
import java.util.HashSet;
import java.util.Set;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.OneToMany;
-import javax.persistence.PreRemove;
+import javax.persistence.*;
/** A switch input device */
@Entity
-public class Switch extends InputDevice {
+public class Switch extends InputDevice implements BooleanTriggerable {
- @OneToMany(mappedBy = "switchDevice")
+ @ManyToMany(cascade = CascadeType.DETACH)
+ @GsonExclude
+ @SocketGsonExclude
+ @JoinTable(
+ name = "switch_switchable",
+ joinColumns = @JoinColumn(name = "switch_id"),
+ inverseJoinColumns = @JoinColumn(name = "switchable_id"))
private Set switchables = new HashSet<>();
/** The state of this switch */
@@ -53,10 +58,8 @@ public class Switch extends InputDevice {
return switchables;
}
- @PreRemove
- public void removeSwitchable() {
- for (Switchable s : getOutputs()) {
- s.setSwitchId(null);
- }
+ @Override
+ public boolean readTriggerState() {
+ return on;
}
}
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 5ba0702..c7abc7e 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
@@ -1,23 +1,23 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
+import java.util.HashSet;
+import java.util.Set;
import javax.persistence.*;
/** A device that can be turned either on or off */
@Entity
-@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Switchable extends OutputDevice {
public static final Connector SWITCH_SWITCHABLE_CONNECTOR =
- Connector.basic(Switch::getOutputs, Switchable::setSwitchId);
+ Connector.basic(Switch::getOutputs, Switchable::getSwitches);
- @ManyToOne
+ @ManyToMany(mappedBy = "switchables", cascade = CascadeType.DETACH)
@GsonExclude
- @JoinColumn(name = "switch_id", updatable = false, insertable = false)
- private Switch switchDevice;
-
- @Column(name = "switch_id")
- private Long switchId;
+ @SocketGsonExclude
+ private Set inputs = new HashSet<>();
protected Switchable(String kind) {
super(kind);
@@ -37,11 +37,20 @@ public abstract class Switchable extends OutputDevice {
*/
public abstract void setOn(boolean on);
- public Long getSwitchId() {
- return switchId;
+ public Set getSwitches() {
+ return inputs;
}
- public void setSwitchId(Long switchId) {
- this.switchId = switchId;
+ public void readStateAndSet(SwitchableState extends Switchable> state) {
+ setOn(state.isOn());
+ }
+
+ @Override
+ public State extends Switchable> cloneState() {
+ final SwitchableState newState = new SwitchableState<>();
+ newState.setDeviceId(getId());
+ newState.setDevice(this);
+ newState.setOn(isOn());
+ return newState;
}
}
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
new file mode 100644
index 0000000..67b3118
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableState.java
@@ -0,0 +1,25 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+/** Represents a state for a Switchable device */
+@Entity
+public class SwitchableState extends State {
+
+ @Column(name = "switchable_on")
+ private boolean on;
+
+ public boolean isOn() {
+ return on;
+ }
+
+ public void setOn(boolean on) {
+ this.on = on;
+ }
+
+ @Override
+ public void apply() {
+ getDevice().readStateAndSet(this);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableStateRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableStateRepository.java
new file mode 100644
index 0000000..933ac6c
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SwitchableStateRepository.java
@@ -0,0 +1,3 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+public interface SwitchableStateRepository extends StateRepository> {}
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
new file mode 100644
index 0000000..e8f0e86
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java
@@ -0,0 +1,129 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import com.google.gson.annotations.SerializedName;
+import java.math.BigDecimal;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Transient;
+import javax.validation.constraints.NotNull;
+
+/** A thermostat capable of controlling cooling and heating. */
+@Entity
+public class Thermostat extends Switchable implements BooleanTriggerable {
+
+ @Override
+ public boolean isOn() {
+ return mode != Mode.OFF;
+ }
+
+ @Override
+ public void setOn(boolean on) {
+ mode = on ? Mode.IDLE : Mode.OFF;
+ computeState();
+ }
+
+ /**
+ * Computes the new thermostat state, for when the thermostat is on;
+ *
+ * @return true if the state changed, false if not;
+ */
+ public boolean computeState() {
+ if (mode == Thermostat.Mode.OFF) {
+ return false;
+ }
+
+ BigDecimal measured = this.getMeasuredTemperature();
+ BigDecimal target = this.getTargetTemperature();
+ 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
+ public boolean readTriggerState() {
+ return mode != Mode.OFF;
+ }
+
+ public enum Mode {
+ @SerializedName("OFF")
+ OFF,
+ @SerializedName("IDLE")
+ IDLE,
+ @SerializedName("COOLING")
+ COOLING,
+ @SerializedName("HEATING")
+ HEATING
+ }
+
+ /** Temperature to be reached */
+ @Column @NotNull private BigDecimal targetTemperature;
+
+ /** The temperature detected by the embedded sensor */
+ @Column(nullable = false, precision = 4, scale = 1)
+ private BigDecimal internalSensorTemperature =
+ Sensor.TYPICAL_VALUES.get(Sensor.SensorType.TEMPERATURE);
+
+ /** State of this thermostat */
+ @Column @NotNull private Thermostat.Mode mode;
+
+ @Transient private BigDecimal measuredTemperature;
+
+ @Column private boolean useExternalSensors = false;
+
+ /** Creates a thermostat with a temperature sensor and its initial OFF state */
+ public Thermostat() {
+ super("thermostat");
+ this.mode = Mode.OFF;
+ }
+
+ public void setMode(Mode state) {
+ this.mode = state;
+ }
+
+ public Mode getMode() {
+ return this.mode;
+ }
+
+ public BigDecimal getTargetTemperature() {
+ return this.targetTemperature;
+ }
+
+ public BigDecimal getInternalSensorTemperature() {
+ return internalSensorTemperature;
+ }
+
+ public boolean isUseExternalSensors() {
+ return useExternalSensors;
+ }
+
+ public BigDecimal getMeasuredTemperature() {
+ return measuredTemperature;
+ }
+
+ public void setMeasuredTemperature(BigDecimal measuredTemperature) {
+ this.measuredTemperature = measuredTemperature;
+ }
+
+ public void setTargetTemperature(BigDecimal targetTemperature) {
+ this.targetTemperature = targetTemperature;
+ }
+
+ public void setInternalSensorTemperature(BigDecimal internalSensorTemperature) {
+ this.internalSensorTemperature = internalSensorTemperature;
+ }
+
+ public void setUseExternalSensors(boolean useExternalSensors) {
+ this.useExternalSensors = useExternalSensors;
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ThermostatRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ThermostatRepository.java
new file mode 100644
index 0000000..896e0ad
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ThermostatRepository.java
@@ -0,0 +1,30 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import javax.transaction.Transactional;
+import org.springframework.data.jpa.repository.Query;
+
+public interface ThermostatRepository extends DeviceRepository {
+
+ /**
+ * Finds all devices belonging to a user
+ *
+ * @param username the User's username
+ * @return all devices of that user
+ */
+ @Transactional
+ @Query("SELECT t FROM Thermostat t JOIN t.room r JOIN r.user u WHERE u.username = ?1")
+ List findAllByUsername(String username);
+
+ /**
+ * Computes the average temperature of all temperature sensors in the room
+ *
+ * @param thermostatRoomId room ID of the thermostat
+ * @return an optional big decimal, empty if none found
+ */
+ @Query(
+ "SELECT AVG(s.value) FROM Sensor s JOIN s.room r WHERE s.sensor = 'TEMPERATURE' AND r.id = ?1")
+ Optional getAverageTemperature(Long thermostatRoomId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Trigger.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Trigger.java
new file mode 100644
index 0000000..77010c6
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Trigger.java
@@ -0,0 +1,101 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
+import io.swagger.annotations.ApiModelProperty;
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+@Entity
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+public abstract class Trigger {
+
+ @Transient private String kind;
+
+ protected Trigger(String kind) {
+ this.kind = kind;
+ }
+
+ public String getKind() {
+ return kind;
+ }
+
+ public abstract boolean triggered();
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id", updatable = false, nullable = false, unique = true)
+ @ApiModelProperty(hidden = true)
+ private long id;
+
+ @ManyToOne(targetEntity = Device.class)
+ @JoinColumn(name = "device_id", updatable = false, insertable = false)
+ @GsonExclude
+ private D device;
+
+ /**
+ * The device this trigger belongs to, as a foreign key id. To use when updating and inserting
+ * from a REST call.
+ */
+ @Column(name = "device_id", nullable = false)
+ @NotNull
+ private Long deviceId;
+
+ @ManyToOne
+ @JoinColumn(name = "automation_id", updatable = false, insertable = false)
+ @GsonExclude
+ private Automation automation;
+
+ @Column(name = "automation_id", nullable = false)
+ @NotNull
+ private Long automationId;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public D getDevice() {
+ return device;
+ }
+
+ public void setDevice(D device) {
+ this.device = device;
+ }
+
+ public Long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(Long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public Automation getAutomation() {
+ return automation;
+ }
+
+ public void setAutomation(Automation automation) {
+ this.automation = automation;
+ }
+
+ public Long getAutomationId() {
+ return automationId;
+ }
+
+ public Trigger setAutomationId(Long automationId) {
+ this.automationId = automationId;
+ return this;
+ }
+
+ @PreRemove
+ public void removeDeviceAndScene() {
+ this.setDevice(null);
+ this.setDeviceId(null);
+
+ this.setAutomation(null);
+ this.setAutomationId(null);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/TriggerRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/TriggerRepository.java
new file mode 100644
index 0000000..8fea53a
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/TriggerRepository.java
@@ -0,0 +1,14 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+
+import java.util.List;
+import javax.transaction.Transactional;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+
+public interface TriggerRepository> extends CrudRepository {
+
+ List findAllByDeviceId(@Param("deviceId") long deviceId);
+
+ @Transactional
+ void deleteAllByAutomationId(Long automationId);
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
index 60aad17..b58e383 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java
@@ -2,7 +2,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import io.swagger.annotations.ApiModelProperty;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import javax.persistence.*;
/** A user of the Smarthut application */
@@ -35,6 +37,23 @@ public class User {
@Column(nullable = false, unique = true)
private String email;
+ /** Guests invited by this user */
+ @ManyToMany(mappedBy = "hosts", cascade = CascadeType.DETACH)
+ @GsonExclude
+ private Set guests = new HashSet<>();
+
+ @ManyToMany(cascade = CascadeType.DETACH)
+ @JoinTable(
+ name = "invited",
+ joinColumns = @JoinColumn(name = "guest_id"),
+ inverseJoinColumns = @JoinColumn(name = "host_id"))
+ @GsonExclude
+ private Set hosts = new HashSet<>();
+
+ /** Determines whether a guest can access security cameras */
+ @Column(nullable = false)
+ private boolean cameraEnabled;
+
@Column(nullable = false)
@GsonExclude
private Boolean isEnabled = false;
@@ -87,10 +106,37 @@ public class User {
isEnabled = enabled;
}
+ public Set getGuests() {
+ return guests;
+ }
+
+ public Set getHosts() {
+ return hosts;
+ }
+
+ public boolean isCameraEnabled() {
+ return cameraEnabled;
+ }
+
+ public void addGuest(User guest) {
+ this.guests.add(guest);
+ }
+
+ public void addHost(User host) {
+ this.hosts.add(host);
+ }
+
+ public void removeGuest(User guest) {
+ this.guests.remove(guest);
+ }
+
+ public void setCameraEnabled(boolean cameraEnabled) {
+ this.cameraEnabled = cameraEnabled;
+ }
+
@Override
public String toString() {
- return "User{"
- + "id="
+ return "User{id="
+ id
+ ", name='"
+ name
@@ -104,6 +150,8 @@ public class User {
+ ", email='"
+ email
+ '\''
+ + ", cameraEnabled="
+ + cameraEnabled
+ ", isEnabled="
+ isEnabled
+ '}';
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java
index 01fd897..bd93385 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java
@@ -6,5 +6,7 @@ import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository {
User findByUsername(String username);
+ Optional findById(Long userId);
+
User findByEmailIgnoreCase(String email);
}
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 932db5c..e701166 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
@@ -1,10 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.scheduled;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.MotionSensorController;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.controller.SensorController;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.MotionSensorService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
-import java.math.BigDecimal;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -26,25 +26,24 @@ public class UpdateTasks {
@Autowired private SmartPlugRepository smartPlugRepository;
- @Autowired private SensorController sensorController;
+ @Autowired private SensorService sensorService;
- @Autowired private MotionSensorController motionSensorController;
+ @Autowired private ThermostatService thermostatService;
+
+ @Autowired private MotionSensorService motionSensorService;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
- /** Generates fake sensor updates every two seconds with a +/- 1.25% error */
+ /** Generates fake sensor updates every two seconds with a +/- 2.5% error */
@Scheduled(fixedRate = 2000)
public void sensorFakeUpdate() {
- StreamSupport.stream(sensorRepository.findAll().spliterator(), true)
- .forEach(
- sensor ->
- sensorController.updateValueFromSensor(
- sensor,
- Sensor.TYPICAL_VALUES
- .get(sensor.getSensor())
- .multiply(
- new BigDecimal(
- 0.9875 + Math.random() / 40))));
+ sensorService.sensorFakeUpdate();
+ }
+
+ /** Generates fake sensor updates every two seconds with a +/- 2.5% error */
+ @Scheduled(fixedRate = 2000)
+ public void thermostatInteralSensorFakeUpdate() {
+ thermostatService.fakeUpdateAll();
}
/**
@@ -56,14 +55,18 @@ public class UpdateTasks {
StreamSupport.stream(motionSensorRepository.findAll().spliterator(), true)
.forEach(
sensor -> {
- motionSensorController.updateDetectionFromMotionSensor(sensor, true);
+ final User owner = motionSensorRepository.findUser(sensor.getId());
+ motionSensorService.updateDetectionFromMotionSensor(
+ sensor, true, owner.getUsername());
CompletableFuture.delayedExecutor(
(long) (Math.random() * 2000), TimeUnit.MILLISECONDS)
.execute(
() ->
- motionSensorController
+ motionSensorService
.updateDetectionFromMotionSensor(
- sensor, false));
+ sensor,
+ false,
+ owner.getUsername()));
});
}
@@ -72,6 +75,15 @@ public class UpdateTasks {
public void smartPlugConsumptionFakeUpdate() {
smartPlugRepository.updateTotalConsumption(SmartPlug.AVERAGE_CONSUMPTION_KW);
final Collection c = smartPlugRepository.findByOn(true);
- c.forEach(s -> sensorSocketEndpoint.broadcast(s, sensorRepository.findUser(s.getId())));
+ c.forEach(
+ s ->
+ sensorSocketEndpoint.queueDeviceUpdate(
+ s, sensorRepository.findUser(s.getId())));
+ }
+
+ /** Sends device updates through sensor socket in batch every one second */
+ @Scheduled(fixedDelay = 1000)
+ public void socketFlush() {
+ sensorSocketEndpoint.flushDeviceUpdates();
}
}
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
new file mode 100644
index 0000000..0bc2a6e
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java
@@ -0,0 +1,194 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
+
+import static ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils.toList;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DeviceService {
+
+ @Autowired private DeviceRepository deviceRepository;
+ @Autowired private AutomationRepository automationRepository;
+ @Autowired private SceneRepository sceneRepository;
+ @Autowired private SceneService sceneService;
+ @Autowired private TriggerRepository> triggerRepository;
+ @Autowired private RoomRepository roomRepository;
+ @Autowired private EagerUserRepository userRepository;
+ @Autowired private SensorSocketEndpoint endpoint;
+ @Autowired private ThermostatService thermostatService;
+
+ public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException {
+ roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new);
+ }
+
+ private void renameIfDuplicate(Device toCreate, String username) {
+ while (deviceRepository.findDuplicates(toCreate.getName(), username)
+ - (toCreate.getId() <= 0 ? 0 : 1)
+ > 0) {
+ toCreate.setName(toCreate.getName() + " (new)");
+ }
+ }
+
+ private void triggerTriggers(Device device) {
+
+ final long deviceId = device.getId();
+
+ List> triggers = triggerRepository.findAllByDeviceId(deviceId);
+
+ triggers.stream()
+ .filter(Trigger::triggered)
+ .map(Trigger::getAutomationId)
+ .map(t -> automationRepository.findById(t).orElseThrow(IllegalStateException::new))
+ .distinct()
+ .map(Automation::getScenes)
+ .flatMap(Collection::stream)
+ .distinct()
+ .sorted(Comparator.comparing(ScenePriority::getPriority))
+ .map(
+ t ->
+ sceneRepository
+ .findById(t.getSceneId())
+ .orElseThrow(IllegalStateException::new))
+ .forEach(sceneService::apply);
+ }
+
+ public List findAll(Long hostId, String username) throws NotFoundException {
+ return findAll(null, hostId, username);
+ }
+
+ public List findAll(Long roomId, Long hostId, String username)
+ throws NotFoundException {
+ try {
+ Iterable devices;
+ if (hostId == null) {
+ if (roomId != null) {
+ roomRepository
+ .findByIdAndUsername(roomId, username)
+ .orElseThrow(NotFoundException::new);
+ devices = deviceRepository.findByRoomId(roomId);
+ } else {
+ devices = deviceRepository.findAllByUsername(username);
+ }
+ } else {
+ final User guest = userRepository.findByUsername(username);
+ final User host =
+ userRepository.findById(hostId).orElseThrow(NotFoundException::new);
+
+ if (!guest.getHosts().contains(host)) {
+ throw new NotFoundException();
+ }
+
+ if (roomId != null) {
+ Room r = roomRepository.findById(roomId).orElseThrow(NotFoundException::new);
+ if (!r.getUserId().equals(hostId)) {
+ throw new NotFoundException();
+ }
+ devices = deviceRepository.findByRoomId(roomId);
+ } else {
+ devices = deviceRepository.findAllByUsername(host.getUsername());
+ }
+ }
+
+ for (Device d : devices) {
+ if (d instanceof Thermostat) {
+ thermostatService.populateMeasuredTemperature((Thermostat) d);
+ }
+ }
+
+ return toList(devices);
+ } catch (NotFoundException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ 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);
+ 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);
+
+ // 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)) {
+ continue;
+ }
+ // enqueue all device updates for all other guests
+ endpoint.queueDeviceUpdate(device, guest);
+ }
+
+ return device;
+ }
+
+ private void propagateUpdateAsOwner(Device device, String username) {
+ 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);
+ }
+ }
+
+ public List saveAllAsOwner(
+ Iterable devices, String username, boolean fromScene) {
+ devices.forEach(d -> renameIfDuplicate(d, username));
+ devices = deviceRepository.saveAll(devices);
+ devices.forEach((d) -> propagateUpdateAsOwner(d, username));
+
+ if (!fromScene) {
+ devices.forEach(this::triggerTriggers);
+ }
+
+ return toList(devices);
+ }
+
+ public T saveAsOwner(T device, String username, boolean fromScene) {
+ renameIfDuplicate(device, username);
+ device = deviceRepository.save(device);
+ propagateUpdateAsOwner(device, username);
+
+ if (!fromScene) {
+ triggerTriggers(device);
+ }
+
+ 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 =
+ deviceRepository
+ .findByIdAndUsername(id, username)
+ .orElseThrow(NotFoundException::new);
+ deviceRepository.delete(device);
+
+ propagateUpdateAsOwner(device, username);
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/Service/EmailSenderService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/EmailSenderService.java
similarity index 100%
rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/Service/EmailSenderService.java
rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/EmailSenderService.java
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/JWTUserDetailsService.java
similarity index 85%
rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java
rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/JWTUserDetailsService.java
index 06ee415..7dff142 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/JWTUserDetailsService.java
@@ -1,6 +1,9 @@
-package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import java.util.Set;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.*;
import org.springframework.security.core.userdetails.UserDetails;
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
new file mode 100644
index 0000000..01a7401
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/MotionSensorService.java
@@ -0,0 +1,33 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensor;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.MotionSensorRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MotionSensorService {
+
+ @Autowired private SensorSocketEndpoint sensorSocketEndpoint;
+ @Autowired private DeviceService deviceService;
+ @Autowired private MotionSensorRepository motionSensorRepository;
+
+ /**
+ * Updates detection status of given motion sensor and propagates update throgh socket
+ *
+ * @param sensor the motion sensor to update
+ * @param detected the new detection status
+ * @return the updated motion sensor
+ */
+ public MotionSensor updateDetectionFromMotionSensor(
+ MotionSensor sensor, boolean detected, String username) {
+ sensor.setDetected(detected);
+ final MotionSensor toReturn = deviceService.saveAsOwner(sensor, username);
+
+ sensorSocketEndpoint.queueDeviceUpdate(
+ sensor, motionSensorRepository.findUser(sensor.getId()));
+
+ 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
new file mode 100644
index 0000000..826d896
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java
@@ -0,0 +1,28 @@
+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 java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SceneService {
+
+ @Autowired private DeviceRepository deviceRepository;
+
+ public List apply(Scene newScene) {
+ final List updated = new ArrayList<>();
+
+ for (final State> s : newScene.getStates()) {
+ s.apply();
+ updated.add(s.getDevice());
+ }
+ deviceRepository.saveAll(updated);
+
+ return updated;
+ }
+}
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
new file mode 100644
index 0000000..0e4fbbd
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java
@@ -0,0 +1,49 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SensorRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import java.math.BigDecimal;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SensorService {
+
+ @Autowired private SensorRepository sensorRepository;
+
+ @Autowired private DeviceService deviceService;
+
+ @Autowired private ThermostatService thermostatService;
+
+ @Autowired private SensorSocketEndpoint endpoint;
+
+ private void randomJitter(Sensor sensor) {
+ updateValueFromSensor(
+ sensor,
+ Sensor.TYPICAL_VALUES
+ .get(sensor.getSensor())
+ .multiply(BigDecimal.valueOf(0.975 + Math.random() / 20)));
+ }
+
+ public void sensorFakeUpdate() {
+ sensorRepository.findAll().forEach(this::randomJitter);
+ thermostatService.updateStates();
+ }
+
+ /**
+ * Updates the sensor with new measurement and propagates update through websocket
+ *
+ * @param sensor the sensor to update
+ * @param value the new measurement
+ * @return the updated sensor
+ */
+ public Sensor updateValueFromSensor(Sensor sensor, BigDecimal value) {
+ sensor.setValue(value);
+ sensor =
+ deviceService.saveAsOwner(
+ sensor, sensorRepository.findUser(sensor.getId()).getUsername());
+ endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId()));
+ 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
new file mode 100644
index 0000000..e214e39
--- /dev/null
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java
@@ -0,0 +1,94 @@
+package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
+
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Sensor;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Thermostat;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ThermostatRepository;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ThermostatService {
+
+ @Autowired private SensorSocketEndpoint endpoint;
+
+ @Autowired private DeviceService deviceService;
+
+ @Autowired private ThermostatRepository thermostatRepository;
+
+ private void randomJitter(Thermostat thermostat) {
+ updateValueForThermostat(
+ thermostat,
+ Sensor.TYPICAL_VALUES
+ .get(Sensor.SensorType.TEMPERATURE)
+ .multiply(BigDecimal.valueOf(0.975 + Math.random() / 20)));
+ }
+
+ private void updateValueForThermostat(Thermostat thermostat, BigDecimal value) {
+ thermostat.setInternalSensorTemperature(value);
+ deviceService.saveAsOwner(
+ thermostat, thermostatRepository.findUser(thermostat.getId()).getUsername());
+ }
+
+ public void fakeUpdateAll() {
+ thermostatRepository.findAll().forEach(this::randomJitter);
+ updateStates();
+ }
+
+ public List findAll(String username) {
+ Iterable all = thermostatRepository.findAllByUsername(username);
+ all.forEach(this::populateMeasuredTemperature);
+ return Utils.toList(all);
+ }
+
+ public boolean computeState(Thermostat t) {
+ populateMeasuredTemperature(t);
+ return t.computeState();
+ }
+
+ private void updateState(Thermostat t) {
+ boolean shouldUpdate = this.computeState(t);
+
+ if (shouldUpdate) {
+ deviceService.saveAsOwner(t, thermostatRepository.findUser(t.getId()).getUsername());
+ endpoint.queueDeviceUpdate(t, thermostatRepository.findUser(t.getId()));
+ }
+ }
+
+ public void updateStates() {
+ Iterable ts = thermostatRepository.findAll();
+ ts.forEach(this::updateState);
+ }
+
+ public Optional findById(Long thermostat, String username) {
+ Optional t = thermostatRepository.findByIdAndUsername(thermostat, username);
+
+ if (t.isPresent()) {
+ Thermostat u = t.get();
+ populateMeasuredTemperature(u);
+ t = Optional.of(u);
+ }
+
+ return t;
+ }
+
+ private BigDecimal measureTemperature(final Thermostat thermostat) {
+ Optional average;
+
+ if (thermostat.isUseExternalSensors()) {
+ average = thermostatRepository.getAverageTemperature(thermostat.getRoomId());
+ } else {
+ return thermostat.getInternalSensorTemperature();
+ }
+
+ return average.orElse(null);
+ }
+
+ public void populateMeasuredTemperature(Thermostat thermostat) {
+ thermostat.setMeasuredTemperature(measureTemperature(thermostat));
+ }
+}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
deleted file mode 100644
index 89415aa..0000000
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
-
-import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import io.jsonwebtoken.ExpiredJwtException;
-import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
-import javax.websocket.MessageHandler;
-import javax.websocket.Session;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-/** Generates MessageHandlers for unauthenticated socket sessions */
-@Component
-public class AuthenticationMessageListener {
-
- private Gson gson = GsonConfig.gson();
-
- private JWTTokenUtils jwtTokenUtils;
-
- private UserRepository userRepository;
-
- @Autowired
- public AuthenticationMessageListener(
- JWTTokenUtils jwtTokenUtils, UserRepository userRepository) {
- this.jwtTokenUtils = jwtTokenUtils;
- this.userRepository = userRepository;
- }
-
- /**
- * Generates a new message handler to handle socket authentication
- *
- * @param session the session to which authentication must be checked
- * @param authorizedSetter function to call once user is authenticated
- * @return a new message handler to handle socket authentication
- */
- MessageHandler.Whole newHandler(
- final Session session, BiConsumer authorizedSetter) {
- return new MessageHandler.Whole<>() {
- @Override
- public void onMessage(final String message) {
- if (message == null) {
- acknowledge(false);
- return;
- }
-
- String token;
- String username;
-
- try {
- token = gson.fromJson(message, JsonObject.class).get("token").getAsString();
- username = jwtTokenUtils.getUsernameFromToken(token);
- } catch (ExpiredJwtException e) {
- System.err.println(e.getMessage());
- acknowledge(false);
- return;
- } catch (Throwable ignored) {
- System.out.println("Token format not valid");
- acknowledge(false);
- return;
- }
-
- final User user = userRepository.findByUsername(username);
- if (user == null || jwtTokenUtils.isTokenExpired(token)) {
- System.out.println("Token not valid");
- acknowledge(false);
- return;
- }
-
- // Here user is authenticated
- session.removeMessageHandler(this);
-
- // Add user-session pair in authorized list
- authorizedSetter.accept(user, session);
-
- // update client to acknowledge authentication
- acknowledge(true);
- }
-
- private void acknowledge(boolean success) {
- try {
- session.getBasicRemote()
- .sendText(gson.toJson(Map.of("authenticated", success)));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- };
- }
-}
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 600bdf4..b68cb25 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
@@ -1,8 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
-
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
+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 com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
@@ -17,52 +19,60 @@ import org.springframework.stereotype.Component;
@Component
public class SensorSocketEndpoint extends Endpoint {
- private Gson gson = GsonConfig.gson();
+ private Gson gson = GsonConfig.socketGson();
- private AuthenticationMessageListener authenticationMessageListener;
+ private UserRepository userRepository;
- private Set unauthorizedClients = Collections.synchronizedSet(new HashSet<>());
+ private JWTTokenUtils jwtTokenUtils;
private Multimap authorizedClients =
Multimaps.synchronizedMultimap(HashMultimap.create());
+ private final Map> messages = new HashMap<>();
+
@Autowired
- public SensorSocketEndpoint(AuthenticationMessageListener authenticationMessageListener) {
- this.authenticationMessageListener = authenticationMessageListener;
+ public SensorSocketEndpoint(UserRepository userRepository, JWTTokenUtils jwtTokenUtils) {
+ this.jwtTokenUtils = jwtTokenUtils;
+ this.userRepository = userRepository;
}
/**
- * Returns a synchronized set of socket sessions not yet authorized with a token
+ * Queues a single device update for a certain user to be sent
*
- * @return a synchronized set of socket sessions not yet authorized with a token
+ * @param device the device update to be sent
+ * @param u the user the device belongs
*/
- public Set getUnauthorizedClients() {
- return unauthorizedClients;
+ public void queueDeviceUpdate(Device device, User u) {
+ synchronized (messages) {
+ messages.putIfAbsent(u, new HashMap<>());
+ messages.get(u).put(device.getId(), 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()) {
+ broadcast(batchForUser.getKey(), batchForUser.getValue().values());
+ batchForUser.getValue().clear();
+ }
+ }
}
/**
- * Returns a synchronized User to Session multimap with authorized sessions
+ * Given a collection of messages and a user, broadcasts that message in json to all associated
+ * clients
*
- * @return a synchronized User to Session multimap with authorized sessions
- */
- public Multimap getAuthorizedClients() {
- return authorizedClients;
- }
-
- /**
- * Given a message and a user, broadcasts that message in json to all associated clients and
- * returns the number of successful transfers
- *
- * @param message the message to send
+ * @param messages the message batch to send
* @param u the user to which to send the message
- * @return number of successful transfer
*/
- public void broadcast(Object message, User u) {
+ 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(message));
+ s.getBasicRemote().sendText(gson.toJson(messages));
} else {
authorizedClients.remove(u, s);
}
@@ -80,13 +90,33 @@ public class SensorSocketEndpoint extends Endpoint {
*/
@Override
public void onOpen(Session session, EndpointConfig config) {
- unauthorizedClients.add(session);
- session.addMessageHandler(
- authenticationMessageListener.newHandler(
- session,
- (u, s) -> {
- unauthorizedClients.remove(s);
- authorizedClients.put(u, s);
- }));
+ final List tokenQuery = session.getRequestParameterMap().get("token");
+ User u;
+ if (!tokenQuery.isEmpty() && (u = checkToken(tokenQuery.get(0))) != null) {
+ authorizedClients.put(u, session);
+ } else {
+ try {
+ session.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ private User checkToken(String protocolString) {
+ String username;
+
+ try {
+ username = jwtTokenUtils.getUsernameFromToken(protocolString);
+ } catch (Throwable ignored) {
+ System.out.println("Token format not valid");
+ return null;
+ }
+
+ final User user = userRepository.findByUsername(username);
+ if (user != null && !jwtTokenUtils.isTokenExpired(protocolString)) {
+ return user;
+ } else {
+ return null;
+ }
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
index 99d363b..050afa5 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/utils/Utils.java
@@ -1,7 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.utils;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
+import java.security.Principal;
import java.util.List;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -9,24 +12,19 @@ import java.util.stream.StreamSupport;
public final class Utils {
private Utils() {}
- @FunctionalInterface
- public interface ConsumerWithException {
- void apply(T input) throws Throwable;
+ public static U returnIfGuest(
+ UserRepository userRepository, U toReturn, Long hostId, Principal principal)
+ throws NotFoundException {
+ User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
+ User guest = userRepository.findByUsername(principal.getName());
+ if (!host.getGuests().contains(guest)) {
+ throw new NotFoundException();
+ } else {
+ return toReturn;
+ }
}
- public static List toList(Iterable iterable) {
+ public static List toList(Iterable extends T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
}
-
- public static