Skip to content

Commit

Permalink
Merge pull request #126 from RADAR-CNS/fix-dynamic-source-registration
Browse files Browse the repository at this point in the history
Fixes for dynamic source registration
  • Loading branch information
dennyverbeeck authored Nov 11, 2017
2 parents 00c20e2 + cdb856b commit 03fada4
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 44 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/radarcns/management/domain/Source.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public void generateUuid() {
this.sourceId = UUID.randomUUID();
}
if(this.sourceName == null) {
this.sourceName = this.sourceId.toString().substring(0,8);
this.sourceName = String.join("-", this.getDeviceType().getDeviceModel(),
this.sourceId.toString().substring(0,8));
}
}
public String getDeviceCategory() {
Expand Down
19 changes: 10 additions & 9 deletions src/main/java/org/radarcns/management/service/SubjectService.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -235,17 +236,17 @@ else if (deviceType.getCanRegisterDynamically()) {
// create a source and register meta data
// we allow only one source of a device-type per subject
if (sources.isEmpty()) {
Source source1 = new Source();
source1.setProject(project);
source1.setAssigned(true);
source1.setDeviceType(deviceType);
Source source1 = new Source()
.project(project)
.assigned(true)
.deviceType(deviceType);
source1.getAttributes().putAll(sourceRegistrationDTO.getAttributes());
source1 = sourceRepository.save(source1);
// if source name is provided update source name
if(sourceRegistrationDTO.getSourceName() !=null ) {
source1.setSourceName(sourceRegistrationDTO.getSourceName()+"_"+source1.getSourceName());
source1 = sourceRepository.save(source1);
if (Objects.nonNull(sourceRegistrationDTO.getSourceName())) {
source1.setSourceName(sourceRegistrationDTO.getSourceName());
}
source1 = sourceRepository.save(source1);

assignedSource = source1;
subject.getSources().add(source1);
} else {
Expand All @@ -262,7 +263,7 @@ else if (deviceType.getCanRegisterDynamically()) {
}
}

if(assignedSource ==null) {
if (assignedSource == null) {
log.error("Cannot find assigned source with sourceId or a source of deviceType"
+ " with the specified producer and model "
+ " is already registered for subject login ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,19 @@ public String getDeviceTypeProducer() {
public void setDeviceTypeProducer(String deviceTypeProducer) {
this.deviceTypeProducer = deviceTypeProducer;
}

@Override
public String toString() {
return "MinimalSourceDetailsDTO{"
+ "id=" + id
+ ", deviceTypeId=" + deviceTypeId
+ ", deviceTypeProducer='" + deviceTypeProducer + '\''
+ ", deviceTypeModel='" + deviceTypeModel + '\''
+ ", deviceTypeCatalogVersion='" + deviceTypeCatalogVersion + '\''
+ ", expectedSourceName='" + expectedSourceName + '\''
+ ", sourceId=" + sourceId
+ ", sourceName='" + sourceName + '\''
+ ", assigned=" + assigned
+ ", attributes=" + attributes + '}';
}
}
90 changes: 72 additions & 18 deletions src/main/java/org/radarcns/management/web/rest/SubjectResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.codahale.metrics.annotation.Timed;
import io.github.jhipster.web.util.ResponseUtil;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.radarcns.auth.authorization.AuthoritiesConstants;
import org.radarcns.auth.authorization.Permission;
import org.radarcns.management.domain.DeviceType;
Expand All @@ -10,10 +12,13 @@
import org.radarcns.management.repository.ProjectRepository;
import org.radarcns.management.repository.SubjectRepository;
import org.radarcns.management.security.SecurityUtils;
import org.radarcns.management.service.DeviceTypeService;
import org.radarcns.management.service.SubjectService;
import org.radarcns.management.service.dto.DeviceTypeDTO;
import org.radarcns.management.service.dto.MinimalSourceDetailsDTO;
import org.radarcns.management.service.dto.SubjectDTO;
import org.radarcns.management.service.mapper.SubjectMapper;
import org.radarcns.management.web.rest.errors.CustomParameterizedException;
import org.radarcns.management.web.rest.util.HeaderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,7 +40,9 @@
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import static org.radarcns.auth.authorization.Permission.SUBJECT_CREATE;
import static org.radarcns.auth.authorization.Permission.SUBJECT_DELETE;
Expand Down Expand Up @@ -69,6 +76,9 @@ public class SubjectResource {
@Autowired
private ProjectRepository projectRepository;

@Autowired
private DeviceTypeService deviceTypeService;

@Autowired
private HttpServletRequest servletRequest;

Expand Down Expand Up @@ -259,26 +269,34 @@ public ResponseEntity<Void> deleteSubject(@PathVariable String login) {
}

/**
* POST /subjects/:login/sources: Assign a list of sources to the currently logged in user
* POST /subjects/:login/sources: Assign a source to the specified user
*
* The request body should contain a source-meta-data to be assigned to the a subject.
* At minimum, each source
* should define it's device type, like so: <code>[{"deviceType": { "id": 3 }}]</code>. A
* source name and source ID will be automatically generated. The source ID will be a new random
* UUID, and the source name will be the device model, appended with a dash and the first six
* characters of the UUID. The sources will be created and assigned to the currently logged in
* user.
* The request body is a {@link MinimalSourceDetailsDTO}. At minimum, the source should
* define it's device type by either supplying the deviceTypeId, or the combination of
* (deviceTypeProducer, deviceTypeModel, deviceTypeCatalogVersion) fields. A source ID will
* be automatically generated. The source ID will be a new random UUID, and the source name,
* if not provided, will be the device model, appended with a dash and the first eight
* characters of the UUID. The sources will be created and assigned to the specified user.
*
* If you need to assign existing sources, simply specify either of id, sourceId, or sourceName
* in the source object.
* fields.
*
* @param sourceDTO The {@link MinimalSourceDetailsDTO} specification
* @return The {@link MinimalSourceDetailsDTO} completed with all identifying fields.
*
* @param sourceDTO List of sources to assign
* @return The updated Subject information
*/
@PostMapping("/subjects/{login}/sources")
@ApiResponses({
@ApiResponse(code = 200, message = "An existing source was assigned"),
@ApiResponse(code = 201, message = "A new source was created and assigned"),
@ApiResponse(code = 400, message = "You must supply either a Device Type ID, or the "
+ "combination of (deviceTypeProducer, deviceTypeModel, catalogVersion)"),
@ApiResponse(code = 404, message = "Either the subject or the device type was not "
+ "found.")
})
@Timed
public ResponseEntity<MinimalSourceDetailsDTO> assignSources(@PathVariable String login,
@RequestBody MinimalSourceDetailsDTO sourceDTO) {
@RequestBody MinimalSourceDetailsDTO sourceDTO) throws URISyntaxException {
// check the subject id
Optional<Subject> subject = subjectRepository.findOneWithEagerBySubjectLogin(login);
if (!subject.isPresent()) {
Expand All @@ -298,29 +316,65 @@ public ResponseEntity<MinimalSourceDetailsDTO> assignSources(@PathVariable Strin
" is not assigned to any project. Could not find project for this subject."));
}
Role role = roleOptional.get();
// find out device type id of supplied device
Long deviceTypeId = sourceDTO.getDeviceTypeId();
if (Objects.isNull(deviceTypeId)) {
// check if combination (producer, model, version) is present
final String msg = "You must supply either the deviceTypeId, or the combination of "
+ "(deviceTypeProducer, deviceTypeModel, catalogVersion) fields.";
try {
String producer = Objects.requireNonNull(sourceDTO.getDeviceTypeProducer(), msg);
String model = Objects.requireNonNull(sourceDTO.getDeviceTypeModel(), msg);
String version = Objects.requireNonNull(sourceDTO.getDeviceTypeCatalogVersion(),
msg);
DeviceTypeDTO deviceTypeDTO = deviceTypeService
.findByProducerAndModelAndVersion(producer, model, version);
if (Objects.isNull(deviceTypeDTO)) {
return ResponseEntity.notFound().build();
}
deviceTypeId = deviceTypeDTO.getId();
} catch (NullPointerException ex) {
log.error(ex.getMessage() + ", supplied sourceDTO: " + sourceDTO.toString());
throw new CustomParameterizedException(ex.getMessage());
}
}
// find whether the relevant device-type is available in the subject's project
Optional<DeviceType> deviceType;
deviceType = projectRepository.findDeviceTypeByProjectIdAndDeviceTypeId(
role.getProject().getId(), sourceDTO.getDeviceTypeId());
role.getProject().getId(), deviceTypeId);

if (!deviceType.isPresent()) {
// return bad request
return ResponseEntity.status(HttpStatus.BAD_REQUEST).headers(HeaderUtil
.createAlert("deviceTypeNotAvailable",
"No device-type found for device type ID " + sourceDTO.getDeviceTypeId()
+ " in relevant project")).body(null);

}

checkPermissionOnSubject(getJWT(servletRequest), SUBJECT_UPDATE, role.getProject()
.getProjectName(), sub.getUser().getLogin());

// check if any of id, sourceID, sourceName were non-null
boolean existing = Stream.of(sourceDTO.getId(), sourceDTO.getSourceName(),
sourceDTO.getSourceId())
.map(Objects::nonNull).reduce(false, (r1, r2) -> r1 || r2);

// handle the source registration
MinimalSourceDetailsDTO sourceRegistered = subjectService
.assignOrUpdateSource(sub, deviceType.get(), role.getProject(), sourceDTO);
.assignOrUpdateSource(sub, deviceType.get(), role.getProject(), sourceDTO);

// TODO: replace ok() with created, with a location to query the new source.
return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(
ENTITY_NAME, sub.getId().toString())).body(sourceRegistered);
// Return the correct response type, either created if a new source was created, or ok if
// an existing source was provided. If an existing source was given but not found, the
// assignOrUpdateSource would throw an error and we would not reach this point.
if (!existing) {
return ResponseEntity.created(new URI("/api/sources/" + sourceRegistered.getSourceName())).headers(
HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, login))
.body(sourceRegistered);
}
else {
return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME,
login)).body(sourceRegistered);
}
}

@GetMapping("/subjects/{login}/sources")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public void setup() throws ServletException {
ReflectionTestUtils.setField(subjectResource, "subjectRepository" , subjectRepository);
ReflectionTestUtils.setField(subjectResource, "subjectMapper" , subjectMapper);
ReflectionTestUtils.setField(subjectResource, "projectRepository" , projectRepository);
ReflectionTestUtils.setField(subjectResource, "deviceTypeService", deviceTypeService);
ReflectionTestUtils.setField(subjectResource, "servletRequest", servletRequest);

JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
Expand Down Expand Up @@ -299,7 +300,7 @@ public void equalsVerifier() throws Exception {

@Test
@Transactional
public void dynamicSourceRegistration() throws Exception {
public void dynamicSourceRegistrationWithId() throws Exception {
int databaseSizeBeforeCreate = subjectRepository.findAll().size();

// Create the Subject
Expand All @@ -317,34 +318,95 @@ public void dynamicSourceRegistration() throws Exception {
String subjectLogin = testSubject.getUser().getLogin();
assertNotNull(subjectLogin);

// Create a source description
MinimalSourceDetailsDTO sourceRegistrationDTO = createSourceWithDeviceId();

restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.sourceId").isNotEmpty());

// A source can not be assigned twice to a subject, so this call must fail
assertThat(sourceRegistrationDTO.getSourceId()).isNull();
restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().is4xxClientError());
}

@Test
@Transactional
public void dynamicSourceRegistrationWithoutId() throws Exception {
int databaseSizeBeforeCreate = subjectRepository.findAll().size();

// Create the Subject
SubjectDTO subjectDTO = createEntityDTO(em);
restSubjectMockMvc.perform(post("/api/subjects")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(subjectDTO)))
.andExpect(status().isCreated());

// Validate the Subject in the database
List<Subject> subjectList = subjectRepository.findAll();
assertThat(subjectList).hasSize(databaseSizeBeforeCreate + 1);
Subject testSubject = subjectList.get(subjectList.size() - 1);

String subjectLogin = testSubject.getUser().getLogin();
assertNotNull(subjectLogin);

// Create a source description
MinimalSourceDetailsDTO sourceRegistrationDTO = createSourceWithoutDeviceId();

restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.sourceId").isNotEmpty());

// A source can not be assigned twice to a subject, so this call must fail
assertThat(sourceRegistrationDTO.getSourceId()).isNull();
restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().is4xxClientError());
}

private MinimalSourceDetailsDTO createSourceWithDeviceId() {
// Create a source description
MinimalSourceDetailsDTO sourceRegistrationDTO = new MinimalSourceDetailsDTO();
sourceRegistrationDTO.setSourceName(DEVICE_PRODUCER + " " + DEVICE_MODEL);
sourceRegistrationDTO.getAttributes().put("some", "value");

List<DeviceTypeDTO> deviceTypes = deviceTypeService.findAll().stream()
.filter(dt -> dt.getCanRegisterDynamically())
.collect(Collectors.toList());
.filter(dt -> dt.getCanRegisterDynamically())
.collect(Collectors.toList());

assertThat(deviceTypes.size()).isGreaterThan(0);
DeviceTypeDTO deviceType = deviceTypes.get(0);
sourceRegistrationDTO.setDeviceTypeId(deviceType.getId());
sourceRegistrationDTO.setDeviceTypeCatalogVersion(deviceType.getCatalogVersion());
sourceRegistrationDTO.setDeviceTypeModel(deviceType.getDeviceModel());
sourceRegistrationDTO.setDeviceTypeProducer(deviceType.getDeviceProducer());

assertThat(sourceRegistrationDTO.getSourceId()).isNull();
return sourceRegistrationDTO;
}

restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.sourceId").isNotEmpty());
private MinimalSourceDetailsDTO createSourceWithoutDeviceId() {
// Create a source description
MinimalSourceDetailsDTO sourceRegistrationDTO = new MinimalSourceDetailsDTO();
sourceRegistrationDTO.setSourceName(DEVICE_PRODUCER + " " + DEVICE_MODEL);
sourceRegistrationDTO.getAttributes().put("some", "value");

List<DeviceTypeDTO> deviceTypes = deviceTypeService.findAll().stream()
.filter(dt -> dt.getCanRegisterDynamically())
.collect(Collectors.toList());

assertThat(deviceTypes.size()).isGreaterThan(0);
DeviceTypeDTO deviceType = deviceTypes.get(0);
sourceRegistrationDTO.setDeviceTypeCatalogVersion(deviceType.getCatalogVersion());
sourceRegistrationDTO.setDeviceTypeModel(deviceType.getDeviceModel());
sourceRegistrationDTO.setDeviceTypeProducer(deviceType.getDeviceProducer());

// An entity with an existing ID cannot be created, so this API call must fail
assertThat(sourceRegistrationDTO.getSourceId()).isNull();
restSubjectMockMvc.perform(post("/api/subjects/{login}/sources", subjectLogin)
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(sourceRegistrationDTO)))
.andExpect(status().is4xxClientError());
return sourceRegistrationDTO;
}
}

0 comments on commit 03fada4

Please sign in to comment.