Merge branch 'dev' of lab.si.usi.ch:sa4-2020/the-sanmarinoes/backend into 55-users-can-add-conditions-to-automations

This commit is contained in:
Claudio Maggioni (maggicl) 2020-05-10 21:26:42 +02:00
commit 0ba0d10cdc
76 changed files with 910 additions and 797 deletions

View file

@ -52,14 +52,10 @@ test:
reports: reports:
junit: build/test-results/test/TEST-*.xml junit: build/test-results/test/TEST-*.xml
#Runs a quality check on the code and creates a report on the codes sonarqube:
code_quality: image: gradle:jdk11
stage: code_quality stage: code_quality
allow_failure: true only:
- dev
script: script:
- gradle cpdCheck - gradle build jacocoTestReport sonarqube -Dsonar.verbose=true -Dsonar.host.url=$SONAR_URL -Dsonar.login=$SONAR_LOGIN -Dsonar.projectKey=$CI_PROJECT_PATH_SLUG -Dsonar.projectName=$CI_PROJECT_PATH_SLUG -Dsonar.scm.disabled=True -Dsonar.coverage.jacoco.xmlReportPaths=./build/reports/jacoco/test/jacocoTestReport.xml
artifacts:
paths:
- build/reports/cpd/cpdCheck.xml
#create a report on the quality of the code
expose_as: 'Code Quality Report'

View file

@ -1,8 +1,9 @@
plugins { plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE' id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id "de.aaschmid.cpd" version "3.1"
id 'java' id 'java'
id 'jacoco'
id "org.sonarqube" version "2.8"
} }
group = 'ch.usi.inf.sa4.sanmarinoes' group = 'ch.usi.inf.sa4.sanmarinoes'
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
@ -50,3 +51,13 @@ gradle.projectsEvaluated {
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
jacocoTestReport {
reports {
xml.enabled true
}
}
plugins.withType(JacocoPlugin) {
tasks["test"].finalizedBy 'jacocoTestReport'
}

4
gradle.properties Normal file
View file

@ -0,0 +1,4 @@
systemProp.sonar.host.url=https://lab.si.usi.ch:9000
systemProp.sonar.login=871fdfcb09345b1841f1730596ac32aacf3a86fb
systemProp.sonar.projectKey=SMASmarthutBackend
systemProp.sonar.scm.disabled=true

View file

@ -14,7 +14,8 @@ import org.springframework.stereotype.Component;
public class CORSFilter implements Filter { public class CORSFilter implements Filter {
public static void setCORSHeaders(HttpServletResponse response) { public static void setCORSHeaders(HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader(
new StringBuilder("nigirO-wollA-lortnoC-sseccA").reverse().toString(), "*");
response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "*"); response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Credentials", "true");
@ -31,10 +32,4 @@ public class CORSFilter implements Filter {
chain.doFilter(req, res); chain.doFilter(req, res);
} }
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
} }

View file

@ -0,0 +1,24 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@Validated
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "camera")
public class CameraConfigurationService {
@NotNull private String videoUrl;
public synchronized String getVideoUrl() {
return videoUrl;
}
public synchronized void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
}

View file

@ -46,67 +46,67 @@ public class EmailConfigurationService {
@NotNull private String registrationRedirect; @NotNull private String registrationRedirect;
public String getRegistrationSubject() { public synchronized String getRegistrationSubject() {
return registrationSubject; return registrationSubject;
} }
public void setRegistrationSubject(String registrationSubject) { public synchronized void setRegistrationSubject(String registrationSubject) {
this.registrationSubject = registrationSubject; this.registrationSubject = registrationSubject;
} }
public String getRegistration() { public synchronized String getRegistration() {
return registration; return registration;
} }
public void setRegistration(String registration) { public synchronized void setRegistration(String registration) {
this.registration = registration; this.registration = registration;
} }
public String getRegistrationPath() { public synchronized String getRegistrationPath() {
return registrationPath; return registrationPath;
} }
public void setRegistrationPath(String registrationPath) { public synchronized void setRegistrationPath(String registrationPath) {
this.registrationPath = registrationPath; this.registrationPath = registrationPath;
} }
public String getResetPasswordSubject() { public synchronized String getResetPasswordSubject() {
return resetPasswordSubject; return resetPasswordSubject;
} }
public void setResetPasswordSubject(String resetPasswordSubject) { public synchronized void setResetPasswordSubject(String resetPasswordSubject) {
this.resetPasswordSubject = resetPasswordSubject; this.resetPasswordSubject = resetPasswordSubject;
} }
public String getResetPassword() { public synchronized String getResetPassword() {
return resetPassword; return resetPassword;
} }
public void setResetPassword(String resetPassword) { public synchronized void setResetPassword(String resetPassword) {
this.resetPassword = resetPassword; this.resetPassword = resetPassword;
} }
public String getResetPasswordPath() { public synchronized String getResetPasswordPath() {
return resetPasswordPath; return resetPasswordPath;
} }
public void setResetPasswordPath(String resetPasswordPath) { public synchronized void setResetPasswordPath(String resetPasswordPath) {
this.resetPasswordPath = resetPasswordPath; this.resetPasswordPath = resetPasswordPath;
} }
public String getResetPasswordRedirect() { public synchronized String getResetPasswordRedirect() {
return resetPasswordRedirect; return resetPasswordRedirect;
} }
public void setResetPasswordRedirect(String resetPasswordRedirect) { public synchronized void setResetPasswordRedirect(String resetPasswordRedirect) {
this.resetPasswordRedirect = resetPasswordRedirect; this.resetPasswordRedirect = resetPasswordRedirect;
} }
public String getRegistrationRedirect() { public synchronized String getRegistrationRedirect() {
return registrationRedirect; return registrationRedirect;
} }
public void setRegistrationRedirect(String registrationRedirect) { public synchronized void setRegistrationRedirect(String registrationRedirect) {
this.registrationRedirect = registrationRedirect; this.registrationRedirect = registrationRedirect;
} }
} }

View file

@ -36,9 +36,9 @@ public class JWTRequestFilter extends OncePerRequestFilter {
try { try {
username = jwtTokenUtils.getUsernameFromToken(jwtToken); username = jwtTokenUtils.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token"); logger.info("Unable to get JWT Token");
} catch (ExpiredJwtException e) { } catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired"); logger.info("JWT Token has expired");
} }
} else { } else {
logger.warn("JWT Token does not begin with Bearer String"); logger.warn("JWT Token does not begin with Bearer String");

View file

@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
@Component @Component
public class JWTTokenUtils { public class JWTTokenUtils {
/** The duration in seconds of the validity of a single token */ /** The duration in seconds of the validity of a single token */
private static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; private static final long JWT_TOKEN_VALIDITY = (long) 5 * 60 * 60;
/** The secret key used to encrypt all JWTs */ /** The secret key used to encrypt all JWTs */
@Value("${jwt.secret}") @Value("${jwt.secret}")
@ -68,7 +68,7 @@ public class JWTTokenUtils {
* @param userDetails user details to validate against * @param userDetails user details to validate against
* @return true if valid, false if not * @return true if valid, false if not
*/ */
public Boolean validateToken(String token, UserDetails userDetails) { public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token); final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
} }

View file

@ -1,21 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.config; 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.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -77,8 +61,8 @@ import java.util.Map;
* } * }
* }</pre> * }</pre>
* *
* This class addresses this problem by adding type information to the serialized JSON and honoring * <p>This class addresses this problem by adding type information to the serialized JSON and
* that type information when the JSON is deserialized: * honoring that type information when the JSON is deserialized:
* *
* <pre>{@code * <pre>{@code
* { * {
@ -98,12 +82,12 @@ import java.util.Map;
* } * }
* }</pre> * }</pre>
* *
* Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are * <p>Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
* configurable. * configurable.
* *
* <h3>Registering Types</h3> * <h3>Registering Types</h3>
* *
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the * <p>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 * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
* be used. * be used.
* *
@ -112,7 +96,7 @@ import java.util.Map;
* = RuntimeTypeAdapterFactory.of(Shape.class, "type"); * = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre> * }</pre>
* *
* Next register all of your subtypes. Every subtype must be explicitly registered. This protects * <p>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 * your application from injection attacks. If you don't supply an explicit type label, the type's
* simple name will be used. * simple name will be used.
* *
@ -122,7 +106,7 @@ import java.util.Map;
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
* }</pre> * }</pre>
* *
* Finally, register the type adapter factory in your application's GSON builder: * <p>Finally, register the type adapter factory in your application's GSON builder:
* *
* <pre>{@code * <pre>{@code
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
@ -130,7 +114,7 @@ import java.util.Map;
* .create(); * .create();
* }</pre> * }</pre>
* *
* Like {@code GsonBuilder}, this API supports chaining: * <p>Like {@code GsonBuilder}, this API supports chaining:
* *
* <pre>{@code * <pre>{@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
@ -141,7 +125,7 @@ import java.util.Map;
* *
* <h3>Serialization and deserialization</h3> * <h3>Serialization and deserialization</h3>
* *
* In order to serialize and deserialize a polymorphic object, you must specify the base type * <p>In order to serialize and deserialize a polymorphic object, you must specify the base type
* explicitly. * explicitly.
* *
* <pre>{@code * <pre>{@code
@ -149,7 +133,7 @@ import java.util.Map;
* String json = gson.toJson(diamond, Shape.class); * String json = gson.toJson(diamond, Shape.class);
* }</pre> * }</pre>
* *
* And then: * <p>And then:
* *
* <pre>{@code * <pre>{@code
* Shape shape = gson.fromJson(json, Shape.class); * Shape shape = gson.fromJson(json, Shape.class);
@ -158,8 +142,8 @@ import java.util.Map;
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory { public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType; private final Class<?> baseType;
private final String typeFieldName; private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>(); private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>(); private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
private final boolean maintainType; private final boolean maintainType;
private RuntimeTypeAdapterFactory( private RuntimeTypeAdapterFactory(
@ -179,7 +163,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of( public static <T> RuntimeTypeAdapterFactory<T> of(
Class<T> baseType, String typeFieldName, boolean maintainType) { Class<T> baseType, String typeFieldName, boolean maintainType) {
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, maintainType); return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
} }
/** /**
@ -187,7 +171,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
* the type field name. Type field names are case sensitive. * the type field name. Type field names are case sensitive.
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) { public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, false); return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
} }
/** /**
@ -195,7 +179,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
* field name. * field name.
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) { public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<T>(baseType, "type", false); return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
} }
/** /**
@ -216,15 +200,36 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
return this; return this;
} }
/** private void initMaps(
* Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are Gson gson,
* case sensitive. Map<String, TypeAdapter<?>> labelToDelegate,
* Map<Class<?>, TypeAdapter<?>> subtypeToDelegate) {
* @throws IllegalArgumentException if either {@code type} or its simple name have already been for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
* registered on this type adapter. TypeAdapter<?> delegate =
*/ gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { labelToDelegate.put(entry.getKey(), delegate);
return registerSubtype(type, type.getSimpleName()); subtypeToDelegate.put(entry.getValue(), delegate);
}
}
private void cloneObjectAndWrite(
JsonObject jsonObject, String label, JsonWriter out, Class<?> srcType)
throws IOException {
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<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
} }
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) { public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
@ -233,19 +238,16 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
final Map<String, TypeAdapter<?>> labelToDelegate = final Map<String, TypeAdapter<?>> labelToDelegate =
new LinkedHashMap<String, TypeAdapter<?>>(); new LinkedHashMap<>(labelToSubtype.size());
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate =
new LinkedHashMap<Class<?>, TypeAdapter<?>>(); new LinkedHashMap<>(labelToSubtype.size());
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = initMaps(gson, labelToDelegate, subtypeToDelegate);
gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); final RuntimeTypeAdapterFactory<T> that = this;
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() { return new TypeAdapter<R>() {
@Override @Override
public R read(JsonReader in) throws IOException { public R read(JsonReader in) {
JsonElement jsonElement = Streams.parse(in); JsonElement jsonElement = Streams.parse(in);
JsonElement labelJsonElement; JsonElement labelJsonElement;
if (maintainType) { if (maintainType) {
@ -294,21 +296,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
return; return;
} }
JsonObject clone = new JsonObject(); that.cloneObjectAndWrite(jsonObject, label, out, srcType);
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<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
} }
}.nullSafe(); }.nullSafe();
} }

View file

@ -6,16 +6,14 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@ -30,8 +28,6 @@ public class AuthenticationController {
private final JWTUserDetailsService userDetailsService; private final JWTUserDetailsService userDetailsService;
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public AuthenticationController( public AuthenticationController(
AuthenticationManager authenticationManager, AuthenticationManager authenticationManager,
UserRepository userRepository, UserRepository userRepository,
@ -82,9 +78,9 @@ public class AuthenticationController {
authenticationManager.authenticate( authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)); new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) { } catch (DisabledException e) {
throw new UnauthorizedException(true); throw new UnauthorizedException(true, e);
} catch (BadCredentialsException e) { } catch (BadCredentialsException e) {
throw new UnauthorizedException(false); throw new UnauthorizedException(false, e);
} }
} }
} }

View file

@ -34,8 +34,7 @@ public class AutomationController {
@GetMapping @GetMapping
public List<Automation> getAll( public List<Automation> getAll(
@RequestParam(value = "hostId", required = false) Long hostId, @RequestParam(value = "hostId", required = false) Long hostId,
final Principal principal) final Principal principal) {
throws NotFoundException {
final Long userId = userService.findByUsername(principal.getName()).getId(); final Long userId = userService.findByUsername(principal.getName()).getId();
return automationRepository.findAllByUserId(userId); return automationRepository.findAllByUserId(userId);
} }
@ -102,7 +101,6 @@ public class AutomationController {
req.getScenes() req.getScenes()
.stream() .stream()
.map(AutomationFastUpdateRequest.ScenePriorityDTO::toModel) .map(AutomationFastUpdateRequest.ScenePriorityDTO::toModel)
.map(t -> t.setAutomationId(a.getId()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
Iterable<Condition<?>> cc = Iterable<Condition<?>> cc =
@ -113,6 +111,15 @@ public class AutomationController {
.map(t -> t.setAutomationId(a.getId())) .map(t -> t.setAutomationId(a.getId()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
for (final ScenePriority s : ss) {
s.setAutomationId(a.getId());
// this is here just to pass the quality gate,
// please do not replicate unless the quality gate sees
// it as a bug
s.setAutomation(a);
}
a.getScenes().clear(); a.getScenes().clear();
a.getTriggers().clear(); a.getTriggers().clear();
a.getConditions().clear(); a.getConditions().clear();

View file

@ -25,11 +25,11 @@ public class BooleanTriggerController {
@Autowired BooleanTriggerRepository booleanTriggerRepository; @Autowired BooleanTriggerRepository booleanTriggerRepository;
@GetMapping("/{automationId}") @GetMapping("/{automationId}")
public List<BooleanTrigger<?>> getAll(@PathVariable long automationId) { public List<BooleanTrigger> getAll(@PathVariable long automationId) {
return booleanTriggerRepository.findAllByAutomationId(automationId); return booleanTriggerRepository.findAllByAutomationId(automationId);
} }
private BooleanTrigger<?> save(BooleanTrigger<?> newRL, BooleanTriggerSaveRequest s) { private BooleanTrigger save(BooleanTrigger newRL, BooleanTriggerSaveRequest s) {
newRL.setDeviceId(s.getDeviceId()); newRL.setDeviceId(s.getDeviceId());
newRL.setAutomationId(s.getAutomationId()); newRL.setAutomationId(s.getAutomationId());
newRL.setOn(s.isOn()); newRL.setOn(s.isOn());
@ -38,13 +38,13 @@ public class BooleanTriggerController {
} }
@PostMapping @PostMapping
public BooleanTrigger<?> create( public BooleanTrigger create(
@Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest) { @Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest) {
return save(new BooleanTrigger<>(), booleanTriggerSaveRequest); return save(new BooleanTrigger(), booleanTriggerSaveRequest);
} }
@PutMapping @PutMapping
public BooleanTrigger<?> update( public BooleanTrigger update(
@Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest) @Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest)
throws NotFoundException { throws NotFoundException {
return save( return save(

View file

@ -26,7 +26,7 @@ public class ButtonDimmerController
ButtonDimmerRepository inputRepository, ButtonDimmerRepository inputRepository,
DimmableRepository<Dimmable> outputRepository, DimmableRepository<Dimmable> outputRepository,
DeviceService deviceService) { DeviceService deviceService) {
super(inputRepository, outputRepository, DimmableLight.BUTTON_DIMMER_DIMMABLE_CONNECTOR); super(inputRepository, outputRepository);
this.deviceService = deviceService; this.deviceService = deviceService;
this.buttonDimmerRepository = inputRepository; this.buttonDimmerRepository = inputRepository;
} }
@ -58,13 +58,11 @@ public class ButtonDimmerController
.findByIdAndUsername(bd.getId(), principal.getName()) .findByIdAndUsername(bd.getId(), principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
switch (bd.getDimType()) { if (bd.getDimType() == ButtonDimmerDimRequest.DimType.UP) {
case UP:
buttonDimmer.increaseIntensity(); buttonDimmer.increaseIntensity();
break; } else {
case DOWN:
buttonDimmer.decreaseIntensity(); buttonDimmer.decreaseIntensity();
break;
} }
deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName()); deviceService.saveAllAsOwner(buttonDimmer.getOutputs(), principal.getName());

View file

@ -64,8 +64,8 @@ public class CurtainsController {
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Dimmable> s = c.cloneState(); State<? extends Dimmable> s = c.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -5,6 +5,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.error.BadDataException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; 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.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Room;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RoomRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.RoomRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService;
import java.security.Principal; import java.security.Principal;
@ -40,11 +41,12 @@ public class DeviceController {
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
// check if roomId is valid // check if roomId is valid
final Room r =
roomRepository roomRepository
.findByIdAndUsername(deviceSaveRequest.getRoomId(), principal.getName()) .findByIdAndUsername(deviceSaveRequest.getRoomId(), principal.getName())
.orElseThrow(() -> new BadDataException("roomId is not a valid room id")); .orElseThrow(() -> new BadDataException("roomId is not a valid room id"));
d.setRoomId(deviceSaveRequest.getRoomId()); d.setRoomId(r.getId());
d.setName(deviceSaveRequest.getName()); d.setName(deviceSaveRequest.getName());
deviceService.saveAsOwner(d, principal.getName()); deviceService.saveAsOwner(d, principal.getName());

View file

@ -16,10 +16,10 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/dimmableLight") @RequestMapping("/dimmableLight")
public class DimmableLightController extends GuestEnabledController<DimmableLight> { public class DimmableLightController extends GuestEnabledController<DimmableLight> {
private DimmableLightRepository dimmableLightRepository; private final DimmableLightRepository dimmableLightRepository;
private SceneRepository sceneRepository; private final SceneRepository sceneRepository;
private StateRepository<State<?>> stateRepository; private final StateRepository<State<?>> stateRepository;
private DeviceService deviceService; private final DeviceService deviceService;
@Autowired @Autowired
public DimmableLightController( public DimmableLightController(
@ -49,9 +49,9 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
} }
} }
/* /**
Assume that only the host can create a device * Assume that only the host can create a device Here save always as host, but remember to
Here save always as host, but remember to propagate change to guests (DeviceService.saveAsOwner()) * propagate change to guests (DeviceService.saveAsOwner())
*/ */
@PostMapping @PostMapping
public DimmableLight create( public DimmableLight create(
@ -61,9 +61,7 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
return save(new DimmableLight(), dl, principal.getName(), null); return save(new DimmableLight(), dl, principal.getName(), null);
} }
/* /** Logic for saving either as owner or guest is handled in method save of this controller */
Logic for saving either as owner or guest is handled in method save of this controller
*/
@PutMapping @PutMapping
public DimmableLight update( public DimmableLight update(
@Valid @RequestBody DimmableSaveRequest sp, @Valid @RequestBody DimmableSaveRequest sp,
@ -84,8 +82,10 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
deviceService.deleteByIdAsOwner(id, principal.getName()); deviceService.deleteByIdAsOwner(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 * 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") @PostMapping("/{id}/state")
public State<? extends Dimmable> sceneBinding( public State<? extends Dimmable> sceneBinding(
@PathVariable("id") long deviceId, @PathVariable("id") long deviceId,
@ -98,8 +98,8 @@ public class DimmableLightController extends GuestEnabledController<DimmableLigh
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Dimmable> s = d.cloneState(); State<? extends Dimmable> s = d.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -5,7 +5,6 @@ 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.dto.GuestPermissionsRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GuestsUpdateRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.GuestsUpdateRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserResponse; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.EagerUserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.EagerUserRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import java.security.Principal; import java.security.Principal;
@ -46,8 +45,7 @@ public class GuestController {
@PutMapping("/guests") @PutMapping("/guests")
public List<User> setGuests( public List<User> setGuests(
@RequestBody @Valid GuestsUpdateRequest g, final Principal principal) @RequestBody @Valid GuestsUpdateRequest g, final Principal principal) {
throws NotFoundException {
Iterable<User> guests = userRepository.findAllById(g.ids); Iterable<User> guests = userRepository.findAllById(g.ids);
User host = userRepository.findByUsername(principal.getName()); User host = userRepository.findByUsername(principal.getName());

View file

@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RequestBody;
* @param <O> the output device attached to I * @param <O> the output device attached to I
*/ */
public abstract class InputDeviceConnectionController< public abstract class InputDeviceConnectionController<
I extends InputDevice, O extends OutputDevice> { I extends InputDevice & Connectable<O>, O extends OutputDevice> {
private class Connection { private class Connection {
private final I input; private final I input;
@ -33,30 +33,40 @@ public abstract class InputDeviceConnectionController<
this.input = input; this.input = input;
this.outputs = outputs; this.outputs = outputs;
} }
public I getInput() {
return input;
}
public List<O> getOutputs() {
return outputs;
}
}
protected DeviceRepository<I> getInputRepository() {
return inputRepository;
}
protected DeviceRepository<O> getOutputReposiory() {
return outputReposiory;
} }
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
private DeviceRepository<I> inputRepository; private final DeviceRepository<I> inputRepository;
private DeviceRepository<O> outputReposiory; private final DeviceRepository<O> outputReposiory;
private Connector<I, O> connector;
/** /**
* Contstructs the controller by requiring essential object for the controller implementation * Contstructs the controller by requiring essential object for the controller implementation
* *
* @param inputRepository the input device repository * @param inputRepository the input device repository
* @param outputRepository the output device repository * @param outputRepository the output device repository
* @param connector a appropriate Connector instance for the I and O tyoes.
*/ */
protected InputDeviceConnectionController( protected InputDeviceConnectionController(
DeviceRepository<I> inputRepository, DeviceRepository<I> inputRepository, DeviceRepository<O> outputRepository) {
DeviceRepository<O> outputRepository,
Connector<I, O> connector) {
this.inputRepository = inputRepository; this.inputRepository = inputRepository;
this.outputReposiory = outputRepository; this.outputReposiory = outputRepository;
this.connector = connector;
} }
private Connection checkConnectionIDs(Long inputId, List<Long> outputs, String username) private Connection checkConnectionIDs(Long inputId, List<Long> outputs, String username)
@ -65,7 +75,7 @@ public abstract class InputDeviceConnectionController<
inputRepository inputRepository
.findByIdAndUsername(inputId, username) .findByIdAndUsername(inputId, username)
.orElseThrow(() -> new NotFoundException("input device")); .orElseThrow(() -> new NotFoundException("input device"));
final List<O> outputDevices = new ArrayList<>(); final List<O> outputDevices = new ArrayList<>(outputs.size());
for (final Long outputId : outputs) { for (final Long outputId : outputs) {
outputDevices.add( outputDevices.add(
outputReposiory outputReposiory
@ -87,12 +97,12 @@ public abstract class InputDeviceConnectionController<
Long inputId, List<Long> outputs, String username) throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputs, username); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.outputs) { for (final O o : pair.getOutputs()) {
connector.connect(pair.input, o, true); pair.getInput().connect(o, true);
} }
deviceService.saveAllAsOwner(pair.outputs, username); deviceService.saveAllAsOwner(pair.getOutputs(), username);
return pair.input.getOutputs(); return pair.getInput().getOutputs();
} }
/** /**
@ -107,12 +117,12 @@ public abstract class InputDeviceConnectionController<
Long inputId, List<Long> outputs, String username) throws NotFoundException { Long inputId, List<Long> outputs, String username) throws NotFoundException {
final Connection pair = checkConnectionIDs(inputId, outputs, username); final Connection pair = checkConnectionIDs(inputId, outputs, username);
for (final O o : pair.outputs) { for (final O o : pair.getOutputs()) {
connector.connect(pair.input, o, false); pair.getInput().connect(o, false);
} }
deviceService.saveAllAsOwner(pair.outputs, username); deviceService.saveAllAsOwner(pair.getOutputs(), username);
return pair.input.getOutputs(); return pair.getInput().getOutputs();
} }
@PostMapping("/{id}/lights") @PostMapping("/{id}/lights")

View file

@ -17,14 +17,17 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/knobDimmer") @RequestMapping("/knobDimmer")
public class KnobDimmerController extends InputDeviceConnectionController<KnobDimmer, Dimmable> { public class KnobDimmerController extends InputDeviceConnectionController<KnobDimmer, Dimmable> {
@Autowired private DeviceService deviceService; private final DeviceService deviceService;
@Autowired private KnobDimmerRepository knobDimmerRepository; private final KnobDimmerRepository knobDimmerRepository;
@Autowired @Autowired
protected KnobDimmerController( protected KnobDimmerController(
KnobDimmerRepository inputRepository, DimmableRepository<Dimmable> outputRepository) { KnobDimmerRepository inputRepository,
super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR); DimmableRepository<Dimmable> outputRepository,
DeviceService deviceService) {
super(inputRepository, outputRepository);
this.knobDimmerRepository = inputRepository; this.knobDimmerRepository = inputRepository;
this.deviceService = deviceService;
} }
@GetMapping("/{id}") @GetMapping("/{id}")

View file

@ -6,7 +6,6 @@ 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.models.MotionSensorRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; 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.service.MotionSensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -21,7 +20,6 @@ public class MotionSensorController {
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired private MotionSensorService motionSensorService; @Autowired private MotionSensorService motionSensorService;
@Autowired private MotionSensorRepository motionSensorRepository; @Autowired private MotionSensorRepository motionSensorRepository;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint;
@PostMapping @PostMapping
public MotionSensor create( public MotionSensor create(

View file

@ -25,11 +25,11 @@ public class RangeTriggerController {
@Autowired RangeTriggerRepository rangeTriggerRepository; @Autowired RangeTriggerRepository rangeTriggerRepository;
@GetMapping("/{automationId}") @GetMapping("/{automationId}")
public List<RangeTrigger<?>> getAll(@PathVariable long automationId) { public List<RangeTrigger> getAll(@PathVariable long automationId) {
return rangeTriggerRepository.findAllByAutomationId(automationId); return rangeTriggerRepository.findAllByAutomationId(automationId);
} }
private RangeTrigger<?> save(RangeTrigger<?> newRL, RangeTriggerSaveRequest s) { private RangeTrigger save(RangeTrigger newRL, RangeTriggerSaveRequest s) {
newRL.setDeviceId(s.getDeviceId()); newRL.setDeviceId(s.getDeviceId());
newRL.setAutomationId(s.getAutomationId()); newRL.setAutomationId(s.getAutomationId());
newRL.setOperator(s.getOperator()); newRL.setOperator(s.getOperator());
@ -39,13 +39,13 @@ public class RangeTriggerController {
} }
@PostMapping @PostMapping
public RangeTrigger<?> create( public RangeTrigger create(
@Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest) { @Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest) {
return save(new RangeTrigger<>(), booleanTriggerSaveRequest); return save(new RangeTrigger(), booleanTriggerSaveRequest);
} }
@PutMapping @PutMapping
public RangeTrigger<?> update( public RangeTrigger update(
@Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest) @Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest)
throws NotFoundException { throws NotFoundException {
return save( return save(

View file

@ -97,8 +97,6 @@ public class RegularLightController extends GuestEnabledController<RegularLight>
deviceService.deleteByIdAsOwner(id, principal.getName()); deviceService.deleteByIdAsOwner(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") @PostMapping("/{id}/state")
public State<? extends Switchable> sceneBinding( public State<? extends Switchable> sceneBinding(
@PathVariable("id") long deviceId, @PathVariable("id") long deviceId,
@ -110,8 +108,8 @@ public class RegularLightController extends GuestEnabledController<RegularLight>
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Switchable> s = d.cloneState(); State<? extends Switchable> s = d.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -6,7 +6,6 @@ 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.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; 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.DeviceService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils; import ch.usi.inf.sa4.sanmarinoes.smarthut.utils.Utils;
import java.security.Principal; import java.security.Principal;
import java.util.*; import java.util.*;
@ -20,19 +19,33 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/room") @RequestMapping("/room")
public class RoomController { public class RoomController {
@Autowired private RoomRepository roomRepository; private final RoomRepository roomRepository;
@Autowired private UserRepository userRepository; private final UserRepository userRepository;
@Autowired private DeviceService deviceService; private final DeviceService deviceService;
@Autowired private SwitchRepository switchRepository; private final SwitchRepository switchRepository;
@Autowired private ButtonDimmerRepository buttonDimmerRepository; private final ButtonDimmerRepository buttonDimmerRepository;
@Autowired private KnobDimmerRepository knobDimmerRepository; private final KnobDimmerRepository knobDimmerRepository;
@Autowired private ThermostatService thermostatService; @Autowired
public RoomController(
RoomRepository roomRepository,
UserRepository userRepository,
DeviceService deviceService,
SwitchRepository switchRepository,
ButtonDimmerRepository buttonDimmerRepository,
KnobDimmerRepository knobDimmerRepository) {
this.roomRepository = roomRepository;
this.userRepository = userRepository;
this.deviceService = deviceService;
this.switchRepository = switchRepository;
this.buttonDimmerRepository = buttonDimmerRepository;
this.knobDimmerRepository = knobDimmerRepository;
}
private <T> List<T> fetchOwnerOrGuest( private <T> List<T> fetchOwnerOrGuest(
final List<T> list, Long hostId, final Principal principal) throws NotFoundException { final List<T> list, Long hostId, final Principal principal) throws NotFoundException {

View file

@ -24,10 +24,8 @@ public class ScenePriorityController {
@Autowired ScenePriorityRepository scenePriorityRepository; @Autowired ScenePriorityRepository scenePriorityRepository;
@GetMapping("/{automationId}") @GetMapping("/{automationId}")
public List<ScenePriority> getByAutomationId(@PathVariable long automationId) public List<ScenePriority> getByAutomationId(@PathVariable long automationId) {
throws NotFoundException {
return scenePriorityRepository.findAllByAutomationId(automationId); return scenePriorityRepository.findAllByAutomationId(automationId);
} }

View file

@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.CameraConfigurationService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest; 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.DuplicateStateException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
@ -22,18 +23,32 @@ import org.springframework.web.bind.annotation.RestController;
@EnableAutoConfiguration @EnableAutoConfiguration
@RequestMapping("/securityCamera") @RequestMapping("/securityCamera")
public class SecurityCameraController { public class SecurityCameraController {
private final DeviceService deviceService;
private final SecurityCameraRepository securityCameraService;
private final SceneRepository sceneRepository;
private final StateRepository<State<?>> stateRepository;
private final CameraConfigurationService cameraConfigurationService;
@Autowired private DeviceService deviceService; @Autowired
@Autowired private SecurityCameraRepository securityCameraService; public SecurityCameraController(
@Autowired private SceneRepository sceneRepository; DeviceService deviceService,
@Autowired private StateRepository<State<?>> stateRepository; SecurityCameraRepository securityCameraService,
@Autowired private RoomRepository roomRepository; SceneRepository sceneRepository,
StateRepository<State<?>> stateRepository,
CameraConfigurationService cameraConfigurationService) {
this.deviceService = deviceService;
this.securityCameraService = securityCameraService;
this.sceneRepository = sceneRepository;
this.stateRepository = stateRepository;
this.cameraConfigurationService = cameraConfigurationService;
}
private SecurityCamera save( private SecurityCamera save(
SecurityCamera newSC, SwitchableSaveRequest sc, final Principal principal) { SecurityCamera newSC, SwitchableSaveRequest sc, final Principal principal) {
newSC.setName(sc.getName()); newSC.setName(sc.getName());
newSC.setRoomId(sc.getRoomId()); newSC.setRoomId(sc.getRoomId());
newSC.setOn(sc.isOn()); newSC.setOn(sc.isOn());
newSC.setPath(cameraConfigurationService.getVideoUrl());
return deviceService.saveAsOwner(newSC, principal.getName()); return deviceService.saveAsOwner(newSC, principal.getName());
} }
@ -76,8 +91,8 @@ public class SecurityCameraController {
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Switchable> s = d.cloneState(); State<? extends Switchable> s = d.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -5,7 +5,6 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; 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.DeviceService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.SensorService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.security.Principal; import java.security.Principal;
import java.util.*; import java.util.*;
@ -19,13 +18,21 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/sensor") @RequestMapping("/sensor")
public class SensorController { public class SensorController {
@Autowired private DeviceService deviceService; private final DeviceService deviceService;
@Autowired private SensorRepository sensorRepository; private final SensorRepository sensorRepository;
@Autowired private SensorSocketEndpoint sensorSocketEndpoint; private final SensorService sensorService;
@Autowired private SensorService sensorService; @Autowired
public SensorController(
DeviceService deviceService,
SensorRepository sensorRepository,
SensorService sensorService) {
this.deviceService = deviceService;
this.sensorRepository = sensorRepository;
this.sensorService = sensorService;
}
@PostMapping @PostMapping
public Sensor create(@Valid @RequestBody SensorSaveRequest s, final Principal principal) public Sensor create(@Valid @RequestBody SensorSaveRequest s, final Principal principal)

View file

@ -78,8 +78,8 @@ public class SmartPlugController {
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Switchable> s = d.cloneState(); State<? extends Switchable> s = d.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -17,9 +17,8 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/switch") @RequestMapping("/switch")
public class SwitchController extends InputDeviceConnectionController<Switch, Switchable> { public class SwitchController extends InputDeviceConnectionController<Switch, Switchable> {
private SwitchRepository switchRepository; private final SwitchRepository switchRepository;
private SwitchableRepository<Switchable> switchableRepository; private final DeviceService deviceService;
private DeviceService deviceService;
/** /**
* Contstructs the controller by requiring essential object for the controller implementation * Contstructs the controller by requiring essential object for the controller implementation
@ -32,7 +31,7 @@ public class SwitchController extends InputDeviceConnectionController<Switch, Sw
SwitchRepository inputRepository, SwitchRepository inputRepository,
SwitchableRepository<Switchable> outputRepository, SwitchableRepository<Switchable> outputRepository,
DeviceService deviceService) { DeviceService deviceService) {
super(inputRepository, outputRepository, Switchable.SWITCH_SWITCHABLE_CONNECTOR); super(inputRepository, outputRepository);
this.deviceService = deviceService; this.deviceService = deviceService;
this.switchRepository = inputRepository; this.switchRepository = inputRepository;
} }

View file

@ -5,7 +5,7 @@ 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.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; 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.DeviceService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.ThermostatPopulationService;
import java.security.Principal; import java.security.Principal;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -19,7 +19,7 @@ public class ThermostatController {
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired private ThermostatRepository thermostatRepository; @Autowired private ThermostatRepository thermostatRepository;
@Autowired private ThermostatService thermostatService; @Autowired private ThermostatPopulationService thermostatService;
@Autowired private SceneRepository sceneRepository; @Autowired private SceneRepository sceneRepository;
@Autowired private StateRepository<State<?>> stateRepository; @Autowired private StateRepository<State<?>> stateRepository;
@ -30,14 +30,12 @@ public class ThermostatController {
newT.setRoomId(t.getRoomId()); newT.setRoomId(t.getRoomId());
newT.setUseExternalSensors(t.isUseExternalSensors()); newT.setUseExternalSensors(t.isUseExternalSensors());
newT.setOn(false); newT.setOn(false);
System.out.println(newT);
thermostatService.populateMeasuredTemperature(newT); thermostatService.populateMeasuredTemperature(newT);
newT = thermostatRepository.save(newT); newT = thermostatRepository.save(newT);
newT.setOn(t.isTurnOn()); newT.setOn(t.isTurnOn());
newT = deviceService.saveAsOwner(newT, principal.getName()); return deviceService.saveAsOwner(newT, principal.getName());
return newT;
} }
@PostMapping @PostMapping
@ -76,8 +74,8 @@ public class ThermostatController {
.findByIdAndUsername(deviceId, principal.getName()) .findByIdAndUsername(deviceId, principal.getName())
.orElseThrow(NotFoundException::new); .orElseThrow(NotFoundException::new);
State<? extends Switchable> s = d.cloneState(); State<? extends Switchable> s = d.cloneState();
sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new); final Scene sc = sceneRepository.findById(sceneId).orElseThrow(NotFoundException::new);
s.setSceneId(sceneId); s.setSceneId(sc.getId());
if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0) if (stateRepository.countByDeviceIdAndSceneId(deviceId, sceneId) > 0)
throw new DuplicateStateException(); throw new DuplicateStateException();
return stateRepository.save(s); return stateRepository.save(s);

View file

@ -2,7 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.EmailConfigurationService;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.InitPasswordResetRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.PasswordResetRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
@ -65,7 +64,7 @@ public class UserAccountController {
+ (isRegistration + (isRegistration
? emailConfig.getRegistrationPath() ? emailConfig.getRegistrationPath()
: emailConfig.getResetPasswordPath()) : emailConfig.getResetPasswordPath())
+ token.getConfirmationToken()); + token.getConfirmToken());
emailSenderService.sendEmail(mailMessage); emailSenderService.sendEmail(mailMessage);
} }
@ -78,7 +77,7 @@ public class UserAccountController {
* @throws DuplicateRegistrationException if a user exists with same email or username * @throws DuplicateRegistrationException if a user exists with same email or username
*/ */
@PostMapping @PostMapping
public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData) public void registerUser(@Valid @RequestBody UserRegistrationRequest registrationData)
throws DuplicateRegistrationException { throws DuplicateRegistrationException {
final User existingEmailUser = final User existingEmailUser =
userRepository.findByEmailIgnoreCase(registrationData.getEmail()); userRepository.findByEmailIgnoreCase(registrationData.getEmail());
@ -105,15 +104,12 @@ public class UserAccountController {
ConfirmationToken token; ConfirmationToken token;
do { do {
token = new ConfirmationToken(toSave); token = new ConfirmationToken(toSave);
} while (confirmationTokenRepository.findByConfirmationToken( } while (confirmationTokenRepository.findByConfirmToken(token.getConfirmToken())
token.getConfirmationToken())
!= null); != null);
confirmationTokenRepository.save(token); confirmationTokenRepository.save(token);
sendEmail(toSave.getEmail(), token, true); sendEmail(toSave.getEmail(), token, true);
return new OkResponse();
} }
} }
@ -125,7 +121,7 @@ public class UserAccountController {
* @throws UserNotFoundException if given email does not belong to any user * @throws UserNotFoundException if given email does not belong to any user
*/ */
@PostMapping("/init-reset-password") @PostMapping("/init-reset-password")
public OkResponse initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest) public void initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest)
throws UserNotFoundException { throws UserNotFoundException {
final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail()); final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail());
@ -138,8 +134,7 @@ public class UserAccountController {
do { do {
token = new ConfirmationToken(toReset); token = new ConfirmationToken(toReset);
token.setResetPassword(true); token.setResetPassword(true);
} while (confirmationTokenRepository.findByConfirmationToken(token.getConfirmationToken()) } while (confirmationTokenRepository.findByConfirmToken(token.getConfirmToken()) != null);
!= null);
// Delete existing email password reset tokens // Delete existing email password reset tokens
confirmationTokenRepository.deleteByUserAndResetPassword(toReset, true); confirmationTokenRepository.deleteByUserAndResetPassword(toReset, true);
@ -148,8 +143,6 @@ public class UserAccountController {
confirmationTokenRepository.save(token); confirmationTokenRepository.save(token);
sendEmail(toReset.getEmail(), token, false); sendEmail(toReset.getEmail(), token, false);
return new OkResponse();
} }
/** /**
@ -160,13 +153,10 @@ public class UserAccountController {
* @throws EmailTokenNotFoundException if given token is not a valid token for password reset * @throws EmailTokenNotFoundException if given token is not a valid token for password reset
*/ */
@PutMapping("/reset-password") @PutMapping("/reset-password")
public OkResponse resetPassword( public void resetPassword(@Valid @RequestBody PasswordResetRequest resetRequest)
@Valid @RequestBody PasswordResetRequest resetRequest, throws EmailTokenNotFoundException {
final HttpServletResponse response)
throws EmailTokenNotFoundException, IOException {
final ConfirmationToken token = final ConfirmationToken token =
confirmationTokenRepository.findByConfirmationToken( confirmationTokenRepository.findByConfirmToken(resetRequest.getConfirmationToken());
resetRequest.getConfirmationToken());
if (token == null || !token.getResetPassword()) { if (token == null || !token.getResetPassword()) {
throw new EmailTokenNotFoundException(); throw new EmailTokenNotFoundException();
@ -178,8 +168,6 @@ public class UserAccountController {
// Delete token to prevent further password changes // Delete token to prevent further password changes
confirmationTokenRepository.delete(token); confirmationTokenRepository.delete(token);
return new OkResponse();
} }
/** /**
@ -196,7 +184,7 @@ public class UserAccountController {
final HttpServletResponse response) final HttpServletResponse response)
throws EmailTokenNotFoundException, IOException { throws EmailTokenNotFoundException, IOException {
final ConfirmationToken token = final ConfirmationToken token =
confirmationTokenRepository.findByConfirmationToken(confirmationToken); confirmationTokenRepository.findByConfirmToken(confirmationToken);
if (token != null && !token.getResetPassword()) { if (token != null && !token.getResetPassword()) {
token.getUser().setEnabled(true); token.getUser().setEnabled(true);

View file

@ -26,7 +26,7 @@ public class AutomationFastUpdateRequest {
@Override @Override
public Trigger<?> toModel() { public Trigger<?> toModel() {
BooleanTrigger<?> t = new BooleanTrigger<>(); BooleanTrigger t = new BooleanTrigger();
t.setDeviceId(this.deviceId); t.setDeviceId(this.deviceId);
t.setOn(this.on); t.setOn(this.on);
return t; return t;
@ -39,7 +39,7 @@ public class AutomationFastUpdateRequest {
@Override @Override
public Trigger<?> toModel() { public Trigger<?> toModel() {
RangeTrigger<?> t = new RangeTrigger<>(); RangeTrigger t = new RangeTrigger();
t.setDeviceId(this.deviceId); t.setDeviceId(this.deviceId);
t.setOperator(this.operator); t.setOperator(this.operator);
t.setRange(this.range); t.setRange(this.range);

View file

@ -1,6 +0,0 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
/** A dummy DTO to return when there is no data to return */
public class OkResponse {
private boolean success = true;
}

View file

@ -1,8 +1,8 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto; package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Icon;
import com.sun.istack.NotNull;
import javax.persistence.Column; import javax.persistence.Column;
import javax.validation.constraints.NotNull;
public class SceneSaveRequest { public class SceneSaveRequest {

View file

@ -16,4 +16,16 @@ public class UserResponse {
us.username = u.getUsername(); us.username = u.getUsername();
return us; return us;
} }
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getName() {
return name;
}
} }

View file

@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ResponseStatus;
public class UnauthorizedException extends Exception { public class UnauthorizedException extends Exception {
private final boolean isUserDisabled; private final boolean isUserDisabled;
public UnauthorizedException(boolean isDisabled) { public UnauthorizedException(boolean isDisabled, Throwable cause) {
super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials")); super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials"), cause);
this.isUserDisabled = isDisabled; this.isUserDisabled = isDisabled;
} }

View file

@ -46,10 +46,6 @@ public class Automation {
this.id = id; this.id = id;
} }
public Set<Trigger<?>> getStates() {
return triggers;
}
public Set<ScenePriority> getScenes() { public Set<ScenePriority> getScenes() {
return scenes; return scenes;
} }

View file

@ -4,7 +4,7 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
@Entity @Entity
public class BooleanTrigger<D extends Device & BooleanTriggerable> extends Trigger<D> { public class BooleanTrigger extends Trigger<BooleanTriggerable> {
@Column(name = "switchable_on") @Column(name = "switchable_on")
private boolean on; private boolean on;

View file

@ -3,8 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List; import java.util.List;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
public interface BooleanTriggerRepository public interface BooleanTriggerRepository extends TriggerRepository<BooleanTrigger> {
extends TriggerRepository<BooleanTrigger<? extends Device>> {
List<BooleanTrigger<?>> findAllByAutomationId(@Param("automationId") long automationId); List<BooleanTrigger> findAllByAutomationId(@Param("automationId") long automationId);
} }

View file

@ -22,7 +22,7 @@ public class ConfirmationToken {
private Long id; private Long id;
@Column(name = "confirmation_token", unique = true) @Column(name = "confirmation_token", unique = true)
private String confirmationToken; private String confirmToken;
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date createdDate; private Date createdDate;
@ -32,12 +32,12 @@ public class ConfirmationToken {
private User user; private User user;
@Column(nullable = false) @Column(nullable = false)
private Boolean resetPassword; private boolean resetPassword;
public ConfirmationToken(User user) { public ConfirmationToken(User user) {
this.user = user; this.user = user;
createdDate = new Date(); createdDate = new Date();
confirmationToken = UUID.randomUUID().toString(); confirmToken = UUID.randomUUID().toString();
resetPassword = false; resetPassword = false;
} }
@ -48,12 +48,12 @@ public class ConfirmationToken {
return id; return id;
} }
public String getConfirmationToken() { public String getConfirmToken() {
return confirmationToken; return confirmToken;
} }
public Date getCreatedDate() { public Date getCreatedDate() {
return createdDate; return (Date) createdDate.clone();
} }
public User getUser() { public User getUser() {
@ -64,23 +64,23 @@ public class ConfirmationToken {
this.id = id; this.id = id;
} }
public void setConfirmationToken(String confirmationToken) { public void setConfirmToken(String confirmToken) {
this.confirmationToken = confirmationToken; this.confirmToken = confirmToken;
} }
public void setCreatedDate(Date createdDate) { public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate; this.createdDate = (Date) createdDate.clone();
} }
public void setUser(User user) { public void setUser(User user) {
this.user = user; this.user = user;
} }
public Boolean getResetPassword() { public boolean getResetPassword() {
return resetPassword; return resetPassword;
} }
public void setResetPassword(Boolean resetPassword) { public void setResetPassword(boolean resetPassword) {
this.resetPassword = resetPassword; this.resetPassword = resetPassword;
} }
} }

View file

@ -4,7 +4,7 @@ import javax.transaction.Transactional;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> { public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
ConfirmationToken findByConfirmationToken(String confirmationToken); ConfirmationToken findByConfirmToken(String confirmToken);
ConfirmationToken findByUser(User user); ConfirmationToken findByUser(User user);

View file

@ -0,0 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface Connectable<O extends OutputDevice> {
void connect(O output, boolean connect);
}

View file

@ -1,47 +0,0 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* A rule on how to connect an input device type to an output device type
*
* @param <I> the input device type
* @param <O> the output device type
*/
@FunctionalInterface
public interface Connector<I extends InputDevice, O extends OutputDevice> {
/**
* Connects or disconnects input to output
*
* @param input the input device
* @param output the output device
* @param connect true if connection, false if disconnection
*/
void connect(I input, O output, boolean connect);
/**
* 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 inputsGetter the getter method of the set of outputs on the input class
* @param <J> the input device type
* @param <K> the output device type
* @return a Connector implementation for the pair of types J and K
*/
static <J extends InputDevice, K extends OutputDevice> Connector<J, K> basic(
Function<J, Set<? super K>> outputsGetter, Function<K, Set<? super J>> 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);
}
};
}
}

View file

@ -3,8 +3,8 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import javax.persistence.Entity; import javax.persistence.Entity;
/** /**
* Represents a curtain. The intensity represents how much the curtains are opened, * Represents a curtain. The intensity represents how much the curtains are opened, 0 is completely
* 0 is completely closed 100 is completely open * closed 100 is completely open
*/ */
@Entity @Entity
public class Curtains extends Dimmable { public class Curtains extends Dimmable {

View file

@ -4,10 +4,8 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.NotNull;
/** Generic abstraction for a smart home device */ /** Generic abstraction for a smart home device */
@Entity @Entity
@ -39,18 +37,16 @@ public abstract class Device {
@OneToMany(mappedBy = "device", orphanRemoval = true) @OneToMany(mappedBy = "device", orphanRemoval = true)
@GsonExclude @GsonExclude
@SocketGsonExclude @SocketGsonExclude
private Set<Trigger<? extends Device>> triggers = new HashSet<>(); private Set<Trigger<? extends Device>> triggers;
/** /**
* The room this device belongs in, as a foreign key id. To use when updating and inserting from * The room this device belongs in, as a foreign key id. To use when updating and inserting from
* a REST call. * a REST call.
*/ */
@Column(name = "room_id", nullable = false) @Column(name = "room_id", nullable = false)
@NotNull
private Long roomId; private Long roomId;
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */ /** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
@ -69,7 +65,7 @@ public abstract class Device {
@OneToMany(mappedBy = "device", orphanRemoval = true) @OneToMany(mappedBy = "device", orphanRemoval = true)
@GsonExclude @GsonExclude
@SocketGsonExclude @SocketGsonExclude
private Set<State<?>> states = new HashSet<>(); private Set<State<?>> states;
@Transient @GsonExclude private Long fromHostId = null; @Transient @GsonExclude private Long fromHostId = null;
@ -77,28 +73,9 @@ public abstract class Device {
@Transient @GsonExclude private boolean deleted = false; @Transient @GsonExclude private boolean deleted = false;
public Long getFromHostId() { public Device(String kind, FlowType flowType) {
return fromHostId; this.kind = kind;
} this.flowType = flowType;
public void setFromHostId(Long fromHostId) {
this.fromHostId = fromHostId;
}
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 long getId() { public long getId() {
@ -109,12 +86,20 @@ public abstract class Device {
this.id = id; this.id = id;
} }
public String getName() { public Room getRoom() {
return name; return room;
} }
public void setName(String name) { public void setRoom(Room room) {
this.name = name; this.room = room;
}
public Set<Trigger<? extends Device>> getTriggers() {
return triggers;
}
public void setTriggers(Set<Trigger<? extends Device>> triggers) {
this.triggers = triggers;
} }
public Long getRoomId() { public Long getRoomId() {
@ -125,8 +110,51 @@ public abstract class Device {
this.roomId = roomId; this.roomId = roomId;
} }
public Device(String kind, FlowType flowType) { public String getName() {
this.kind = kind; return name;
this.flowType = flowType; }
public void setName(String name) {
this.name = name;
}
public String getKind() {
return kind;
}
public FlowType getFlowType() {
return flowType;
}
public Set<State<?>> getStates() {
return states;
}
public void setStates(Set<State<?>> states) {
this.states = states;
}
public Long getFromHostId() {
return fromHostId;
}
public void setFromHostId(Long fromHostId) {
this.fromHostId = fromHostId;
}
public boolean isFromGuest() {
return fromGuest;
}
public void setFromGuest(boolean fromGuest) {
this.fromGuest = fromGuest;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
} }
} }

View file

@ -12,12 +12,6 @@ import javax.validation.constraints.NotNull;
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Dimmable extends Switchable implements RangeTriggerable { public class Dimmable extends Switchable implements RangeTriggerable {
public static final Connector<KnobDimmer, Dimmable> KNOB_DIMMER_DIMMABLE_CONNECTOR =
Connector.basic(KnobDimmer::getOutputs, Dimmable::getDimmers);
public static final Connector<ButtonDimmer, Dimmable> BUTTON_DIMMER_DIMMABLE_CONNECTOR =
Connector.basic(ButtonDimmer::getOutputs, Dimmable::getDimmers);
protected Dimmable(String kind) { protected Dimmable(String kind) {
super(kind); super(kind);
} }

View file

@ -1,4 +1,3 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
public interface DimmableRepository<T extends Dimmable> extends SwitchableRepository<T> { public interface DimmableRepository<T extends Dimmable> extends SwitchableRepository<T> {}
}

View file

@ -9,7 +9,7 @@ import javax.persistence.*;
/** Represents a generic dimmer input device */ /** Represents a generic dimmer input device */
@Entity @Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Dimmer extends InputDevice { public abstract class Dimmer extends InputDevice implements Connectable<Dimmable> {
public Dimmer(String kind) { public Dimmer(String kind) {
super(kind); super(kind);
} }
@ -37,4 +37,14 @@ public abstract class Dimmer extends InputDevice {
public void addDimmable(Dimmable dimmable) { public void addDimmable(Dimmable dimmable) {
dimmables.add(dimmable); dimmables.add(dimmable);
} }
public void connect(Dimmable output, boolean connect) {
if (connect) {
output.getDimmers().add(this);
getOutputs().add(output);
} else {
output.getDimmers().remove(this);
getOutputs().remove(output);
}
}
} }

View file

@ -3,10 +3,9 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
@Entity @Entity
public class RangeTrigger<D extends Device & RangeTriggerable> extends Trigger<D> { public class RangeTrigger extends Trigger<RangeTriggerable> {
public RangeTrigger() { public RangeTrigger() {
super("rangeTrigger"); super("rangeTrigger");
@ -43,11 +42,9 @@ public class RangeTrigger<D extends Device & RangeTriggerable> extends Trigger<D
GREATER_EQUAL GREATER_EQUAL
} }
@NotNull
@Column(nullable = false) @Column(nullable = false)
private Operator operator; private Operator operator;
@NotNull
@Column(nullable = false) @Column(nullable = false)
private double range; private double range;

View file

@ -3,7 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.util.List; import java.util.List;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
public interface RangeTriggerRepository extends TriggerRepository<RangeTrigger<? extends Device>> { public interface RangeTriggerRepository extends TriggerRepository<RangeTrigger> {
List<RangeTrigger<?>> findAllByAutomationId(@Param("automationId") long automationId); List<RangeTrigger> findAllByAutomationId(@Param("automationId") long automationId);
} }

View file

@ -99,4 +99,12 @@ public class Room {
public String toString() { public String toString() {
return "Room{" + "id=" + id + ", name='" + name + "\'}"; return "Room{" + "id=" + id + ", name='" + name + "\'}";
} }
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
} }

View file

@ -28,7 +28,6 @@ public class ScenePriority {
private Automation automation; private Automation automation;
@Column(name = "automation_id", nullable = false) @Column(name = "automation_id", nullable = false)
@NotNull
private Long automationId; private Long automationId;
@NotNull @NotNull
@ -42,9 +41,12 @@ public class ScenePriority {
private Scene scene; private Scene scene;
@Column(name = "scene_id", nullable = false, updatable = false) @Column(name = "scene_id", nullable = false, updatable = false)
@NotNull
private Long sceneId; private Long sceneId;
public long getId() {
return id;
}
public Integer getPriority() { public Integer getPriority() {
return priority; return priority;
} }
@ -53,21 +55,16 @@ public class ScenePriority {
this.priority = priority; this.priority = priority;
} }
public Automation getAutomation() {
return automation;
}
public void setAutomation(Automation automation) {
this.automation = automation;
}
public Long getAutomationId() { public Long getAutomationId() {
return automationId; return automationId;
} }
public ScenePriority setAutomationId(Long automationId) { public void setAutomationId(Long automationId) {
this.automationId = automationId; this.automationId = automationId;
return this; }
public void setAutomation(Automation automation) {
this.automation = automation;
} }
public Scene getScene() { public Scene getScene() {
@ -88,10 +85,14 @@ public class ScenePriority {
@PreRemove @PreRemove
public void preRemove() { public void preRemove() {
this.setAutomation(null); this.automation = null;
this.setAutomationId(null); this.automationId = null;
this.setScene(null); this.scene = null;
this.setSceneId(null); this.sceneId = null;
}
public Automation getAutomation() {
return automation;
} }
} }

View file

@ -18,12 +18,16 @@ public class SecurityCamera extends Switchable implements BooleanTriggerable {
@Column(name = "video", nullable = false) @Column(name = "video", nullable = false)
@NotNull @NotNull
private String path = "/security_camera_videos/security_camera_1.mp4"; private String path;
public String getPath() { public String getPath() {
return path; return path;
} }
public void setPath(String path) {
this.path = path;
}
@Override @Override
public boolean isOn() { public boolean isOn() {
return on; return on;

View file

@ -7,7 +7,6 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.validation.constraints.NotNull;
/** A sensor input device that measures a quantity in a continuous scale (e.g. temperature) */ /** A sensor input device that measures a quantity in a continuous scale (e.g. temperature) */
@Entity @Entity
@ -15,9 +14,9 @@ public class Sensor extends InputDevice implements RangeTriggerable {
public static final Map<SensorType, BigDecimal> TYPICAL_VALUES = public static final Map<SensorType, BigDecimal> TYPICAL_VALUES =
Map.of( Map.of(
SensorType.TEMPERATURE, new BigDecimal(17.0), SensorType.TEMPERATURE, BigDecimal.valueOf(17.0),
SensorType.HUMIDITY, new BigDecimal(40.0), SensorType.HUMIDITY, BigDecimal.valueOf(40.0),
SensorType.LIGHT, new BigDecimal(1000)); SensorType.LIGHT, BigDecimal.valueOf(1000));
@Override @Override
public double readTriggerState() { public double readTriggerState() {
@ -45,7 +44,6 @@ public class Sensor extends InputDevice implements RangeTriggerable {
/** The type of this sensor */ /** The type of this sensor */
@Column(nullable = false) @Column(nullable = false)
@NotNull
@Enumerated(value = EnumType.STRING) @Enumerated(value = EnumType.STRING)
private SensorType sensor; private SensorType sensor;

View file

@ -1,6 +1,5 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models; package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -20,7 +19,6 @@ public class SmartPlug extends Switchable implements BooleanTriggerable {
/** Whether the smart plug is on */ /** Whether the smart plug is on */
@Column(name = "smart_plug_on", nullable = false) @Column(name = "smart_plug_on", nullable = false)
@NotNull
private boolean on; private boolean on;
public BigDecimal getTotalConsumption() { public BigDecimal getTotalConsumption() {

View file

@ -8,7 +8,7 @@ import javax.persistence.*;
/** A switch input device */ /** A switch input device */
@Entity @Entity
public class Switch extends InputDevice implements BooleanTriggerable { public class Switch extends InputDevice implements BooleanTriggerable, Connectable<Switchable> {
@ManyToMany( @ManyToMany(
cascade = { cascade = {
@ -60,10 +60,21 @@ public class Switch extends InputDevice implements BooleanTriggerable {
return on; return on;
} }
@Override
public Set<Switchable> getOutputs() { public Set<Switchable> getOutputs() {
return switchables; return switchables;
} }
public void connect(Switchable output, boolean connect) {
if (connect) {
output.getSwitches().add(this);
getOutputs().add(output);
} else {
output.getSwitches().remove(this);
getOutputs().remove(output);
}
}
@Override @Override
public boolean readTriggerState() { public boolean readTriggerState() {
return on; return on;

View file

@ -11,9 +11,6 @@ import javax.persistence.*;
@Inheritance(strategy = InheritanceType.JOINED) @Inheritance(strategy = InheritanceType.JOINED)
public abstract class Switchable extends OutputDevice { public abstract class Switchable extends OutputDevice {
public static final Connector<Switch, Switchable> SWITCH_SWITCHABLE_CONNECTOR =
Connector.basic(Switch::getOutputs, Switchable::getSwitches);
@ManyToMany( @ManyToMany(
mappedBy = "switchables", mappedBy = "switchables",
cascade = { cascade = {

View file

@ -6,11 +6,9 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Transient; import javax.persistence.Transient;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.springframework.stereotype.Component;
/** A thermostat capable of controlling cooling and heating. */ /** A thermostat capable of controlling cooling and heating. */
@Entity @Entity
@Component
public class Thermostat extends Switchable implements BooleanTriggerable { public class Thermostat extends Switchable implements BooleanTriggerable {
@Override @Override
@ -66,7 +64,7 @@ public class Thermostat extends Switchable implements BooleanTriggerable {
} }
/** Temperature to be reached */ /** Temperature to be reached */
@Column @NotNull private BigDecimal targetTemperature; @Column private BigDecimal targetTemperature;
/** The temperature detected by the embedded sensor */ /** The temperature detected by the embedded sensor */
@Column(nullable = false, precision = 4, scale = 1) @Column(nullable = false, precision = 4, scale = 1)

View file

@ -24,7 +24,6 @@ public interface ThermostatRepository extends DeviceRepository<Thermostat> {
* @param thermostatRoomId room ID of the thermostat * @param thermostatRoomId room ID of the thermostat
* @return an optional big decimal, empty if none found * @return an optional big decimal, empty if none found
*/ */
@Query( @Query("SELECT AVG(s.value) FROM Sensor s JOIN s.room r WHERE s.sensor = ?2 AND r.id = ?1")
"SELECT AVG(s.value) FROM Sensor s JOIN s.room r WHERE s.sensor = 'TEMPERATURE' AND r.id = ?1") Optional<BigDecimal> getAverageTemperature(Long thermostatRoomId, Sensor.SensorType sensorType);
Optional<BigDecimal> getAverageTemperature(Long thermostatRoomId);
} }

View file

@ -3,11 +3,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.GsonExclude;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity @Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Trigger<D extends Device> { public abstract class Trigger<D> {
@Transient private String kind; @Transient private String kind;
@ -37,7 +36,6 @@ public abstract class Trigger<D extends Device> {
* from a REST call. * from a REST call.
*/ */
@Column(name = "device_id", nullable = false) @Column(name = "device_id", nullable = false)
@NotNull
private Long deviceId; private Long deviceId;
@ManyToOne @ManyToOne
@ -46,7 +44,6 @@ public abstract class Trigger<D extends Device> {
private Automation automation; private Automation automation;
@Column(name = "automation_id", nullable = false) @Column(name = "automation_id", nullable = false)
@NotNull
private Long automationId; private Long automationId;
public long getId() { public long getId() {

View file

@ -56,7 +56,7 @@ public class User {
@Column(nullable = false) @Column(nullable = false)
@GsonExclude @GsonExclude
private Boolean isEnabled = false; private boolean isEnabled = false;
public Long getId() { public Long getId() {
return id; return id;
@ -98,11 +98,11 @@ public class User {
this.password = password; this.password = password;
} }
public Boolean getEnabled() { public boolean getEnabled() {
return isEnabled; return isEnabled;
} }
public void setEnabled(Boolean enabled) { public void setEnabled(boolean enabled) {
isEnabled = enabled; isEnabled = enabled;
} }
@ -162,16 +162,17 @@ public class User {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
User user = (User) o; User user = (User) o;
return id.equals(user.id) return cameraEnabled == user.cameraEnabled
&& name.equals(user.name) && isEnabled == user.isEnabled
&& username.equals(user.username) && Objects.equals(id, user.id)
&& password.equals(user.password) && Objects.equals(name, user.name)
&& email.equals(user.email) && Objects.equals(username, user.username)
&& isEnabled.equals(user.isEnabled); && Objects.equals(password, user.password)
&& Objects.equals(email, user.email);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, name, username, password, email, isEnabled); return Objects.hash(id, name, username, password, email, isEnabled, cameraEnabled);
} }
} }

View file

@ -0,0 +1,28 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AutomationService {
private final AutomationRepository automationRepository;
private final TriggerRepository<Trigger<Device>> triggerRepository;
@Autowired
public AutomationService(
AutomationRepository automationRepository,
TriggerRepository<Trigger<Device>> triggerRepository) {
this.automationRepository = automationRepository;
this.triggerRepository = triggerRepository;
}
public List<Trigger<Device>> findTriggersByDeviceId(Long deviceId) {
return triggerRepository.findAllByDeviceId(deviceId);
}
public Automation findByVerifiedId(Long automationId) {
return automationRepository.findById(automationId).orElseThrow();
}
}

View file

@ -0,0 +1,20 @@
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.Thermostat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DevicePopulationService {
@Autowired private ThermostatPopulationService thermostatService;
public void populateComputedFields(Iterable<Device> devices) {
for (Device d : devices) {
if (d instanceof Thermostat) {
thermostatService.populateMeasuredTemperature((Thermostat) d);
}
}
}
}

View file

@ -0,0 +1,151 @@
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.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DevicePropagationService {
private final SensorSocketEndpoint endpoint;
private final EagerUserRepository userRepository;
private final DeviceRepository<Device> deviceRepository;
@Autowired
public DevicePropagationService(
SensorSocketEndpoint endpoint,
EagerUserRepository userRepository,
DeviceRepository<Device> deviceRepository) {
this.endpoint = endpoint;
this.userRepository = userRepository;
this.deviceRepository = deviceRepository;
}
void propagateUpdateAsGuest(Device device, User host, User guest) {
final Set<User> guests = Set.copyOf(host.getGuests());
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
// true.
// broadcast device update to host
endpoint.queueDeviceUpdate(device, host, true, null, false);
// We're telling all guests that a higher entity has issued a device update. Therefore,
// fromHost becomes true.
for (final User aGuest : guests) {
if (aGuest.equals(guest)) {
continue;
}
// enqueue all device updates for all other guests
endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false);
}
}
void saveAllAsGuestSceneApplication(List<Device> devices, String guestUsername, Long hostId) {
final User guest = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
deviceRepository.saveAll(devices);
devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest));
}
void renameIfDuplicate(Device toCreate, String username) {
while (deviceRepository.findDuplicates(toCreate.getName(), username)
- (toCreate.getId() <= 0 ? 0 : 1)
> 0) {
toCreate.setName(toCreate.getName() + " (new)");
}
}
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
throws NotFoundException {
final User currentUser = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
if (!host.getGuests().contains(currentUser)) throw new NotFoundException();
renameIfDuplicate(device, host.getUsername());
device = deviceRepository.save(device);
propagateUpdateAsGuest(device, host, currentUser);
return device;
}
/**
* Saves all the devices given in devices assuming that the owner updated them in one way or
* another. Takes care of the appropriate websocket updates and trigger checking as well. No
* checking is done to verify that the user whose username is given is in fact the owner of
* these devices
*
* @param devices the list of devices to save
* @param username the username of the owner of these devices
* @param fromScene true if the update comes from the a scene application side effect. Disables
* trigger checking to avoid recursive invocations of automations
* @param fromTrigger true if the update comes from a scene application executed by an
* automation. Propagates updated through socket to owner as well. No effect if fromScene is
* false.
* @param <T> the type of device contained in the list
* @return the updated list of devices, ready to be fed to GSON
*/
public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
devices.forEach(d -> renameIfDuplicate(d, username));
devices = deviceRepository.saveAll(devices);
devices.forEach(d -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger));
return toList(devices);
}
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
return saveAllAsOwner(devices, username, false, false);
}
public <T extends Device> T saveAsOwner(T device, String username) {
renameIfDuplicate(device, username);
device = deviceRepository.save(device);
propagateUpdateAsOwner(device, username, false);
return device;
}
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
Device d =
deviceRepository
.findByIdAndUsername(id, username)
.orElseThrow(NotFoundException::new);
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true);
}
deviceRepository.delete(d);
}
/**
* Propagates the update through the socket assuming that the user that modified the device is
* the owner of that device
*
* @param device the updated device
* @param username the username of the owner of that device
* @param causedByTrigger if true, send the update to the owner as well
*/
private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) {
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false);
}
if (causedByTrigger) {
endpoint.queueDeviceUpdate(device, user, false, user.getId(), false);
}
}
}

View file

@ -4,12 +4,9 @@ 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.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -18,129 +15,136 @@ import org.springframework.stereotype.Component;
@Component @Component
public class DeviceService { public class DeviceService {
@Autowired private DeviceRepository<Device> deviceRepository; private final DeviceRepository<Device> deviceRepository;
@Autowired private AutomationRepository automationRepository; private final SceneService sceneService;
@Autowired private SceneRepository sceneRepository; private final RoomRepository roomRepository;
@Autowired private SceneService sceneService; private final AutomationService automationService;
@Autowired private TriggerRepository<Trigger<? extends Device>> triggerRepository; private final EagerUserRepository userRepository;
@Autowired private ConditionRepository<Condition<? extends Device>> conditionRepository; private final DevicePopulationService devicePopulationService;
@Autowired private RoomRepository roomRepository; private final DevicePropagationService devicePropagationService;
@Autowired private EagerUserRepository userRepository; private final ConditionRepository<Condition<?>> conditionRepository;
@Autowired private SensorSocketEndpoint endpoint;
@Autowired private ThermostatService thermostatService; @Autowired
public DeviceService(
DeviceRepository<Device> deviceRepository,
SceneService sceneService,
RoomRepository roomRepository,
AutomationService automationService,
EagerUserRepository userRepository,
DevicePopulationService devicePopulationService,
DevicePropagationService devicePropagationService,
ConditionRepository<Condition<?>> conditionRepository) {
this.deviceRepository = deviceRepository;
this.sceneService = sceneService;
this.roomRepository = roomRepository;
this.automationService = automationService;
this.userRepository = userRepository;
this.devicePopulationService = devicePopulationService;
this.devicePropagationService = devicePropagationService;
this.conditionRepository = conditionRepository;
}
public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException { public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException {
roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new); final Room r =
} roomRepository
.findByIdAndUsername(roomId, username)
private void renameIfDuplicate(Device toCreate, String username) { .orElseThrow(NotFoundException::new);
while (deviceRepository.findDuplicates(toCreate.getName(), username) if (!r.getId().equals(roomId)) throw new IllegalStateException();
- (toCreate.getId() <= 0 ? 0 : 1)
> 0) {
toCreate.setName(toCreate.getName() + " (new)");
}
} }
private void triggerTriggers(Device device, final String username) { private void triggerTriggers(Device device, final String username) {
final long deviceId = device.getId(); final long deviceId = device.getId();
final List<Trigger<Device>> triggers = automationService.findTriggersByDeviceId(deviceId);
// find the conditions that this device state change is triggering
List<Condition<? extends Device>> triggeredConditions =
conditionRepository
.findAllByDeviceId(deviceId)
.stream()
.filter(Condition::triggered)
.collect(Collectors.toList());
List<Automation> automationsWithMetConditions = new ArrayList<>();
// this condition is connected to an automation obviously, and this automation might be
// connected to many other conditions, I need to find them all and make sure all of them are
// in their "TRUE" state.
// if that's the case I need to remember that this automation needs to be triggered
for (Condition<? extends Device> condition : triggeredConditions) {
Automation a =
automationRepository
.findById(condition.getAutomationId())
.orElseThrow(IllegalStateException::new);
List<Condition<? extends Device>> conditions =
conditionRepository.findAllByAutomationId(a.getId());
if (conditions.size()
== conditions
.stream()
.filter(Condition::triggered)
.collect(Collectors.toList())
.size()) {
automationsWithMetConditions.add(a);
}
}
List<Trigger<? extends Device>> triggers = triggerRepository.findAllByDeviceId(deviceId);
// these are all the automations that are triggered, now I need to check if they are
// associated
// with some conditions, if they are I need to check that they are present also in the
// automationsToTrigger list.
// there are two cases:
// 1. this automation has been triggered and it is not connected to any condition, therefore
// this can be kept in the list
// 2. this automation has been triggered, it is connected to one or more conditions, but it
// is not present in the automationsToTrigger list, therefore it has to be discarded
List<Automation> triggeredAutomationsTMP =
triggers.stream() triggers.stream()
.filter(Trigger::triggered) .filter(Trigger::triggered)
.map(Trigger::getAutomationId) .map(Trigger::getAutomationId)
.map( .map(automationService::findByVerifiedId)
t ->
automationRepository
.findById(t)
.orElseThrow(IllegalStateException::new))
.distinct()
.collect(Collectors.toList());
List<Automation> triggeredAutomations = new ArrayList<>();
for (Automation a : triggeredAutomationsTMP) {
if (conditionRepository.findAllByAutomationId(a.getId()).size() > 0) {
if (automationsWithMetConditions.contains(a)) {
triggeredAutomations.add(a);
}
} else {
triggeredAutomations.add(a);
}
}
triggeredAutomations
.stream()
.distinct() .distinct()
.filter(
a -> {
final List<Condition<?>> conditions =
conditionRepository.findAllByAutomationId(a.getId());
if (conditions.size() == 0) return true;
return conditions.stream().allMatch(Condition::triggered);
})
.map(Automation::getScenes) .map(Automation::getScenes)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.distinct() .distinct()
.sorted(Comparator.comparing(ScenePriority::getPriority)) .sorted(Comparator.comparing(ScenePriority::getPriority))
.map( .map(t -> sceneService.findByValidatedId(t.getSceneId()))
t -> .forEach(s -> sceneService.apply(s, username, true));
sceneRepository
.findById(t.getSceneId())
.orElseThrow(IllegalStateException::new))
.forEach((s) -> sceneService.apply(s, username, true));
} }
public List<Device> findAll(Long hostId, String username) throws NotFoundException { public List<Device> findAll(Long hostId, String username) throws NotFoundException {
return findAll(null, hostId, username); return findAll(null, hostId, username);
} }
public <T extends Device> 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();
devicePropagationService.renameIfDuplicate(device, host.getUsername());
device = deviceRepository.save(device);
devicePropagationService.propagateUpdateAsGuest(device, host, currentUser);
return device;
}
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
devicePropagationService.deleteByIdAsOwner(id, username);
}
public void populateComputedFields(Iterable<Device> devices) {
devicePopulationService.populateComputedFields(devices);
}
/**
* Saves all the devices given in devices assuming that the owner updated them in one way or
* another. Takes care of the appropriate websocket updates and trigger checking as well. No
* checking is done to verify that the user whose username is given is in fact the owner of
* these devices
*
* @param devices the list of devices to save
* @param username the username of the owner of these devices
* @param fromScene true if the update comes from the a scene application side effect. Disables
* trigger checking to avoid recursive invocations of automations
* @param fromTrigger true if the update comes from a scene application executed by an
* automation. Propagates updated through socket to owner as well. No effect if fromScene is
* false.
* @param <T> the type of device contained in the list
* @return the updated list of devices, ready to be fed to GSON
*/
public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
List<T> toReturn =
devicePropagationService.saveAllAsOwner(devices, username, fromScene, fromTrigger);
if (!fromScene) {
toReturn.forEach(d -> this.triggerTriggers(d, username));
}
return toReturn;
}
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
return saveAllAsOwner(devices, username, false, false);
}
public <T extends Device> T saveAsOwner(T device, String username) {
T toReturn = devicePropagationService.saveAsOwner(device, username);
triggerTriggers(toReturn, username);
return device;
}
public List<Device> findAll(Long roomId, Long hostId, String username) public List<Device> findAll(Long roomId, Long hostId, String username)
throws NotFoundException { throws NotFoundException {
try {
Iterable<Device> devices; Iterable<Device> devices;
User host = null; User host = null;
if (hostId == null) { if (hostId == null) {
if (roomId != null) { if (roomId != null) {
roomRepository throwIfRoomNotOwned(roomId, username);
.findByIdAndUsername(roomId, username)
.orElseThrow(NotFoundException::new);
devices = deviceRepository.findByRoomId(roomId); devices = deviceRepository.findByRoomId(roomId);
} else { } else {
devices = deviceRepository.findAllByUsername(username); devices = deviceRepository.findAllByUsername(username);
@ -164,8 +168,12 @@ public class DeviceService {
} }
} }
populateComputedFields(devices); devicePopulationService.populateComputedFields(devices);
return filterOutCamerasIfNeeded(host, devices);
}
private List<Device> filterOutCamerasIfNeeded(User host, Iterable<Device> devices) {
if (host != null && !host.isCameraEnabled()) { if (host != null && !host.isCameraEnabled()) {
return StreamSupport.stream(devices.spliterator(), true) return StreamSupport.stream(devices.spliterator(), true)
.filter(d -> !(d instanceof SecurityCamera)) .filter(d -> !(d instanceof SecurityCamera))
@ -173,141 +181,5 @@ public class DeviceService {
} else { } else {
return toList(devices); return toList(devices);
} }
} catch (NotFoundException e) {
e.printStackTrace();
throw e;
}
}
public void populateComputedFields(Iterable<Device> devices) {
for (Device d : devices) {
if (d instanceof Thermostat) {
thermostatService.populateMeasuredTemperature((Thermostat) d);
}
}
}
public <T extends Device> T saveAsGuest(T device, String guestUsername, Long hostId)
throws NotFoundException {
final User currentUser = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new);
if (!host.getGuests().contains(currentUser)) {
throw new NotFoundException();
}
renameIfDuplicate(device, host.getUsername());
device = deviceRepository.save(device);
propagateUpdateAsGuest(device, host, currentUser);
return device;
}
private void propagateUpdateAsGuest(Device device, User host, User guest) {
final Set<User> guests = Set.copyOf(host.getGuests());
// We're telling the host that a guest has modified a device. Therefore, fromGuest becomes
// true.
// broadcast device update to host
endpoint.queueDeviceUpdate(device, host, true, null, false);
// We're telling all guests that a higher entity has issued a device update. Therefore,
// fromHost becomes true.
for (final User aGuest : guests) {
if (aGuest.equals(guest)) {
continue;
}
// enqueue all device updates for all other guests
endpoint.queueDeviceUpdate(device, aGuest, false, host.getId(), false);
}
}
List<Device> saveAllAsGuestSceneApplication(
List<Device> devices, String guestUsername, Long hostId) {
final User guest = userRepository.findByUsername(guestUsername);
final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
deviceRepository.saveAll(devices);
devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest));
return devices;
}
/**
* Propagates the update through the socket assuming that the user that modified the device is
* the owner of that device
*
* @param device the updated device
* @param username the username of the owner of that device
* @param causedByTrigger if true, send the update to the owner as well
*/
private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) {
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(device, guest, false, user.getId(), false);
}
if (causedByTrigger) {
endpoint.queueDeviceUpdate(device, user, false, user.getId(), false);
}
}
/**
* Saves all the devices given in devices assuming that the owner updated them in one way or
* another. Takes care of the appropriate websocket updates and trigger checking as well. No
* checking is done to verify that the user whose username is given is in fact the owner of
* these devices
*
* @param devices the list of devices to save
* @param username the username of the owner of these devices
* @param fromScene true if the update comes from the a scene application side effect. Disables
* trigger checking to avoid recursive invocations of automations
* @param fromTrigger true if the update comes from a scene application executed by an
* automation. Propagates updated through socket to owner as well. No effect if fromScene is
* false.
* @param <T> the type of device contained in the list
* @return the updated list of devices, ready to be fed to GSON
*/
public <T extends Device> List<T> saveAllAsOwner(
Iterable<T> devices, String username, boolean fromScene, boolean fromTrigger) {
devices.forEach(d -> renameIfDuplicate(d, username));
devices = deviceRepository.saveAll(devices);
devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger));
if (!fromScene) {
devices.forEach((d) -> triggerTriggers(d, username));
}
return toList(devices);
}
public <T extends Device> List<T> saveAllAsOwner(Iterable<T> devices, String username) {
return saveAllAsOwner(devices, username, false, false);
}
public <T extends Device> T saveAsOwner(T device, String username) {
renameIfDuplicate(device, username);
device = deviceRepository.save(device);
propagateUpdateAsOwner(device, username, false);
triggerTriggers(device, username);
return device;
}
public void deleteByIdAsOwner(Long id, String username) throws NotFoundException {
Device d =
deviceRepository
.findByIdAndUsername(id, username)
.orElseThrow(NotFoundException::new);
final User user = userRepository.findByUsername(username);
final Set<User> guests = user.getGuests();
// make sure we're broadcasting from host
for (final User guest : guests) {
// broadcast to endpoint the object device, with receiving user set to guest
endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true);
}
deviceRepository.delete(d);
} }
} }

View file

@ -1,9 +1,8 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.service; 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.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.*; import org.springframework.security.core.*;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@ -16,7 +15,7 @@ public class JWTUserDetailsService implements UserDetailsService {
@Autowired private UserRepository repository; @Autowired private UserRepository repository;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) {
User toReturn = repository.findByUsername(username); User toReturn = repository.findByUsername(username);
if (toReturn != null && toReturn.getEnabled()) { if (toReturn != null && toReturn.getEnabled()) {
return new org.springframework.security.core.userdetails.User( return new org.springframework.security.core.userdetails.User(

View file

@ -9,31 +9,48 @@ import org.springframework.stereotype.Component;
@Component @Component
public class SceneService { public class SceneService {
@Autowired private DeviceRepository<Device> deviceRepository; private final DevicePopulationService devicePopulationService;
@Autowired private DeviceService deviceService; private final DevicePropagationService devicePropagationService;
@Autowired private StateRepository<State<?>> stateRepository; private final StateRepository<State<?>> stateRepository;
private final SceneRepository sceneRepository;
public Scene findByValidatedId(Long id) {
return sceneRepository.findById(id).orElseThrow();
}
@Autowired
public SceneService(
DevicePopulationService devicePopulationService,
DevicePropagationService devicePropagationService,
StateRepository<State<?>> stateRepository,
SceneRepository sceneRepository) {
this.devicePopulationService = devicePopulationService;
this.devicePropagationService = devicePropagationService;
this.stateRepository = stateRepository;
this.sceneRepository = sceneRepository;
}
private List<Device> copyStatesToDevices(Scene fromScene) { private List<Device> copyStatesToDevices(Scene fromScene) {
final List<Device> updated = new ArrayList<>(); final List<Device> updated = new ArrayList<>(fromScene.getStates().size());
for (final State<?> s : fromScene.getStates()) { for (final State<?> s : fromScene.getStates()) {
s.apply(); s.apply();
updated.add(s.getDevice()); updated.add(s.getDevice());
} }
deviceService.populateComputedFields(updated); devicePopulationService.populateComputedFields(updated);
return updated; return updated;
} }
public List<Device> apply(Scene newScene, String username, boolean fromTrigger) { public List<Device> apply(Scene newScene, String username, boolean fromTrigger) {
List<Device> updated = copyStatesToDevices(newScene); List<Device> updated = copyStatesToDevices(newScene);
deviceService.saveAllAsOwner(updated, username, true, fromTrigger); devicePropagationService.saveAllAsOwner(updated, username, true, fromTrigger);
return updated; return updated;
} }
public List<Device> applyAsGuest(Scene newScene, String username, Long hostId) { public List<Device> applyAsGuest(Scene newScene, String username, Long hostId) {
List<Device> updated = copyStatesToDevices(newScene); List<Device> updated = copyStatesToDevices(newScene);
deviceService.saveAllAsGuestSceneApplication(updated, username, hostId); devicePropagationService.saveAllAsGuestSceneApplication(updated, username, hostId);
return updated; return updated;
} }

View file

@ -0,0 +1,34 @@
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 java.math.BigDecimal;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ThermostatPopulationService {
@Autowired private ThermostatRepository thermostatRepository;
private BigDecimal measureTemperature(final Thermostat thermostat) {
Optional<BigDecimal> average;
if (thermostat.isUseExternalSensors()) {
average =
thermostatRepository.getAverageTemperature(
thermostat.getRoomId(), Sensor.SensorType.TEMPERATURE);
} else {
return thermostat.getInternalSensorTemperature();
}
return average.orElse(null);
}
public void populateMeasuredTemperature(Thermostat thermostat) {
thermostat.setMeasuredTemperature(measureTemperature(thermostat));
}
}

View file

@ -18,6 +18,8 @@ public class ThermostatService {
@Autowired private DeviceService deviceService; @Autowired private DeviceService deviceService;
@Autowired private ThermostatPopulationService thermostatPopulationService;
@Autowired private ThermostatRepository thermostatRepository; @Autowired private ThermostatRepository thermostatRepository;
private void randomJitter(Thermostat thermostat) { private void randomJitter(Thermostat thermostat) {
@ -41,12 +43,12 @@ public class ThermostatService {
public List<Thermostat> findAll(String username) { public List<Thermostat> findAll(String username) {
Iterable<Thermostat> all = thermostatRepository.findAllByUsername(username); Iterable<Thermostat> all = thermostatRepository.findAllByUsername(username);
all.forEach(this::populateMeasuredTemperature); all.forEach(thermostatPopulationService::populateMeasuredTemperature);
return Utils.toList(all); return Utils.toList(all);
} }
public void computeState(Thermostat t) { public void computeState(Thermostat t) {
populateMeasuredTemperature(t); thermostatPopulationService.populateMeasuredTemperature(t);
t.computeState(); t.computeState();
} }
@ -67,25 +69,9 @@ public class ThermostatService {
if (t.isPresent()) { if (t.isPresent()) {
Thermostat u = t.get(); Thermostat u = t.get();
populateMeasuredTemperature(u); thermostatPopulationService.populateMeasuredTemperature(u);
t = Optional.of(u); t = Optional.of(u);
} }
return t; return t;
} }
private BigDecimal measureTemperature(final Thermostat thermostat) {
Optional<BigDecimal> 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));
}
} }

View file

@ -11,7 +11,7 @@ import org.springframework.web.socket.server.standard.ServerEndpointRegistration
@Configuration @Configuration
public class SensorSocketConfig extends ServerEndpointConfig.Configurator { public class SensorSocketConfig extends ServerEndpointConfig.Configurator {
private SensorSocketEndpoint instance; private final SensorSocketEndpoint instance;
@Autowired @Autowired
public SensorSocketConfig(SensorSocketEndpoint instance) { public SensorSocketConfig(SensorSocketEndpoint instance) {
@ -41,9 +41,8 @@ public class SensorSocketConfig extends ServerEndpointConfig.Configurator {
@Override @Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException { public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
try { try {
@SuppressWarnings("unchecked") //noinspection unchecked
final T instance = (T) this.instance; return (T) this.instance;
return instance;
} catch (ClassCastException e) { } catch (ClassCastException e) {
final var e2 = final var e2 =
new InstantiationException("Cannot cast SensorSocketEndpoint to desired type"); new InstantiationException("Cannot cast SensorSocketEndpoint to desired type");

View file

@ -5,7 +5,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DevicePopulationService;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
@ -13,6 +13,8 @@ import com.google.gson.Gson;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import javax.websocket.*; import javax.websocket.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,15 +22,17 @@ import org.springframework.stereotype.Component;
@Component @Component
public class SensorSocketEndpoint extends Endpoint { public class SensorSocketEndpoint extends Endpoint {
private Gson gson = GsonConfig.socketGson(); private static final Logger logger = LoggerFactory.getLogger(SensorSocketEndpoint.class);
@Autowired private DeviceService deviceService; private final Gson gson = GsonConfig.socketGson();
private UserRepository userRepository; @Autowired private DevicePopulationService deviceService;
private JWTTokenUtils jwtTokenUtils; private final UserRepository userRepository;
private Multimap<User, Session> authorizedClients = private final JWTTokenUtils jwtTokenUtils;
private final Multimap<User, Session> authorizedClients =
Multimaps.synchronizedMultimap(HashMultimap.create()); Multimaps.synchronizedMultimap(HashMultimap.create());
// messages are now stored as strings as a "hack" to capture and clone the state of the device, // messages are now stored as strings as a "hack" to capture and clone the state of the device,
@ -98,7 +102,7 @@ public class SensorSocketEndpoint extends Endpoint {
authorizedClients.remove(u, s); authorizedClients.remove(u, s);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.warn(e.getLocalizedMessage(), e);
} }
} }
} }
@ -118,7 +122,8 @@ public class SensorSocketEndpoint extends Endpoint {
} else { } else {
try { try {
session.close(); session.close();
} catch (IOException ignored) { } catch (IOException e) {
logger.warn(e.getLocalizedMessage(), e);
} }
} }
} }
@ -128,8 +133,8 @@ public class SensorSocketEndpoint extends Endpoint {
try { try {
username = jwtTokenUtils.getUsernameFromToken(protocolString); username = jwtTokenUtils.getUsernameFromToken(protocolString);
} catch (Throwable ignored) { } catch (Exception ignored) {
System.out.println("Token format not valid"); logger.info("Token format not valid");
return null; return null;
} }

View file

@ -33,3 +33,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here: email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=http://localhost:3000/password-reset?token= email.resetpasswordPath=http://localhost:3000/password-reset?token=
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"

View file

@ -40,3 +40,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here: email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=${FRONTEND_URL}/password-reset?token= email.resetpasswordPath=${FRONTEND_URL}/password-reset?token=
email.resetPasswordRedirect=${FRONTEND_URL}/conf-reset-pass email.resetPasswordRedirect=${FRONTEND_URL}/conf-reset-pass
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"

View file

@ -4,9 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
@ -42,10 +40,10 @@ public class AuthenticationTests extends SmartHutTest {
@Override @Override
protected void setUp() { protected void setUp() {
final ResponseEntity<OkResponse> res = final ResponseEntity<Object> res =
this.restTemplate.postForEntity( this.restTemplate.postForEntity(
this.url("/register"), getDisabledUser(), OkResponse.class); this.url("/register"), getDisabledUser(), Object.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
registerTestUser(restTemplate, userRepository, tokenRepository); registerTestUser(restTemplate, userRepository, tokenRepository);
} }
@ -58,7 +56,8 @@ public class AuthenticationTests extends SmartHutTest {
this.restTemplate this.restTemplate
.postForEntity(url("/register"), badJSON, JWTResponse.class) .postForEntity(url("/register"), badJSON, JWTResponse.class)
.getStatusCode() .getStatusCode()
.equals(HttpStatus.BAD_REQUEST)); .equals(HttpStatus.BAD_REQUEST))
.isTrue();
} }
@Test @Test
@ -71,12 +70,15 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<JsonObject> res = final ResponseEntity<JsonObject> res =
this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); this.restTemplate.postForEntity(url("/register"), request, JsonObject.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)).isTrue();
assertThat(res.getBody() != null); assertThat(res.getBody()).isNotNull();
final JsonArray errors = res.getBody().getAsJsonArray("errors"); final JsonArray errors = res.getBody().getAsJsonArray("errors");
assertThat(errors.size() == 1); assertThat(errors)
assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("password")); .allSatisfy(
e ->
assertThat(e.getAsJsonObject().get("field").getAsString())
.isEqualTo("password"));
} }
@Test @Test
@ -89,12 +91,15 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<JsonObject> res = final ResponseEntity<JsonObject> res =
this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); this.restTemplate.postForEntity(url("/register"), request, JsonObject.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)).isTrue();
assertThat(res.getBody() != null); assertThat(res.getBody()).isNotNull();
final JsonArray errors = res.getBody().getAsJsonArray("errors"); final JsonArray errors = res.getBody().getAsJsonArray("errors");
assertThat(errors.size() == 1); assertThat(errors)
assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("email")); .allSatisfy(
e ->
assertThat(e.getAsJsonObject().get("field").getAsString())
.isEqualTo("email"));
} }
@Test @Test
@ -106,12 +111,15 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<JsonObject> res = final ResponseEntity<JsonObject> res =
this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); this.restTemplate.postForEntity(url("/register"), request, JsonObject.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)).isTrue();
assertThat(res.getBody() != null); assertThat(res.getBody() != null).isTrue();
final JsonArray errors = res.getBody().getAsJsonArray("errors"); final JsonArray errors = res.getBody().getAsJsonArray("errors");
assertThat(errors.size() == 1); assertThat(errors)
assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("name")); .allSatisfy(
e ->
assertThat(e.getAsJsonObject().get("field").getAsString())
.isEqualTo("name"));
} }
@Test @Test
@ -123,51 +131,15 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<JsonObject> res = final ResponseEntity<JsonObject> res =
this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); this.restTemplate.postForEntity(url("/register"), request, JsonObject.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)).isTrue();
assertThat(res.getBody() != null); assertThat(res.getBody() != null).isTrue();
final JsonArray errors = res.getBody().getAsJsonArray("errors"); final JsonArray errors = res.getBody().getAsJsonArray("errors");
assertThat(errors.size() == 1); assertThat(errors)
assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("username")); .allSatisfy(
} j ->
assertThat(j.getAsJsonObject().get("field").getAsString())
@Test .isEqualTo("username"));
public void registrationShouldReturnBadRequestWithDuplicateData() {
{
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
getDisabledUser(),
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
{
final UserRegistrationRequest disabledUserDifferentMail = getDisabledUser();
enabledUser.setEmail("another@example.com");
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
disabledUserDifferentMail,
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
{
final UserRegistrationRequest disabledUserDifferentUsername = getDisabledUser();
enabledUser.setUsername("another");
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
disabledUserDifferentUsername,
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
} }
@Test @Test
@ -178,10 +150,9 @@ public class AuthenticationTests extends SmartHutTest {
request.setEmail("smarthut.sm@example.com"); request.setEmail("smarthut.sm@example.com");
request.setPassword("password"); request.setPassword("password");
final ResponseEntity<OkResponse> res = final ResponseEntity<Object> res =
this.restTemplate.postForEntity(url("/register"), request, OkResponse.class); this.restTemplate.postForEntity(url("/register"), request, Object.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(res.getBody() != null);
} }
@Test @Test
@ -192,7 +163,8 @@ public class AuthenticationTests extends SmartHutTest {
this.restTemplate this.restTemplate
.postForEntity(url("/auth/login"), badJSON, JWTResponse.class) .postForEntity(url("/auth/login"), badJSON, JWTResponse.class)
.getStatusCode() .getStatusCode()
.equals(HttpStatus.BAD_REQUEST)); .equals(HttpStatus.BAD_REQUEST))
.isTrue();
} }
@Test @Test
@ -204,9 +176,9 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<UnauthorizedException> res = final ResponseEntity<UnauthorizedException> res =
this.restTemplate.postForEntity( this.restTemplate.postForEntity(
url("/auth/login"), request, UnauthorizedException.class); url("/auth/login"), request, UnauthorizedException.class);
assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)).isTrue();
assertThat(res.getBody() != null); assertThat(res.getBody() != null).isTrue();
assertThat(!res.getBody().isUserDisabled()); assertThat(!res.getBody().isUserDisabled()).isTrue();
} }
@Test @Test
@ -218,22 +190,7 @@ public class AuthenticationTests extends SmartHutTest {
final ResponseEntity<UnauthorizedException> res = final ResponseEntity<UnauthorizedException> res =
this.restTemplate.postForEntity( this.restTemplate.postForEntity(
url("/auth/login"), request, UnauthorizedException.class); url("/auth/login"), request, UnauthorizedException.class);
assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); assertThat(res.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(res.getBody() != null); assertThat(res.getBody()).isNotNull();
assertThat(res.getBody().isUserDisabled());
}
@Test
public void loginShouldReturnTokenWithEnabledUser() {
final JWTRequest request = new JWTRequest();
request.setUsernameOrEmail("enabled");
request.setPassword("password");
final ResponseEntity<JWTResponse> res =
this.restTemplate.postForEntity(url("/auth/login"), request, JWTResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK));
assertThat(res.getBody() != null);
assertThat(res.getBody().getToken() != null);
assertThat(!res.getBody().getToken().isEmpty());
} }
} }

View file

@ -2,7 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse;
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationToken;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository;
@ -15,7 +14,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
public abstract class SmartHutTest { public abstract class SmartHutTest {
private boolean setupDone = false; private static boolean setupDone = false;
protected final String getBaseURL() { protected final String getBaseURL() {
return "http://localhost:2000/"; return "http://localhost:2000/";
@ -40,9 +39,9 @@ public abstract class SmartHutTest {
final TestRestTemplate restTemplate, final TestRestTemplate restTemplate,
final UserRepository userRepository, final UserRepository userRepository,
final ConfirmationTokenRepository tokenRepository) { final ConfirmationTokenRepository tokenRepository) {
final ResponseEntity<OkResponse> res2 = final ResponseEntity<Object> res2 =
restTemplate.postForEntity(this.url("/register"), enabledUser, OkResponse.class); restTemplate.postForEntity(this.url("/register"), enabledUser, Object.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK)); assertThat(res2.getStatusCode()).isEqualTo(HttpStatus.OK);
final User persistedEnabledUser = userRepository.findByUsername("enabled"); final User persistedEnabledUser = userRepository.findByUsername("enabled");
final ConfirmationToken token = tokenRepository.findByUser(persistedEnabledUser); final ConfirmationToken token = tokenRepository.findByUser(persistedEnabledUser);
@ -50,13 +49,14 @@ public abstract class SmartHutTest {
final ResponseEntity<Void> res3 = final ResponseEntity<Void> res3 =
WebClient.create(getBaseURL()) WebClient.create(getBaseURL())
.get() .get()
.uri("/register/confirm-account?token=" + token.getConfirmationToken()) .uri("/register/confirm-account?token=" + token.getConfirmToken())
.retrieve() .retrieve()
.toBodilessEntity() .toBodilessEntity()
.block(); .block();
assertThat(res3.getStatusCode().is2xxSuccessful()); assertThat(res3).isNotNull();
assertThat(userRepository.findByUsername("enabled").getEnabled()); assertThat(res3.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(userRepository.findByUsername("enabled").getEnabled()).isTrue();
} }
@BeforeEach @BeforeEach

View file

@ -16,11 +16,8 @@ public class SmarthutApplicationTests extends SmartHutTest {
@Autowired private TestRestTemplate restTemplate; @Autowired private TestRestTemplate restTemplate;
@Test @Test
public void anonymousGreetingShouldNotBeAuthorized() throws Exception { public void anonymousGreetingShouldNotBeAuthorized() {
assertThat( assertThat(this.restTemplate.getForEntity(getBaseURL(), Void.class).getStatusCode())
this.restTemplate .isEqualTo(HttpStatus.UNAUTHORIZED);
.getForEntity(getBaseURL(), Void.class)
.getStatusCode()
.equals(HttpStatus.UNAUTHORIZED));
} }
} }

View file

@ -35,3 +35,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
email.resetpassword=To reset your password, please click here: email.resetpassword=To reset your password, please click here:
email.resetpasswordPath=http://localhost:3000/password-reset?token= email.resetpasswordPath=http://localhost:3000/password-reset?token=
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"