Code review
This commit is contained in:
parent
6317ac99e4
commit
eef0887da1
6 changed files with 113 additions and 31 deletions
|
@ -23,15 +23,26 @@ public class GsonConfig {
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Gson gson() {
|
private static GsonBuilder configureBuilder() {
|
||||||
final GsonBuilder builder = new GsonBuilder();
|
final GsonBuilder builder = new GsonBuilder();
|
||||||
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
|
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
|
||||||
builder.addSerializationExclusionStrategy(new AnnotationExclusionStrategy());
|
|
||||||
RuntimeTypeAdapterFactory<State> runtimeTypeAdapterFactory =
|
RuntimeTypeAdapterFactory<State> runtimeTypeAdapterFactory =
|
||||||
RuntimeTypeAdapterFactory.of(State.class, "kind")
|
RuntimeTypeAdapterFactory.of(State.class, "kind")
|
||||||
.registerSubtype(SwitchableState.class, "switchableState")
|
.registerSubtype(SwitchableState.class, "switchableState")
|
||||||
.registerSubtype(DimmableState.class, "dimmableState");
|
.registerSubtype(DimmableState.class, "dimmableState");
|
||||||
builder.registerTypeAdapterFactory(runtimeTypeAdapterFactory);
|
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();
|
return builder.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,3 +67,16 @@ class AnnotationExclusionStrategy implements ExclusionStrategy {
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {}
|
|
@ -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.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 java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
@ -19,37 +20,43 @@ import org.springframework.web.bind.annotation.*;
|
||||||
public class DimmableLightController {
|
public class DimmableLightController {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
@Autowired private UserRepository userRepository;
|
||||||
@Autowired private DimmableLightRepository dimmableLightService;
|
@Autowired private DimmableLightRepository dimmableLightRepository;
|
||||||
@Autowired private SceneRepository sceneRepository;
|
@Autowired private SceneRepository sceneRepository;
|
||||||
@Autowired private StateRepository<State<?>> stateRepository;
|
@Autowired private StateRepository<State<?>> stateRepository;
|
||||||
|
@Autowired private DeviceService deviceService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<DimmableLight> findAll() {
|
public List<DimmableLight> findAll() {
|
||||||
return toList(dimmableLightService.findAll());
|
return toList(dimmableLightRepository.findAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public DimmableLight findById(@PathVariable("id") long id) throws NotFoundException {
|
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.setIntensity(dl.getIntensity());
|
||||||
initial.setName(dl.getName());
|
initial.setName(dl.getName());
|
||||||
initial.setRoomId(dl.getRoomId());
|
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
|
@PostMapping
|
||||||
public DimmableLight create(@Valid @RequestBody DimmableSaveRequest dl) {
|
public DimmableLight create(
|
||||||
return save(new DimmableLight(), dl);
|
@Valid @RequestBody DimmableSaveRequest dl, final Principal principal) {
|
||||||
|
return save(new DimmableLight(), dl, principal.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DimmableLight fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId)
|
private DimmableLight fetchIfOwnerOrGuest(final Principal principal, Long id, Long hostId)
|
||||||
throws NotFoundException {
|
throws NotFoundException {
|
||||||
if (hostId == null) {
|
if (hostId == null) {
|
||||||
return dimmableLightService
|
return dimmableLightRepository
|
||||||
.findByIdAndUsername(id, principal.getName())
|
.findByIdAndUsername(id, principal.getName())
|
||||||
.orElseThrow(NotFoundException::new);
|
.orElseThrow(NotFoundException::new);
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,11 +64,12 @@ public class DimmableLightController {
|
||||||
* Slightly less extremely verbose check through various repositories to control user/guest authorization.
|
* Slightly less extremely verbose check through various repositories to control user/guest authorization.
|
||||||
*/
|
*/
|
||||||
DimmableLight dl =
|
DimmableLight dl =
|
||||||
dimmableLightService
|
dimmableLightRepository
|
||||||
.findByIdAndUserId(id, hostId)
|
.findByIdAndUserId(id, hostId)
|
||||||
.orElseThrow(NotFoundException::new);
|
.orElseThrow(NotFoundException::new);
|
||||||
User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
|
User host = userRepository.findById(hostId).orElseThrow(IllegalStateException::new);
|
||||||
User guest = userRepository.findByUsername(principal.getName());
|
User guest = userRepository.findByUsername(principal.getName());
|
||||||
|
dl.setFromHost(true);
|
||||||
if (!host.getGuests().contains(guest)) {
|
if (!host.getGuests().contains(guest)) {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
} else {
|
} 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
|
@PutMapping
|
||||||
public DimmableLight update(
|
public DimmableLight update(
|
||||||
@Valid @RequestBody DimmableSaveRequest sp, final Principal principal, Long hostId)
|
@Valid @RequestBody DimmableSaveRequest sp, final Principal principal, Long hostId)
|
||||||
throws NotFoundException {
|
throws NotFoundException {
|
||||||
return save(fetchIfOwnerOrGuest(principal, sp.getId(), hostId), sp);
|
return save(fetchIfOwnerOrGuest(principal, sp.getId(), hostId), sp, principal.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public void delete(@PathVariable("id") long id) {
|
public void delete(@PathVariable("id") long id) {
|
||||||
dimmableLightService.deleteById(id);
|
dimmableLightRepository.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId}
|
// the full url should be: "/dimmableLight/{id}/state?sceneId={sceneId}
|
||||||
|
@ -92,7 +105,7 @@ public class DimmableLightController {
|
||||||
throws NotFoundException, DuplicateStateException {
|
throws NotFoundException, DuplicateStateException {
|
||||||
|
|
||||||
DimmableLight d =
|
DimmableLight d =
|
||||||
dimmableLightService
|
dimmableLightRepository
|
||||||
.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();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
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 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.HashSet;
|
||||||
|
@ -32,6 +33,7 @@ public abstract class Device {
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "room_id", updatable = false, insertable = false)
|
@JoinColumn(name = "room_id", updatable = false, insertable = false)
|
||||||
@GsonExclude
|
@GsonExclude
|
||||||
|
@SocketGsonExclude
|
||||||
private Room room;
|
private Room room;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,8 +63,15 @@ public abstract class Device {
|
||||||
|
|
||||||
@OneToMany(mappedBy = "device", orphanRemoval = true)
|
@OneToMany(mappedBy = "device", orphanRemoval = true)
|
||||||
@GsonExclude
|
@GsonExclude
|
||||||
|
@SocketGsonExclude
|
||||||
private Set<State<?>> states = new HashSet<>();
|
private Set<State<?>> states = new HashSet<>();
|
||||||
|
|
||||||
|
@Transient @GsonExclude private boolean fromHost = false;
|
||||||
|
|
||||||
|
public void setFromHost(boolean fromHost) {
|
||||||
|
this.fromHost = fromHost;
|
||||||
|
}
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Device> 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 extends Device> T saveAsOwner(T device, String username) {
|
||||||
|
final User user = userRepository.findByUsername(username);
|
||||||
|
final Set<User> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,25 +9,17 @@ 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;
|
||||||
import com.google.gson.Gson;
|
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 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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.parameters.P;
|
|
||||||
import org.springframework.stereotype.Component;
|
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
|
@Component
|
||||||
public class SensorSocketEndpoint extends Endpoint {
|
public class SensorSocketEndpoint extends Endpoint {
|
||||||
|
|
||||||
private Gson gson = GsonConfig.gson();
|
private Gson gson = GsonConfig.socketGson();
|
||||||
|
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@ -46,6 +38,7 @@ public class SensorSocketEndpoint extends Endpoint {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues a single device update for a certain user to be sent
|
* Queues a single device update for a certain user to be sent
|
||||||
|
*
|
||||||
* @param device the device update to be sent
|
* @param device the device update to be sent
|
||||||
* @param u the user the device belongs
|
* @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() {
|
public void flushDeviceUpdates() {
|
||||||
synchronized (messages) {
|
synchronized (messages) {
|
||||||
for (Map.Entry<User, Map<Long, Device>> batchForUser : messages.entrySet()) {
|
for (Map.Entry<User, Map<Long, Device>> 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
|
* Given a collection of messages and a user, broadcasts that message in json to all associated
|
||||||
* associated clients
|
* clients
|
||||||
*
|
*
|
||||||
* @param messages the message batch to send
|
* @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) {
|
private void broadcast(User u, Collection<?> messages) {
|
||||||
if (messages.isEmpty()) return;
|
if (messages.isEmpty()) return;
|
||||||
|
@ -95,7 +86,7 @@ public class SensorSocketEndpoint extends Endpoint {
|
||||||
* Handles the opening of a socket session with a client
|
* Handles the opening of a socket session with a client
|
||||||
*
|
*
|
||||||
* @param session the newly born session
|
* @param session the newly born session
|
||||||
* @param config endpoint configuration
|
* @param config endpoint configuration
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(Session session, EndpointConfig config) {
|
public void onOpen(Session session, EndpointConfig config) {
|
||||||
|
|
Loading…
Reference in a new issue