package com.aemreunal.controller.beacon; /* * *********************** * * Copyright (c) 2015 * * * * This code belongs to: * * * * @author Ahmet Emre Ünal * * S001974 * * * * aemreunal@gmail.com * * emre.unal@ozu.edu.tr * * * * aemreunal.com * * *********************** * */ import net.minidev.json.JSONObject; import java.util.LinkedHashSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.mvc.ControllerLinkBuilder; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.util.UriComponentsBuilder; import com.aemreunal.config.GlobalSettings; import com.aemreunal.controller.project.ProjectController; import com.aemreunal.controller.region.RegionController; import com.aemreunal.controller.scenario.ScenarioController; import com.aemreunal.controller.user.UserController; import com.aemreunal.domain.Beacon; import com.aemreunal.domain.Connection; import com.aemreunal.exception.connection.ConnectionNotPossibleException; import com.aemreunal.exception.connection.ConnectionExistsException; import com.aemreunal.exception.connection.ConnectionNotFoundException; import com.aemreunal.exception.imageStorage.ImageDeleteException; import com.aemreunal.exception.imageStorage.ImageLoadException; import com.aemreunal.exception.imageStorage.ImageSaveException; import com.aemreunal.exception.region.MultipartFileReadException; import com.aemreunal.exception.region.WrongFileTypeSubmittedException; import com.aemreunal.exception.textStorage.TextSaveException; import com.aemreunal.helper.json.JsonBuilderFactory; import com.aemreunal.service.BeaconService; import com.aemreunal.service.ConnectionService; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @Controller @RequestMapping(GlobalSettings.BEACON_PATH_MAPPING) public class BeaconController { @Autowired private BeaconService beaconService; @Autowired private ConnectionService connectionService; /** * Get all the {@link com.aemreunal.domain.Beacon beacons} that belong to the * specified {@link com.aemreunal.domain.Project project}. Returns an empty list if no * beacons are present in the project. * <p> * {@literal @}Transactional mark via http://stackoverflow.com/questions/11812432/spring-data-hibernate * <p> * Optionally, certain parameters may be specified to get a refined search. These * paramters are: <li><b>UUID:</b> The UUID constraint. Can be specified with the * "{@code?uuid=...}" URI parameter. Any continuous part of thes UUID attribute may be * specified in the constraint.</li> <li><b>Major:</b> The Major constraint. Can be * specified with the "{@code?major=...}" URI parameter.</li> <li><b>Minor:</b> The * Minor constraint. Can be specified with the "{@code?minor=...}" URI * parameter.</li> * * @param username * The username of the owner of the project * @param projectId * The ID of the project * @param uuid * (Optional) The UUID constraint for the beacon search * @param major * (Optional) The Major constraint for the beacon search * @param minor * (Optional) The Minor constraint for the beacon search * * @return If no optional parameters are specified, returns all the beacons that * belong to a project (an empty list if the project has no beacons). If optional * constraints are given, returns a list of all the matching beacons or throws a * {@link com.aemreunal.exception.beacon.BeaconNotFoundException * BeaconNotFoundException} if no beacons match the constraints. */ @Transactional @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<LinkedHashSet<Beacon>> getBeaconsOfRegion(@PathVariable String username, @PathVariable Long projectId, @PathVariable Long regionId, @RequestParam(value = "uuid", required = false) String uuid, @RequestParam(value = "major", required = false) Integer major, @RequestParam(value = "minor", required = false) Integer minor, @RequestParam(value = "designated", required = false) Boolean designated) { if (uuid == null && major == null && minor == null && designated == null) { LinkedHashSet<Beacon> beaconSet = beaconService.getBeaconsOfRegion(username, projectId, regionId); return new ResponseEntity<LinkedHashSet<Beacon>>(beaconSet, HttpStatus.OK); } else { LinkedHashSet<Beacon> beacons = beaconService.findBeaconsBySpecs(username, projectId, regionId, uuid, major, minor, designated); return new ResponseEntity<LinkedHashSet<Beacon>>(beacons, HttpStatus.OK); } } /** * Get the beacon with the specified ID * * @param username * The username of the owner of the project * @param projectId * The ID of the project * @param beaconId * The ID of the beacon * * @return The beacon */ @RequestMapping(method = RequestMethod.GET, value = GlobalSettings.BEACON_ID_MAPPING, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Beacon> getBeacon(@PathVariable String username, @PathVariable Long projectId, @PathVariable Long regionId, @PathVariable Long beaconId) { Beacon beacon = beaconService.getBeacon(username, projectId, regionId, beaconId); addLinks(username, projectId, regionId, beacon); return new ResponseEntity<Beacon>(beacon, HttpStatus.OK); } private void addLinks(String username, Long projectId, Long regionId, Beacon beacon) { beacon.add(ControllerLinkBuilder.linkTo(methodOn(BeaconController.class).getBeacon(username, projectId, regionId, beacon.getBeaconId())).withSelfRel()); beacon.add(ControllerLinkBuilder.linkTo(methodOn(UserController.class).getUserByUsername(username)).withRel("owner")); beacon.add(ControllerLinkBuilder.linkTo(methodOn(ProjectController.class).getProjectById(username, projectId)).withRel("project")); beacon.add(ControllerLinkBuilder.linkTo(methodOn(RegionController.class).getRegion(username, projectId, beacon.getRegion().getRegionId())).withRel("region")); if (beacon.getScenario() != null) { beacon.add(ControllerLinkBuilder.linkTo(methodOn(ScenarioController.class).getScenario(username, projectId, beacon.getScenario().getScenarioId())).withRel("scenario")); } } /** * Create a new beacon in region. * <p> * Beacon creation request JSON: * <pre> * { * "uuid":"12345678-1234-1234-1234-123456789012", * "major":"1", * "minor":"2", * "xCoordinate":"150" * "yCoordinate":"120" * "designated":"true" * "displayName":"Room 237" * "description":"Beacon description" * } * </pre> * * @param username * The username of the owner of the project * @param projectId * The ID of the project to create the beacon in * @param regionId * The ID of the region * @param beacon * The beacon parsed from the JSON object * @param locationInfoText * The [optional] location info text as a {@link MultipartFile}. * @param builder * The URI builder for post-creation redirect * * @return The created beacon */ @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Beacon> createBeacon(@PathVariable String username, @PathVariable Long projectId, @PathVariable Long regionId, @RequestPart(value = "beacon") Beacon beacon, @RequestPart(value = "info", required = false) MultipartFile locationInfoText, UriComponentsBuilder builder) throws TextSaveException { Beacon savedBeacon = beaconService.saveNewBeacon(username, projectId, regionId, beacon, locationInfoText); GlobalSettings.log("Saved beacon with UUID = \'" + savedBeacon.getUuid() + "\' major = \'" + savedBeacon.getMajor() + "\' minor = \'" + savedBeacon.getMinor() + "\' in project with ID = \'" + projectId + "\'"); addLinks(username, projectId, regionId, savedBeacon); return buildCreateResponse(username, builder, savedBeacon); } private ResponseEntity<Beacon> buildCreateResponse(String username, UriComponentsBuilder builder, Beacon savedBeacon) { HttpHeaders headers = new HttpHeaders(); headers.setLocation(builder.path(GlobalSettings.BEACON_SPECIFIC_MAPPING) .buildAndExpand( username, savedBeacon.getRegion().getProject().getProjectId().toString(), savedBeacon.getRegion().getRegionId().toString(), savedBeacon.getBeaconId().toString()) .toUri()); return new ResponseEntity<Beacon>(savedBeacon, headers, HttpStatus.CREATED); } /** * Create a connection between two beacons. * * @param username * The username of the owner of the project. * @param projectId * The ID of the project. * @param regionOneId * The ID of the region. * @param beaconOneId * The ID of one of the two beacons. * @param regionTwoId * The ID of the region the other of the two beacons' is in. * @param beaconTwoId * The ID of the other of the two beacons. * @param imageMultipartFile * The connection navigation image file as a {@link org.springframework.web.multipart.MultipartFile * MultipartFile}. * * @return The list of beacon IDs that were connected. * * @throws WrongFileTypeSubmittedException * The file type of the {@link org.springframework.web.multipart.MultipartFile * MultipartFile} file submitted as the connection image is of some other type * than JPEG, GIF, or PNG. * @throws ImageSaveException * If the server is unable to save the connection image. * @throws ImageDeleteException * If the connection image being replaced couldn't be deleted by the server. * @throws MultipartFileReadException * If the Multipart file couldn't be read. */ @RequestMapping(method = RequestMethod.POST, value = GlobalSettings.BEACON_CONNECTION_MAPPING, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<JSONObject> connectBeacons(@PathVariable String username, @PathVariable Long projectId, @PathVariable(value = "regionId") Long regionOneId, @PathVariable(value = "beaconId") Long beaconOneId, @RequestParam("region2id") Long regionTwoId, @RequestParam("beacon2id") Long beaconTwoId, @RequestPart(value = "image") MultipartFile imageMultipartFile) throws WrongFileTypeSubmittedException, ImageSaveException, ImageDeleteException, MultipartFileReadException, ConnectionExistsException, ConnectionNotPossibleException { Connection connection = connectionService.createNewConnection(username, projectId, beaconOneId, regionOneId, beaconTwoId, regionTwoId, imageMultipartFile); JSONObject connectionBeacons = JsonBuilderFactory.object().add("beacons", connection.getBeaconIdsAsJson()).build(); return new ResponseEntity<JSONObject>(connectionBeacons, HttpStatus.CREATED); } @RequestMapping(method = RequestMethod.GET, value = GlobalSettings.BEACON_CONNECTION_MAPPING, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) // When ConnectionNotFound is triggered as a result of this method, an HttpMediaTypeNotAcceptableException // exception is raised. It's not raised if the exception handler also returns ResponseEntity<byte[]>. // TODO Fix. public ResponseEntity<byte[]> downloadConnectionImage(@PathVariable String username, @PathVariable Long projectId, @PathVariable(value = "regionId") Long regionOneId, @PathVariable(value = "beaconId") Long beaconOneId, @RequestParam("region2id") Long regionTwoId, @RequestParam("beacon2id") Long beaconTwoId) throws ConnectionNotFoundException, ImageLoadException { byte[] connectionImage = connectionService.getConnectionImage(username, projectId, regionOneId, beaconOneId, regionTwoId, beaconTwoId); return new ResponseEntity<byte[]>(connectionImage, HttpStatus.OK); } @RequestMapping(method = RequestMethod.DELETE, value = GlobalSettings.BEACON_CONNECTION_MAPPING, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<JSONObject> disconnectBeacons(@PathVariable String username, @PathVariable Long projectId, @PathVariable(value = "regionId") Long regionOneId, @PathVariable(value = "beaconId") Long beaconOneId, @RequestParam("region2id") Long regionTwoId, @RequestParam("beacon2id") Long beaconTwoId) throws ImageDeleteException, ConnectionNotFoundException { Connection connection = connectionService.deleteConnection(username, projectId, regionOneId, beaconOneId, regionTwoId, beaconTwoId); JSONObject connectionBeacons = JsonBuilderFactory.object().add("beacons", connection.getBeaconIdsAsJson()).build(); return new ResponseEntity<JSONObject>(connectionBeacons, HttpStatus.OK); } /** * Delete the specified beacon * <p> * To delete the beacon, confirmation must be supplied as a URI parameter, in the form * of "?confirm=yes". If not supplied, the beacon will not be deleted. * * @param projectId * The ID of the project * @param beaconId * The ID of the beacon * * @return The status of deletion action */ @RequestMapping(method = RequestMethod.DELETE, value = GlobalSettings.BEACON_ID_MAPPING) public ResponseEntity<Beacon> deleteBeacon(@PathVariable String username, @PathVariable Long projectId, @PathVariable Long regionId, @PathVariable Long beaconId, @RequestParam(value = "confirm", required = true) String confirmation) { if (confirmation.toLowerCase().equals("yes")) { Beacon deletedBeacon = beaconService.delete(username, projectId, regionId, beaconId); return new ResponseEntity<Beacon>(deletedBeacon, HttpStatus.OK); } else { return new ResponseEntity<Beacon>(HttpStatus.PRECONDITION_FAILED); } } }