diff --git a/build.gradle b/build.gradle index c55c8c1..52470bf 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'org.springframework.boot' version '2.2.4.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id "de.aaschmid.cpd" version "3.1" + id "org.sonarqube" version "2.7" id 'java' } group = 'ch.usi.inf.sa4.sanmarinoes' @@ -49,4 +50,4 @@ gradle.projectsEvaluated { test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5fc88e8 --- /dev/null +++ b/gradle.properties @@ -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 diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java index a26aeeb..a87d21f 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/EmailConfigurationService.java @@ -46,67 +46,67 @@ public class EmailConfigurationService { @NotNull private String registrationRedirect; - public String getRegistrationSubject() { + public synchronized String getRegistrationSubject() { return registrationSubject; } - public void setRegistrationSubject(String registrationSubject) { + public synchronized void setRegistrationSubject(String registrationSubject) { this.registrationSubject = registrationSubject; } - public String getRegistration() { + public synchronized String getRegistration() { return registration; } - public void setRegistration(String registration) { + public synchronized void setRegistration(String registration) { this.registration = registration; } - public String getRegistrationPath() { + public synchronized String getRegistrationPath() { return registrationPath; } - public void setRegistrationPath(String registrationPath) { + public synchronized void setRegistrationPath(String registrationPath) { this.registrationPath = registrationPath; } - public String getResetPasswordSubject() { + public synchronized String getResetPasswordSubject() { return resetPasswordSubject; } - public void setResetPasswordSubject(String resetPasswordSubject) { + public synchronized void setResetPasswordSubject(String resetPasswordSubject) { this.resetPasswordSubject = resetPasswordSubject; } - public String getResetPassword() { + public synchronized String getResetPassword() { return resetPassword; } - public void setResetPassword(String resetPassword) { + public synchronized void setResetPassword(String resetPassword) { this.resetPassword = resetPassword; } - public String getResetPasswordPath() { + public synchronized String getResetPasswordPath() { return resetPasswordPath; } - public void setResetPasswordPath(String resetPasswordPath) { + public synchronized void setResetPasswordPath(String resetPasswordPath) { this.resetPasswordPath = resetPasswordPath; } - public String getResetPasswordRedirect() { + public synchronized String getResetPasswordRedirect() { return resetPasswordRedirect; } - public void setResetPasswordRedirect(String resetPasswordRedirect) { + public synchronized void setResetPasswordRedirect(String resetPasswordRedirect) { this.resetPasswordRedirect = resetPasswordRedirect; } - public String getRegistrationRedirect() { + public synchronized String getRegistrationRedirect() { return registrationRedirect; } - public void setRegistrationRedirect(String registrationRedirect) { + public synchronized void setRegistrationRedirect(String registrationRedirect) { this.registrationRedirect = registrationRedirect; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java index e4a9107..dbe1331 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/RuntimeTypeAdapterFactory.java @@ -233,9 +233,9 @@ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { } final Map> labelToDelegate = - new LinkedHashMap>(); + new LinkedHashMap>(labelToSubtype.size()); final Map, TypeAdapter> subtypeToDelegate = - new LinkedHashMap, TypeAdapter>(); + new LinkedHashMap, TypeAdapter>(labelToSubtype.size()); for (Map.Entry> entry : labelToSubtype.entrySet()) { TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); @@ -245,7 +245,7 @@ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { return new TypeAdapter() { @Override - public R read(JsonReader in) throws IOException { + public R read(JsonReader in) { JsonElement jsonElement = Streams.parse(in); JsonElement labelJsonElement; if (maintainType) { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java index f806f01..7179130 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java @@ -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.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService; import java.security.Principal; import javax.validation.Valid; - -import ch.usi.inf.sa4.sanmarinoes.smarthut.service.JWTUserDetailsService; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; @RestController @@ -30,8 +28,6 @@ public class AuthenticationController { private final JWTUserDetailsService userDetailsService; - private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - public AuthenticationController( AuthenticationManager authenticationManager, UserRepository userRepository, @@ -82,9 +78,9 @@ public class AuthenticationController { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { - throw new UnauthorizedException(true); + throw new UnauthorizedException(true, e); } catch (BadCredentialsException e) { - throw new UnauthorizedException(false); + throw new UnauthorizedException(false, e); } } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java index 51ea46e..575d196 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java @@ -33,6 +33,22 @@ public abstract class InputDeviceConnectionController< this.input = input; this.outputs = outputs; } + + public I getInput() { + return input; + } + + public List getOutputs() { + return outputs; + } + } + + protected DeviceRepository getInputRepository() { + return inputRepository; + } + + protected DeviceRepository getOutputReposiory() { + return outputReposiory; } @Autowired private DeviceService deviceService; @@ -65,7 +81,7 @@ public abstract class InputDeviceConnectionController< inputRepository .findByIdAndUsername(inputId, username) .orElseThrow(() -> new NotFoundException("input device")); - final List outputDevices = new ArrayList<>(); + final List outputDevices = new ArrayList<>(outputs.size()); for (final Long outputId : outputs) { outputDevices.add( outputReposiory @@ -87,12 +103,12 @@ public abstract class InputDeviceConnectionController< Long inputId, List outputs, String username) throws NotFoundException { final Connection pair = checkConnectionIDs(inputId, outputs, username); - for (final O o : pair.outputs) { - connector.connect(pair.input, o, true); + for (final O o : pair.getOutputs()) { + connector.connect(pair.getInput(), o, true); } - deviceService.saveAllAsOwner(pair.outputs, username); - return pair.input.getOutputs(); + deviceService.saveAllAsOwner(pair.getOutputs(), username); + return pair.getInput().getOutputs(); } /** @@ -107,12 +123,12 @@ public abstract class InputDeviceConnectionController< Long inputId, List outputs, String username) throws NotFoundException { final Connection pair = checkConnectionIDs(inputId, outputs, username); - for (final O o : pair.outputs) { - connector.connect(pair.input, o, false); + for (final O o : pair.getOutputs()) { + connector.connect(pair.getInput(), o, false); } - deviceService.saveAllAsOwner(pair.outputs, username); - return pair.input.getOutputs(); + deviceService.saveAllAsOwner(pair.getOutputs(), username); + return pair.getInput().getOutputs(); } @PostMapping("/{id}/lights") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java index 1af7a99..f0900e4 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java @@ -17,14 +17,17 @@ import org.springframework.web.bind.annotation.*; @RequestMapping("/knobDimmer") public class KnobDimmerController extends InputDeviceConnectionController { - @Autowired private DeviceService deviceService; - @Autowired private KnobDimmerRepository knobDimmerRepository; + private final DeviceService deviceService; + private final KnobDimmerRepository knobDimmerRepository; @Autowired protected KnobDimmerController( - KnobDimmerRepository inputRepository, DimmableRepository outputRepository) { + KnobDimmerRepository inputRepository, + DimmableRepository outputRepository, + DeviceService deviceService) { super(inputRepository, outputRepository, Dimmable.KNOB_DIMMER_DIMMABLE_CONNECTOR); this.knobDimmerRepository = inputRepository; + this.deviceService = deviceService; } @GetMapping("/{id}") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java index d92cd49..96f5a52 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java @@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.*; public class SwitchController extends InputDeviceConnectionController { private SwitchRepository switchRepository; - private SwitchableRepository switchableRepository; private DeviceService deviceService; /** diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java index f56d792..49c31f2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ThermostatController.java @@ -36,8 +36,7 @@ public class ThermostatController { newT = thermostatRepository.save(newT); newT.setOn(t.isTurnOn()); - newT = deviceService.saveAsOwner(newT, principal.getName()); - return newT; + return deviceService.saveAsOwner(newT, principal.getName()); } @PostMapping diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java index 950fb1a..2432a13 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserAccountController.java @@ -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.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.UserRegistrationRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateRegistrationException; @@ -78,7 +77,7 @@ public class UserAccountController { * @throws DuplicateRegistrationException if a user exists with same email or username */ @PostMapping - public OkResponse registerUser(@Valid @RequestBody UserRegistrationRequest registrationData) + public void registerUser(@Valid @RequestBody UserRegistrationRequest registrationData) throws DuplicateRegistrationException { final User existingEmailUser = userRepository.findByEmailIgnoreCase(registrationData.getEmail()); @@ -112,8 +111,6 @@ public class UserAccountController { confirmationTokenRepository.save(token); sendEmail(toSave.getEmail(), token, true); - - return new OkResponse(); } } @@ -125,7 +122,7 @@ public class UserAccountController { * @throws UserNotFoundException if given email does not belong to any user */ @PostMapping("/init-reset-password") - public OkResponse initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest) + public void initResetPassword(@Valid @RequestBody InitPasswordResetRequest resetRequest) throws UserNotFoundException { final User toReset = userRepository.findByEmailIgnoreCase(resetRequest.getEmail()); @@ -148,8 +145,6 @@ public class UserAccountController { confirmationTokenRepository.save(token); sendEmail(toReset.getEmail(), token, false); - - return new OkResponse(); } /** @@ -160,7 +155,7 @@ public class UserAccountController { * @throws EmailTokenNotFoundException if given token is not a valid token for password reset */ @PutMapping("/reset-password") - public OkResponse resetPassword( + public void resetPassword( @Valid @RequestBody PasswordResetRequest resetRequest, final HttpServletResponse response) throws EmailTokenNotFoundException, IOException { @@ -178,8 +173,6 @@ public class UserAccountController { // Delete token to prevent further password changes confirmationTokenRepository.delete(token); - - return new OkResponse(); } /** diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/OkResponse.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/OkResponse.java deleted file mode 100644 index e3de94e..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/OkResponse.java +++ /dev/null @@ -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; -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java index 2ed2c09..86314a0 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/dto/UserResponse.java @@ -16,4 +16,16 @@ public class UserResponse { us.username = u.getUsername(); return us; } + + public Long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java index 9176df6..53b8620 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ResponseStatus; public class UnauthorizedException extends Exception { private final boolean isUserDisabled; - public UnauthorizedException(boolean isDisabled) { - super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials")); + public UnauthorizedException(boolean isDisabled, Throwable cause) { + super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials"), cause); this.isUserDisabled = isDisabled; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java index d324724..ac1f151 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/ConfirmationToken.java @@ -32,7 +32,7 @@ public class ConfirmationToken { private User user; @Column(nullable = false) - private Boolean resetPassword; + private boolean resetPassword; public ConfirmationToken(User user) { this.user = user; @@ -76,11 +76,11 @@ public class ConfirmationToken { this.user = user; } - public Boolean getResetPassword() { + public boolean getResetPassword() { return resetPassword; } - public void setResetPassword(Boolean resetPassword) { + public void setResetPassword(boolean resetPassword) { this.resetPassword = resetPassword; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java index 2fb5442..0124016 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Sensor.java @@ -15,9 +15,9 @@ public class Sensor extends InputDevice implements RangeTriggerable { public static final Map TYPICAL_VALUES = Map.of( - SensorType.TEMPERATURE, new BigDecimal(17.0), - SensorType.HUMIDITY, new BigDecimal(40.0), - SensorType.LIGHT, new BigDecimal(1000)); + SensorType.TEMPERATURE, BigDecimal.valueOf(17.0), + SensorType.HUMIDITY, BigDecimal.valueOf(40.0), + SensorType.LIGHT, BigDecimal.valueOf(1000)); @Override public double readTriggerState() { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java index 632dfd7..b6caf72 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Thermostat.java @@ -6,11 +6,9 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Transient; import javax.validation.constraints.NotNull; -import org.springframework.stereotype.Component; /** A thermostat capable of controlling cooling and heating. */ @Entity -@Component public class Thermostat extends Switchable implements BooleanTriggerable { @Override diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java index b58e383..61bdf6d 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java @@ -56,7 +56,7 @@ public class User { @Column(nullable = false) @GsonExclude - private Boolean isEnabled = false; + private boolean isEnabled = false; public Long getId() { return id; @@ -98,11 +98,11 @@ public class User { this.password = password; } - public Boolean getEnabled() { + public boolean getEnabled() { return isEnabled; } - public void setEnabled(Boolean enabled) { + public void setEnabled(boolean enabled) { isEnabled = enabled; } @@ -162,16 +162,17 @@ public class User { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; - return id.equals(user.id) - && name.equals(user.name) - && username.equals(user.username) - && password.equals(user.password) - && email.equals(user.email) - && isEnabled.equals(user.isEnabled); + return cameraEnabled == user.cameraEnabled + && isEnabled == user.isEnabled + && Objects.equals(id, user.id) + && Objects.equals(name, user.name) + && Objects.equals(username, user.username) + && Objects.equals(password, user.password) + && Objects.equals(email, user.email); } @Override public int hashCode() { - return Objects.hash(id, name, username, password, email, isEnabled); + return Objects.hash(id, name, username, password, email, isEnabled, cameraEnabled); } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePopulationService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePopulationService.java new file mode 100644 index 0000000..4d3d490 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePopulationService.java @@ -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 ThermostatService thermostatService; + + public void populateComputedFields(Iterable devices) { + for (Device d : devices) { + if (d instanceof Thermostat) { + thermostatService.populateMeasuredTemperature((Thermostat) d); + } + } + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePropagationService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePropagationService.java new file mode 100644 index 0000000..8df15ea --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DevicePropagationService.java @@ -0,0 +1,141 @@ +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 { + + @Autowired private SensorSocketEndpoint endpoint; + @Autowired private EagerUserRepository userRepository; + @Autowired private DeviceRepository deviceRepository; + + void propagateUpdateAsGuest(Device device, User host, User guest) { + final Set guests = Set.copyOf(host.getGuests()); + + // We're telling the host that a guest has modified a device. Therefore, fromGuest becomes + // true. + // 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 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 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 the type of device contained in the list + * @return the updated list of devices, ready to be fed to GSON + */ + public List saveAllAsOwner( + Iterable devices, String username, boolean fromScene, boolean fromTrigger) { + devices.forEach(d -> renameIfDuplicate(d, username)); + devices = deviceRepository.saveAll(devices); + devices.forEach((d) -> propagateUpdateAsOwner(d, username, fromScene && fromTrigger)); + + return toList(devices); + } + + public List saveAllAsOwner(Iterable devices, String username) { + return saveAllAsOwner(devices, username, false, false); + } + + public T saveAsOwner(T device, String username) { + renameIfDuplicate(device, username); + device = deviceRepository.save(device); + propagateUpdateAsOwner(device, username, 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 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 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); + } + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java index d2b02a6..b1a9c9e 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java @@ -4,11 +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.models.*; -import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.springframework.beans.factory.annotation.Autowired; @@ -17,28 +15,42 @@ import org.springframework.stereotype.Component; @Component public class DeviceService { - @Autowired private DeviceRepository deviceRepository; - @Autowired private AutomationRepository automationRepository; - @Autowired private SceneRepository sceneRepository; - @Autowired private SceneService sceneService; - @Autowired private TriggerRepository> triggerRepository; - @Autowired private RoomRepository roomRepository; - @Autowired private EagerUserRepository userRepository; - @Autowired private SensorSocketEndpoint endpoint; - @Autowired private ThermostatService thermostatService; + private final DeviceRepository deviceRepository; + private final AutomationRepository automationRepository; + private final SceneRepository sceneRepository; + private final SceneService sceneService; + private final TriggerRepository> triggerRepository; + private final RoomRepository roomRepository; + private final EagerUserRepository userRepository; + private final DevicePopulationService devicePopulationService; + private final DevicePropagationService devicePropagationService; + + @Autowired + public DeviceService( + DeviceRepository deviceRepository, + AutomationRepository automationRepository, + SceneRepository sceneRepository, + SceneService sceneService, + TriggerRepository> triggerRepository, + RoomRepository roomRepository, + EagerUserRepository userRepository, + DevicePopulationService devicePopulationService, + DevicePropagationService devicePropagationService) { + this.deviceRepository = deviceRepository; + this.automationRepository = automationRepository; + this.sceneRepository = sceneRepository; + this.sceneService = sceneService; + this.triggerRepository = triggerRepository; + this.roomRepository = roomRepository; + this.userRepository = userRepository; + this.devicePopulationService = devicePopulationService; + this.devicePropagationService = devicePropagationService; + } public void throwIfRoomNotOwned(Long roomId, String username) throws NotFoundException { roomRepository.findByIdAndUsername(roomId, username).orElseThrow(NotFoundException::new); } - private void renameIfDuplicate(Device toCreate, String username) { - while (deviceRepository.findDuplicates(toCreate.getName(), username) - - (toCreate.getId() <= 0 ? 0 : 1) - > 0) { - toCreate.setName(toCreate.getName() + " (new)"); - } - } - private void triggerTriggers(Device device, final String username) { final long deviceId = device.getId(); @@ -66,122 +78,24 @@ public class DeviceService { return findAll(null, hostId, username); } - public List findAll(Long roomId, Long hostId, String username) - throws NotFoundException { - try { - Iterable devices; - User host = null; - if (hostId == null) { - if (roomId != null) { - roomRepository - .findByIdAndUsername(roomId, username) - .orElseThrow(NotFoundException::new); - devices = deviceRepository.findByRoomId(roomId); - } else { - devices = deviceRepository.findAllByUsername(username); - } - } else { - final User guest = userRepository.findByUsername(username); - host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); - - if (!guest.getHosts().contains(host)) { - throw new NotFoundException(); - } - - if (roomId != null) { - Room r = roomRepository.findById(roomId).orElseThrow(NotFoundException::new); - if (!r.getUserId().equals(hostId)) { - throw new NotFoundException(); - } - devices = deviceRepository.findByRoomId(roomId); - } else { - devices = deviceRepository.findAllByUsername(host.getUsername()); - } - } - - populateComputedFields(devices); - - if (host != null && !host.isCameraEnabled()) { - return StreamSupport.stream(devices.spliterator(), true) - .filter(d -> !(d instanceof SecurityCamera)) - .collect(Collectors.toList()); - } else { - return toList(devices); - } - } catch (NotFoundException e) { - e.printStackTrace(); - throw e; - } - } - - public void populateComputedFields(Iterable devices) { - for (Device d : devices) { - if (d instanceof Thermostat) { - thermostatService.populateMeasuredTemperature((Thermostat) d); - } - } - } - public T saveAsGuest(T device, String guestUsername, Long hostId) throws NotFoundException { final User currentUser = userRepository.findByUsername(guestUsername); final User host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); if (!host.getGuests().contains(currentUser)) throw new NotFoundException(); - renameIfDuplicate(device, host.getUsername()); + devicePropagationService.renameIfDuplicate(device, host.getUsername()); device = deviceRepository.save(device); - propagateUpdateAsGuest(device, host, currentUser); + devicePropagationService.propagateUpdateAsGuest(device, host, currentUser); return device; } - private void propagateUpdateAsGuest(Device device, User host, User guest) { - final Set guests = Set.copyOf(host.getGuests()); - - // We're telling the host that a guest has modified a device. Therefore, fromGuest becomes - // true. - // 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); - } + public void deleteByIdAsOwner(Long id, String username) throws NotFoundException { + devicePropagationService.deleteByIdAsOwner(id, username); } - List saveAllAsGuestSceneApplication( - List devices, String guestUsername, Long hostId) { - final User guest = userRepository.findByUsername(guestUsername); - final User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new); - deviceRepository.saveAll(devices); - devices.forEach(d -> this.propagateUpdateAsGuest(d, host, guest)); - return devices; - } - - /** - * Propagates the update through the socket assuming that the user that modified the device is - * the owner of that device - * - * @param device the updated device - * @param username the username of the owner of that device - * @param causedByTrigger if true, send the update to the owner as well - */ - private void propagateUpdateAsOwner(Device device, String username, boolean causedByTrigger) { - final User user = userRepository.findByUsername(username); - final Set guests = user.getGuests(); - // make sure we're broadcasting from host - 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); - } + public void populateComputedFields(Iterable devices) { + devicePopulationService.populateComputedFields(devices); } /** @@ -202,15 +116,12 @@ public class DeviceService { */ public List saveAllAsOwner( Iterable 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)); - + List toReturn = + devicePropagationService.saveAllAsOwner(devices, username, fromScene, fromTrigger); if (!fromScene) { - devices.forEach((d) -> triggerTriggers(d, username)); + toReturn.forEach((d) -> this.triggerTriggers(d, username)); } - - return toList(devices); + return toReturn; } public List saveAllAsOwner(Iterable devices, String username) { @@ -218,29 +129,51 @@ public class DeviceService { } public T saveAsOwner(T device, String username) { - renameIfDuplicate(device, username); - device = deviceRepository.save(device); - propagateUpdateAsOwner(device, username, false); - - triggerTriggers(device, username); - + T toReturn = devicePropagationService.saveAsOwner(device, username); + triggerTriggers(toReturn, username); return device; } - public void deleteByIdAsOwner(Long id, String username) throws NotFoundException { - Device d = - deviceRepository - .findByIdAndUsername(id, username) + public List findAll(Long roomId, Long hostId, String username) + throws NotFoundException { + Iterable devices; + User host = null; + if (hostId == null) { + if (roomId != null) { + roomRepository + .findByIdAndUsername(roomId, username) .orElseThrow(NotFoundException::new); + devices = deviceRepository.findByRoomId(roomId); + } else { + devices = deviceRepository.findAllByUsername(username); + } + } else { + final User guest = userRepository.findByUsername(username); + host = userRepository.findById(hostId).orElseThrow(NotFoundException::new); - final User user = userRepository.findByUsername(username); - final Set guests = user.getGuests(); - // make sure we're broadcasting from host - for (final User guest : guests) { - // broadcast to endpoint the object device, with receiving user set to guest - endpoint.queueDeviceUpdate(d, guest, false, user.getId(), true); + if (!guest.getHosts().contains(host)) { + throw new NotFoundException(); + } + + if (roomId != null) { + Room r = roomRepository.findById(roomId).orElseThrow(NotFoundException::new); + if (!r.getUserId().equals(hostId)) { + throw new NotFoundException(); + } + devices = deviceRepository.findByRoomId(roomId); + } else { + devices = deviceRepository.findAllByUsername(host.getUsername()); + } } - deviceRepository.delete(d); + devicePopulationService.populateComputedFields(devices); + + if (host != null && !host.isCameraEnabled()) { + return StreamSupport.stream(devices.spliterator(), true) + .filter(d -> !(d instanceof SecurityCamera)) + .collect(Collectors.toList()); + } else { + return toList(devices); + } } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java index 3c91277..71bf6d1 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SceneService.java @@ -10,30 +10,31 @@ import org.springframework.stereotype.Component; public class SceneService { @Autowired private DeviceRepository deviceRepository; - @Autowired private DeviceService deviceService; + @Autowired private DevicePopulationService devicePopulationService; + @Autowired private DevicePropagationService devicePropagationService; @Autowired private StateRepository> stateRepository; private List copyStatesToDevices(Scene fromScene) { - final List updated = new ArrayList<>(); + final List updated = new ArrayList<>(fromScene.getStates().size()); for (final State s : fromScene.getStates()) { s.apply(); updated.add(s.getDevice()); } - deviceService.populateComputedFields(updated); + devicePopulationService.populateComputedFields(updated); return updated; } public List apply(Scene newScene, String username, boolean fromTrigger) { List updated = copyStatesToDevices(newScene); - deviceService.saveAllAsOwner(updated, username, true, fromTrigger); + devicePropagationService.saveAllAsOwner(updated, username, true, fromTrigger); return updated; } public List applyAsGuest(Scene newScene, String username, Long hostId) { List updated = copyStatesToDevices(newScene); - deviceService.saveAllAsGuestSceneApplication(updated, username, hostId); + devicePropagationService.saveAllAsGuestSceneApplication(updated, username, hostId); return updated; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java index 328d092..5cdee37 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/ThermostatService.java @@ -16,7 +16,7 @@ public class ThermostatService { @Autowired private SensorSocketEndpoint endpoint; - @Autowired private DeviceService deviceService; + @Autowired private DevicePropagationService deviceService; @Autowired private ThermostatRepository thermostatRepository; diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java index 4764f18..5cb3110 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java @@ -5,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.User; 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.Multimap; import com.google.common.collect.Multimaps; @@ -20,15 +20,15 @@ import org.springframework.stereotype.Component; @Component public class SensorSocketEndpoint extends Endpoint { - private Gson gson = GsonConfig.socketGson(); + private final Gson gson = GsonConfig.socketGson(); - @Autowired private DeviceService deviceService; + @Autowired private DevicePopulationService deviceService; - private UserRepository userRepository; + private final UserRepository userRepository; - private JWTTokenUtils jwtTokenUtils; + private final JWTTokenUtils jwtTokenUtils; - private Multimap authorizedClients = + private final Multimap authorizedClients = Multimaps.synchronizedMultimap(HashMultimap.create()); // messages are now stored as strings as a "hack" to capture and clone the state of the device, diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java index d13104f..5eac647 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java @@ -4,7 +4,6 @@ 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.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.error.DuplicateRegistrationException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; @@ -42,9 +41,9 @@ public class AuthenticationTests extends SmartHutTest { @Override protected void setUp() { - final ResponseEntity res = + final ResponseEntity res = this.restTemplate.postForEntity( - this.url("/register"), getDisabledUser(), OkResponse.class); + this.url("/register"), getDisabledUser(), Object.class); assertThat(res.getStatusCode().equals(HttpStatus.OK)); registerTestUser(restTemplate, userRepository, tokenRepository); @@ -178,8 +177,8 @@ public class AuthenticationTests extends SmartHutTest { request.setEmail("smarthut.sm@example.com"); request.setPassword("password"); - final ResponseEntity res = - this.restTemplate.postForEntity(url("/register"), request, OkResponse.class); + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, Object.class); assertThat(res.getStatusCode().equals(HttpStatus.OK)); assertThat(res.getBody() != null); } diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java index f2b737a..69a7ba1 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java @@ -2,7 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; 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.models.ConfirmationToken; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.ConfirmationTokenRepository; @@ -40,8 +39,8 @@ public abstract class SmartHutTest { final TestRestTemplate restTemplate, final UserRepository userRepository, final ConfirmationTokenRepository tokenRepository) { - final ResponseEntity res2 = - restTemplate.postForEntity(this.url("/register"), enabledUser, OkResponse.class); + final ResponseEntity res2 = + restTemplate.postForEntity(this.url("/register"), enabledUser, Object.class); assertThat(res2.getStatusCode().equals(HttpStatus.OK)); final User persistedEnabledUser = userRepository.findByUsername("enabled");