Code review

This commit is contained in:
Claudio Maggioni (maggicl) 2020-04-21 14:57:11 +02:00
parent 6317ac99e4
commit eef0887da1
6 changed files with 113 additions and 31 deletions

View file

@ -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<State> 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;
}
}

View file

@ -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 {}

View file

@ -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<State<?>> stateRepository;
@Autowired private DeviceService deviceService;
@GetMapping
public List<DimmableLight> 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<? extends Dimmable> s = d.cloneState();

View file

@ -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<State<?>> states = new HashSet<>();
@Transient @GsonExclude private boolean fromHost = false;
public void setFromHost(boolean fromHost) {
this.fromHost = fromHost;
}
public long getId() {
return id;
}

View file

@ -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
}
}
}

View file

@ -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<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
* 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) {