diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java index 67b25dc..08c3e60 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/GsonConfig.java @@ -23,15 +23,26 @@ public class GsonConfig { return converter; } - public static Gson gson() { + private static GsonBuilder configureBuilder() { final GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()); - builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy()); RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory.of(State.class, "kind") .registerSubtype(SwitchableState.class, "switchableState") .registerSubtype(DimmableState.class, "dimmableState"); builder.registerTypeAdapterFactory(runtimeTypeAdapterFactory); + return builder; + } + + public static Gson gson() { + final GsonBuilder builder = configureBuilder(); + builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy()); + return builder.create(); + } + + public static Gson socketGson() { + final GsonBuilder builder = configureBuilder(); + builder.addSerializationExclusionStrategy(new SocketAnnotationExclusionStrategy()); return builder.create(); } } @@ -56,3 +67,16 @@ class AnnotationExclusionStrategy implements ExclusionStrategy { return false; } } + +/** GSON exclusion strategy to exclude attributes with @SocketGsonExclude */ +class SocketAnnotationExclusionStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(SocketGsonExclude.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java new file mode 100644 index 0000000..e9806bc --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/SocketGsonExclude.java @@ -0,0 +1,10 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SocketGsonExclude {} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java index bde6bef..8c3265a 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/DimmableLightController.java @@ -6,6 +6,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.DimmableSaveRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import ch.usi.inf.sa4.sanmarinoes.smarthut.service.DeviceService; import java.security.Principal; import java.util.List; import javax.validation.Valid; @@ -19,37 +20,43 @@ import org.springframework.web.bind.annotation.*; public class DimmableLightController { @Autowired private UserRepository userRepository; - @Autowired private DimmableLightRepository dimmableLightService; + @Autowired private DimmableLightRepository dimmableLightRepository; @Autowired private SceneRepository sceneRepository; @Autowired private StateRepository> stateRepository; + @Autowired private DeviceService deviceService; @GetMapping public List findAll() { - return toList(dimmableLightService.findAll()); + return toList(dimmableLightRepository.findAll()); } @GetMapping("/{id}") public DimmableLight findById(@PathVariable("id") long id) throws NotFoundException { - return dimmableLightService.findById(id).orElseThrow(NotFoundException::new); + return dimmableLightRepository.findById(id).orElseThrow(NotFoundException::new); } - private DimmableLight save(DimmableLight initial, DimmableSaveRequest dl) { + private DimmableLight save(DimmableLight initial, DimmableSaveRequest dl, String username) { initial.setIntensity(dl.getIntensity()); initial.setName(dl.getName()); initial.setRoomId(dl.getRoomId()); - return dimmableLightService.save(initial); + return deviceService.saveAsOwner(initial, username); } + /* + Assume that only the host can create a device + Here save always as host, but remember to propagate change to guests (DeviceService.saveAsOwner()) + */ @PostMapping - public DimmableLight create(@Valid @RequestBody DimmableSaveRequest dl) { - return save(new DimmableLight(), dl); + public DimmableLight create( + @Valid @RequestBody DimmableSaveRequest dl, final Principal principal) { + return save(new DimmableLight(), dl, principal.getName()); } private DimmableLight fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId) throws NotFoundException { if (hostId == null) { - return dimmableLightService + return dimmableLightRepository .findByIdAndUsername(id, principal.getName()) .orElseThrow(NotFoundException::new); } else { @@ -57,11 +64,12 @@ public class DimmableLightController { * Slightly less extremely verbose check through various repositories to control user/guest authorization. */ DimmableLight dl = - dimmableLightService + dimmableLightRepository .findByIdAndUserId(id, hostId) .orElseThrow(NotFoundException::new); User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new); User guest = userRepository.findByUsername(principal.getName()); + dl.setFromHost(true); if (!host.getGuests().contains(guest)) { throw new NotFoundException(); } else { @@ -70,16 +78,21 @@ public class DimmableLightController { } } + /* + Here you must behave differently if hostId is given or not: + - if not given, assume the owner of the device wants to update the device. In this case, save with DeviceService.saveAsOwner(); + - if given, assume a guest wants to update the intensity of this light. In this case, save with DeviceService.saveAsGuest(); + */ @PutMapping public DimmableLight update( @Valid @RequestBody DimmableSaveRequest sp, final Principal principal, Long hostId) throws NotFoundException { - return save(fetchIfOwnerOrGuest(principal, sp.getId(), hostId), sp); + return save(fetchIfOwnerOrGuest(principal, sp.getId(), hostId), sp, principal.getName()); } @DeleteMapping("/{id}") public void delete(@PathVariable("id") long id) { - dimmableLightService.deleteById(id); + dimmableLightRepository.deleteById(id); } // the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId} @@ -92,7 +105,7 @@ public class DimmableLightController { throws NotFoundException, DuplicateStateException { DimmableLight d = - dimmableLightService + dimmableLightRepository .findByIdAndUsername(deviceId, principal.getName()) .orElseThrow(NotFoundException::new); State s = d.cloneState(); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java index 9b0cc2d..4629779 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java @@ -1,6 +1,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import ch.usi.inf.sa4.sanmarinoes.smarthut.config.GsonExclude; +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.SocketGsonExclude; import com.google.gson.annotations.SerializedName; import io.swagger.annotations.ApiModelProperty; import java.util.HashSet; @@ -32,6 +33,7 @@ public abstract class Device { @ManyToOne @JoinColumn(name = "room_id", updatable = false, insertable = false) @GsonExclude + @SocketGsonExclude private Room room; /** @@ -61,8 +63,15 @@ public abstract class Device { @OneToMany(mappedBy = "device", orphanRemoval = true) @GsonExclude + @SocketGsonExclude private Set> states = new HashSet<>(); + @Transient @GsonExclude private boolean fromHost = false; + + public void setFromHost(boolean fromHost) { + this.fromHost = fromHost; + } + public long getId() { return id; } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java new file mode 100644 index 0000000..a4502ec --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/service/DeviceService.java @@ -0,0 +1,35 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.service; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.Device; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.DeviceRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.User; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserRepository; +import ch.usi.inf.sa4.sanmarinoes.smarthut.socket.SensorSocketEndpoint; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class DeviceService { + // FIXME: TO BE MERGED WITH USER STORY 5 (MATTEO'S STUFF) + + @Autowired DeviceRepository deviceRepository; + @Autowired UserRepository userRepository; + @Autowired SensorSocketEndpoint endpoint; + + /* + TODO: remember to put a @Transient @GsonIgnore (but not @SocketGsonIgnore) property on device to signal a device update + TODO: coming from DeviceService.saveAsGuest() + */ + + // TODO: create saveAsGuest(device, guestUsername, hostId) + + public T saveAsOwner(T device, String username) { + final User user = userRepository.findByUsername(username); + final Set guests = user.getGuests(); + for (final User guest : guests) { + // set set from host true + // broadcast to endpoint the object device, with recieving user set to guest + } + } +} 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 b222cf5..b68cb25 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/socket/SensorSocketEndpoint.java @@ -9,25 +9,17 @@ 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 Gson gson = GsonConfig.socketGson(); private UserRepository userRepository; @@ -46,6 +38,7 @@ public class SensorSocketEndpoint extends Endpoint { /** * 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 */ @@ -56,9 +49,7 @@ public class SensorSocketEndpoint extends Endpoint { } } - /** - * Sends all device updates queued to be sent in a unique WebSocket message - */ + /** Sends all device updates queued to be sent in a unique WebSocket message */ public void flushDeviceUpdates() { synchronized (messages) { for (Map.Entry> batchForUser : messages.entrySet()) { @@ -69,11 +60,11 @@ public class SensorSocketEndpoint extends Endpoint { } /** - * Given a collection of messages and a user, broadcasts that message in json to all - * associated clients + * Given a collection of messages and a user, broadcasts that message in json to all associated + * clients * * @param messages the message batch to send - * @param u the user to which to send the message + * @param u the user to which to send the message */ private void broadcast(User u, Collection messages) { if (messages.isEmpty()) return; @@ -95,7 +86,7 @@ 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) {