diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a2bf131..b09929f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Sun Apr 12 12:33:03 CEST 2020
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/socket_test.html b/socket_test.html
index 687388b..91399e5 100644
--- a/socket_test.html
+++ b/socket_test.html
@@ -1,40 +1,43 @@
-
+
-
-
-
-
Waiting for authentication...
-
-
-
+
+
+
+
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java
index 944bd8e..ed517f8 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/ButtonDimmerController.java
@@ -77,14 +77,14 @@ public class ButtonDimmerController
@PostMapping("/{id}/lights")
public Set extends OutputDevice> addLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
+ @PathVariable("id") long inputId, @RequestBody List lightId)
throws NotFoundException {
return addOutput(inputId, lightId);
}
@DeleteMapping("/{id}/lights")
public Set extends OutputDevice> removeLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
+ @PathVariable("id") long inputId, @RequestBody List lightId)
throws NotFoundException {
return removeOutput(inputId, lightId);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/InputDeviceConnectionController.java
index 52f3483..ca2997a 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
@@ -2,6 +2,8 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/**
@@ -14,11 +16,11 @@ import java.util.Set;
public abstract class InputDeviceConnectionController<
I extends InputDevice, O extends OutputDevice> {
- private class IOPair {
+ private class Connection {
private final I input;
- private final O output;
+ private final List output;
- private IOPair(I input, O output) {
+ private Connection(I input, List output) {
this.input = input;
this.output = output;
}
@@ -46,31 +48,39 @@ public abstract class InputDeviceConnectionController<
this.connector = connector;
}
- private IOPair checkConnectionIDs(Long inputId, Long outputId) throws NotFoundException {
+ private Connection checkConnectionIDs(Long inputId, List outputs)
+ throws NotFoundException {
final I input =
inputRepository
.findById(inputId)
.orElseThrow(() -> new NotFoundException("input device"));
- final O output =
- outputReposiory
- .findById(outputId)
- .orElseThrow(() -> new NotFoundException("output device"));
- return new IOPair(input, output);
+ final List outputDevices = new ArrayList<>();
+ for (final Long outputId : outputs) {
+ outputDevices.add(
+ outputReposiory
+ .findById(outputId)
+ .orElseThrow(() -> new NotFoundException("output device")));
+ }
+ return new Connection(input, outputDevices);
}
/**
* Implements the output device connection creation (add) route
*
* @param inputId input device id
- * @param outputId output device id
+ * @param outputId output device id list
* @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid
*/
- protected Set extends OutputDevice> addOutput(Long inputId, Long outputId)
+ protected Set extends OutputDevice> addOutput(Long inputId, List outputId)
throws NotFoundException {
- final IOPair pair = checkConnectionIDs(inputId, outputId);
- connector.connect(pair.input, pair.output, true);
- outputReposiory.save(pair.output);
+ final Connection pair = checkConnectionIDs(inputId, outputId);
+
+ for (final O o : pair.output) {
+ connector.connect(pair.input, o, true);
+ }
+
+ outputReposiory.saveAll(pair.output);
return pair.input.getOutputs();
}
@@ -78,15 +88,19 @@ public abstract class InputDeviceConnectionController<
* Implements the output device connection destruction (remove) route
*
* @param inputId input device id
- * @param outputId output device id
+ * @param outputId output device id list
* @return the list of output devices attached to the input device of id inputId
* @throws NotFoundException if inputId or outputId are not valid
*/
- protected Set extends OutputDevice> removeOutput(Long inputId, Long outputId)
+ protected Set extends OutputDevice> removeOutput(Long inputId, List outputId)
throws NotFoundException {
- final IOPair pair = checkConnectionIDs(inputId, outputId);
- connector.connect(pair.input, pair.output, false);
- outputReposiory.save(pair.output);
+ final Connection pair = checkConnectionIDs(inputId, outputId);
+
+ for (final O o : pair.output) {
+ connector.connect(pair.input, o, false);
+ }
+
+ outputReposiory.saveAll(pair.output);
return pair.input.getOutputs();
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/KnobDimmerController.java
index c15d867..a259d98 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
@@ -70,14 +70,14 @@ public class KnobDimmerController
@PostMapping("/{id}/lights")
public Set extends OutputDevice> addLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
+ @PathVariable("id") long inputId, @RequestBody List lightId)
throws NotFoundException {
return addOutput(inputId, lightId);
}
@DeleteMapping("/{id}/lights")
public Set extends OutputDevice> removeLight(
- @PathVariable("id") long inputId, @RequestParam("lightId") Long lightId)
+ @PathVariable("id") long inputId, @RequestBody List lightId)
throws NotFoundException {
return removeOutput(inputId, lightId);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
index 59c0343..c349aa2 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/MotionSensorController.java
@@ -53,7 +53,7 @@ public class MotionSensorController {
sensor.setDetected(detected);
final MotionSensor toReturn = motionSensorService.save(sensor);
- sensorSocketEndpoint.broadcast(sensor, motionSensorService.findUser(sensor.getId()));
+ sensorSocketEndpoint.queueDeviceUpdate(sensor, motionSensorService.findUser(sensor.getId()));
return toReturn;
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/SwitchController.java
index fc64ccb..5794a01 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
@@ -83,14 +83,14 @@ public class SwitchController extends InputDeviceConnectionController addSwitchable(
- @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId)
+ @PathVariable("id") long inputId, @RequestBody List switchableId)
throws NotFoundException {
return addOutput(inputId, switchableId);
}
@DeleteMapping("/{id}/lights")
public Set extends OutputDevice> removeSwitchable(
- @PathVariable("id") long inputId, @RequestParam("switchableId") Long switchableId)
+ @PathVariable("id") long inputId, @RequestBody List switchableId)
throws NotFoundException {
return removeOutput(inputId, switchableId);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
index c116165..dc39404 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/KnobDimmer.java
@@ -1,5 +1,6 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
+import javax.persistence.Column;
import javax.persistence.Entity;
/**
@@ -9,6 +10,9 @@ import javax.persistence.Entity;
@Entity
public class KnobDimmer extends Dimmer {
+ @Column
+ Integer intensity = 0;
+
public KnobDimmer() {
super("knobDimmer");
}
@@ -19,6 +23,7 @@ public class KnobDimmer extends Dimmer {
* @param intensity the intensity (must be from 0 to 100)
*/
public void setLightIntensity(int intensity) {
+ this.intensity = intensity;
for (DimmableLight dl : getOutputs()) {
dl.setIntensity(intensity);
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java
index 0f4bea5..e902662 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/scheduled/UpdateTasks.java
@@ -64,6 +64,12 @@ public class UpdateTasks {
public void smartPlugConsumptionFakeUpdate() {
smartPlugRepository.updateTotalConsumption(SmartPlug.AVERAGE_CONSUMPTION_KW);
final Collection c = smartPlugRepository.findByOn(true);
- c.forEach(s -> sensorSocketEndpoint.broadcast(s, sensorRepository.findUser(s.getId())));
+ c.forEach(s -> sensorSocketEndpoint.queueDeviceUpdate(s, sensorRepository.findUser(s.getId())));
+ }
+
+ /** Sends device updates through sensor socket in batch every one second */
+ @Scheduled(fixedDelay = 1000)
+ public void socketFlush() {
+ sensorSocketEndpoint.flushDeviceUpdates();
}
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java
index b9c8fae..cc1224a 100644
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java
+++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/SensorService.java
@@ -45,7 +45,7 @@ public class SensorService {
sensor.setValue(value);
final Sensor toReturn = sensorRepository.save(sensor);
- endpoint.broadcast(sensor, sensorRepository.findUser(sensor.getId()));
+ endpoint.queueDeviceUpdate(sensor, sensorRepository.findUser(sensor.getId()));
return toReturn;
}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
deleted file mode 100644
index 89415aa..0000000
--- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/AuthenticationMessageListener.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
-
-import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
-import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import io.jsonwebtoken.ExpiredJwtException;
-import java.io.IOException;
-import java.util.Map;
-import java.util.function.BiConsumer;
-import javax.websocket.MessageHandler;
-import javax.websocket.Session;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-/** Generates MessageHandlers for unauthenticated socket sessions */
-@Component
-public class AuthenticationMessageListener {
-
- private Gson gson = GsonConfig.gson();
-
- private JWTTokenUtils jwtTokenUtils;
-
- private UserRepository userRepository;
-
- @Autowired
- public AuthenticationMessageListener(
- JWTTokenUtils jwtTokenUtils, UserRepository userRepository) {
- this.jwtTokenUtils = jwtTokenUtils;
- this.userRepository = userRepository;
- }
-
- /**
- * Generates a new message handler to handle socket authentication
- *
- * @param session the session to which authentication must be checked
- * @param authorizedSetter function to call once user is authenticated
- * @return a new message handler to handle socket authentication
- */
- MessageHandler.Whole newHandler(
- final Session session, BiConsumer authorizedSetter) {
- return new MessageHandler.Whole<>() {
- @Override
- public void onMessage(final String message) {
- if (message == null) {
- acknowledge(false);
- return;
- }
-
- String token;
- String username;
-
- try {
- token = gson.fromJson(message, JsonObject.class).get("token").getAsString();
- username = jwtTokenUtils.getUsernameFromToken(token);
- } catch (ExpiredJwtException e) {
- System.err.println(e.getMessage());
- acknowledge(false);
- return;
- } catch (Throwable ignored) {
- System.out.println("Token format not valid");
- acknowledge(false);
- return;
- }
-
- final User user = userRepository.findByUsername(username);
- if (user == null || jwtTokenUtils.isTokenExpired(token)) {
- System.out.println("Token not valid");
- acknowledge(false);
- return;
- }
-
- // Here user is authenticated
- session.removeMessageHandler(this);
-
- // Add user-session pair in authorized list
- authorizedSetter.accept(user, session);
-
- // update client to acknowledge authentication
- acknowledge(true);
- }
-
- private void acknowledge(boolean success) {
- try {
- session.getBasicRemote()
- .sendText(gson.toJson(Map.of("authenticated", success)));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- };
- }
-}
diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java
index 600bdf4..655a773 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
@@ -2,67 +2,87 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.socket;
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonConfig;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtils;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device;
import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User;
+import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.gson.Gson;
+
import java.io.IOException;
import java.util.*;
import javax.websocket.*;
+
+import com.google.gson.JsonObject;
+import io.jsonwebtoken.ExpiredJwtException;
+import org.hibernate.annotations.common.reflection.XProperty;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Component;
-/** Endpoint of socket at URL /sensor-socket used to update the client with sensor information */
+/**
+ * Endpoint of socket at URL /sensor-socket used to update the client with sensor information
+ */
@Component
public class SensorSocketEndpoint extends Endpoint {
private Gson gson = GsonConfig.gson();
- private AuthenticationMessageListener authenticationMessageListener;
+ private UserRepository userRepository;
- private Set unauthorizedClients = Collections.synchronizedSet(new HashSet<>());
+ private JWTTokenUtils jwtTokenUtils;
private Multimap authorizedClients =
Multimaps.synchronizedMultimap(HashMultimap.create());
+ private final Map> messages = new HashMap<>();
+
@Autowired
- public SensorSocketEndpoint(AuthenticationMessageListener authenticationMessageListener) {
- this.authenticationMessageListener = authenticationMessageListener;
+ public SensorSocketEndpoint(UserRepository userRepository, JWTTokenUtils jwtTokenUtils) {
+ this.jwtTokenUtils = jwtTokenUtils;
+ this.userRepository = userRepository;
}
/**
- * Returns a synchronized set of socket sessions not yet authorized with a token
- *
- * @return a synchronized set of socket sessions not yet authorized with a token
+ * Queues a single device update for a certain user to be sent
+ * @param device the device update to be sent
+ * @param u the user the device belongs
*/
- public Set getUnauthorizedClients() {
- return unauthorizedClients;
+ public void queueDeviceUpdate(Device device, User u) {
+ synchronized (messages) {
+ messages.putIfAbsent(u, new HashMap<>());
+ messages.get(u).put(device.getId(), device);
+ }
}
/**
- * Returns a synchronized User to Session multimap with authorized sessions
- *
- * @return a synchronized User to Session multimap with authorized sessions
+ * Sends all device updates queued to be sent in a unique WebSocket message
*/
- public Multimap getAuthorizedClients() {
- return authorizedClients;
+ public void flushDeviceUpdates() {
+ synchronized (messages) {
+ for (Map.Entry> batchForUser : messages.entrySet()) {
+ broadcast(batchForUser.getKey(), batchForUser.getValue().values());
+ batchForUser.getValue().clear();
+ }
+ }
}
/**
- * Given a message and a user, broadcasts that message in json to all associated clients and
- * returns the number of successful transfers
+ * Given a collection of messages and a user, broadcasts that message in json to all
+ * associated clients
*
- * @param message the message to send
- * @param u the user to which to send the message
- * @return number of successful transfer
+ * @param messages the message batch to send
+ * @param u the user to which to send the message
*/
- public void broadcast(Object message, User u) {
+ private void broadcast(User u, Collection> messages) {
+ if (messages.isEmpty()) return;
final HashSet sessions = new HashSet<>(authorizedClients.get(u));
for (Session s : sessions) {
try {
if (s.isOpen()) {
- s.getBasicRemote().sendText(gson.toJson(message));
+ s.getBasicRemote().sendText(gson.toJson(messages));
} else {
authorizedClients.remove(u, s);
}
@@ -76,17 +96,37 @@ public class SensorSocketEndpoint extends Endpoint {
* Handles the opening of a socket session with a client
*
* @param session the newly born session
- * @param config endpoint configuration
+ * @param config endpoint configuration
*/
@Override
public void onOpen(Session session, EndpointConfig config) {
- unauthorizedClients.add(session);
- session.addMessageHandler(
- authenticationMessageListener.newHandler(
- session,
- (u, s) -> {
- unauthorizedClients.remove(s);
- authorizedClients.put(u, s);
- }));
+ final List tokenQuery = session.getRequestParameterMap().get("token");
+ User u;
+ if (!tokenQuery.isEmpty() && (u = checkToken(tokenQuery.get(0))) != null) {
+ authorizedClients.put(u, session);
+ } else {
+ try {
+ session.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ private User checkToken(String protocolString) {
+ String username;
+
+ try {
+ username = jwtTokenUtils.getUsernameFromToken(protocolString);
+ } catch (Throwable ignored) {
+ System.out.println("Token format not valid");
+ return null;
+ }
+
+ final User user = userRepository.findByUsername(username);
+ if (user != null && !jwtTokenUtils.isTokenExpired(protocolString)) {
+ return user;
+ } else {
+ return null;
+ }
}
}