diff --git a/build.gradle b/build.gradle index 3116923..c55c8c1 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() + } dependencies { compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' @@ -39,6 +40,13 @@ dependencies { // Fixes https://stackoverflow.com/a/60455550 testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.11' } + +gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" + } +} + test { useJUnitPlatform() } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a2bf131..b09929f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sun Apr 12 12:33:03 CEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/socket_test.html b/socket_test.html index 687388b..776248d 100644 --- a/socket_test.html +++ b/socket_test.html @@ -1,40 +1,45 @@ - + - - -
-

Waiting for authentication...

-
- - + + + + diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java index 69c4fc9..1204a60 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java @@ -1,5 +1,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.config; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.AutomationFastUpdateRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableState; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.State; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SwitchableState; import com.google.gson.*; import java.lang.reflect.Type; import org.springframework.context.annotation.Bean; @@ -20,12 +24,39 @@ public class GsonConfig { return converter; } - public static Gson gson() { + private static GsonBuilder configureBuilder() { final GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()); + RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = + RuntimeTypeAdapterFactory.of(State.class, "kind") + .registerSubtype(SwitchableState.class, "switchableState") + .registerSubtype(DimmableState.class, "dimmableState"); + RuntimeTypeAdapterFactory + 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 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 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 addLight( - @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) - throws NotFoundException { - return addOutput(inputId, lightId); - } - - @DeleteMapping("/{id}/lights") - public Set 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 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 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 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 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 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 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 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 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 addLight( - @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId) - throws NotFoundException { - return addOutput(inputId, lightId); - } - - @DeleteMapping("/{id}/lights") - public Set 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 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 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 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 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 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 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 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 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 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 state) { + setIntensity(state.getIntensity()); + } + + @Override + public State 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 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 state) { + setOn(state.isOn()); + } + + @Override + public State 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 iterable) { return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); } - - public static Predicate didThrow(ConsumerWithException consumer) { - return (t) -> { - try { - consumer.apply(t); - return true; - } catch (Throwable e) { - System.err.println(e.getMessage()); - return false; - } - }; - } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 3c77362..8720442 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -27,7 +27,7 @@ spring.mail.properties.mail.smtp.writetimeout=5000 email.registrationSubject=Complete your SmartHut.sm registration email.registration=To confirm your registration, please click here: email.registrationPath=http://localhost:8080/register/confirm-account?token= -email.registrationRedirect=http://localhost:3000 +email.registrationRedirect=http://localhost:3000/login email.resetpasswordSubject=SmartHut.sm password reset email.resetpassword=To reset your password, please click here: diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 5888106..1727673 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -32,7 +32,7 @@ spring.mail.properties.mail.smtp.writetimeout=5000 email.registrationSubject=Complete your SmartHut.sm registration email.registration=To confirm your registration, please click here: email.registrationPath=${BACKEND_URL}/register/confirm-account?token= -email.registrationRedirect=${FRONTEND_URL} +email.registrationRedirect=${FRONTEND_URL}/login # Password reset email properties diff --git a/src/main/resources/static/security_camera_videos/security_camera_1.mp4 b/src/main/resources/static/security_camera_videos/security_camera_1.mp4 new file mode 100644 index 0000000..f8d6c2d Binary files /dev/null and b/src/main/resources/static/security_camera_videos/security_camera_1.mp4 differ diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/ButtonDimmerTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/ButtonDimmerTests.java index 22b3e0c..45ad962 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/ButtonDimmerTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/ButtonDimmerTests.java @@ -3,6 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; import static org.junit.jupiter.api.Assertions.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ButtonDimmer; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Dimmable; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -30,7 +31,7 @@ public class ButtonDimmerTests { dl = new DimmableLight(); dl.setIntensity(10); ; - buttonDimmer.addDimmableLight(dl); + buttonDimmer.addDimmable(dl); } } @@ -38,7 +39,7 @@ public class ButtonDimmerTests { @DisplayName(" increase the intensity ") public void increase() { buttonDimmer.increaseIntensity(); - for (DimmableLight dl : buttonDimmer.getOutputs()) { + for (Dimmable dl : buttonDimmer.getOutputs()) { assertTrue(dl.getIntensity() > 10); } } @@ -47,7 +48,7 @@ public class ButtonDimmerTests { @DisplayName(" decrease the intensity ") public void decrease() { buttonDimmer.decreaseIntensity(); - for (DimmableLight dl : buttonDimmer.getOutputs()) { + for (Dimmable dl : buttonDimmer.getOutputs()) { assertTrue(dl.getIntensity() < 10); } } diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/CurtainsTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/CurtainsTests.java new file mode 100644 index 0000000..06bd3e2 --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/CurtainsTests.java @@ -0,0 +1,45 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Curtains; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("Curtains tests") +public class CurtainsTests { + private Curtains curtains; + + @BeforeEach + public void createCurtains() { + this.curtains = new Curtains(); + } + + @Test + @DisplayName("State when just created") + public void initialState() { + assertEquals(0, this.curtains.getIntensity()); + } + + @Test + @DisplayName("Check wether setting the opening works") + public void normalSet() { + this.curtains.setIntensity(42); + assertEquals(42, this.curtains.getIntensity()); + } + + @Test + @DisplayName("Set setting a negative number") + public void setNeg() { + this.curtains.setIntensity(-1); + assertEquals(0, this.curtains.getIntensity()); + } + + @Test + @DisplayName("Setting state to a number greater than 100") + public void setLarge() { + this.curtains.setIntensity(32768); + assertEquals(100, this.curtains.getIntensity()); + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java index 155242d..8eb7f74 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/KnobDimmerTests.java @@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; import static org.junit.jupiter.api.Assertions.*; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Dimmable; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DimmableLight; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.KnobDimmer; import org.junit.jupiter.api.BeforeEach; @@ -30,7 +31,7 @@ public class KnobDimmerTests { dl = new DimmableLight(); dl.setIntensity(10); ; - knobDimmer.addDimmableLight(dl); + knobDimmer.addDimmable(dl); } } @@ -38,7 +39,7 @@ public class KnobDimmerTests { @DisplayName(" set the intensity ") public void increase() { knobDimmer.setLightIntensity(30); - for (DimmableLight dl : knobDimmer.getOutputs()) { + for (Dimmable dl : knobDimmer.getOutputs()) { assertEquals(30, dl.getIntensity()); } }