package ca.intelliware.ihtsdo.mlds.web.rest; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Resource; import javax.annotation.security.RolesAllowed; import javax.transaction.Transactional; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.joda.time.Instant; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.Lists; import ca.intelliware.ihtsdo.mlds.domain.Affiliate; import ca.intelliware.ihtsdo.mlds.domain.AffiliateDetails; import ca.intelliware.ihtsdo.mlds.domain.AffiliateSubType; import ca.intelliware.ihtsdo.mlds.domain.AffiliateType; import ca.intelliware.ihtsdo.mlds.domain.AgreementType; import ca.intelliware.ihtsdo.mlds.domain.Application; import ca.intelliware.ihtsdo.mlds.domain.Application.ApplicationType; import ca.intelliware.ihtsdo.mlds.domain.ApprovalState; import ca.intelliware.ihtsdo.mlds.domain.MailingAddress; import ca.intelliware.ihtsdo.mlds.domain.Member; import ca.intelliware.ihtsdo.mlds.domain.OrganizationType; import ca.intelliware.ihtsdo.mlds.domain.PrimaryApplication; import ca.intelliware.ihtsdo.mlds.domain.StandingState; import ca.intelliware.ihtsdo.mlds.domain.UsageReportState; import ca.intelliware.ihtsdo.mlds.domain.User; import ca.intelliware.ihtsdo.mlds.domain.json.ApplicationCollection; import ca.intelliware.ihtsdo.mlds.repository.AffiliateDetailsRepository; import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository; import ca.intelliware.ihtsdo.mlds.repository.ApplicationRepository; import ca.intelliware.ihtsdo.mlds.repository.CountryRepository; import ca.intelliware.ihtsdo.mlds.repository.MemberRepository; import ca.intelliware.ihtsdo.mlds.repository.UserRepository; import ca.intelliware.ihtsdo.mlds.security.AuthoritiesConstants; import ca.intelliware.ihtsdo.mlds.service.AffiliateAuditEvents; import ca.intelliware.ihtsdo.mlds.service.AffiliateDetailsResetter; import ca.intelliware.ihtsdo.mlds.service.ApplicationService; import ca.intelliware.ihtsdo.mlds.service.CommercialUsageService; import ca.intelliware.ihtsdo.mlds.service.UsageReportTransition; import ca.intelliware.ihtsdo.mlds.service.UserMembershipAccessor; import ca.intelliware.ihtsdo.mlds.service.mail.ApplicationApprovedEmailSender; import ca.intelliware.ihtsdo.mlds.web.RouteLinkBuilder; import ca.intelliware.ihtsdo.mlds.web.SessionService; @RestController public class ApplicationResource { @Resource ApplicationRepository applicationRepository; @Resource SessionService sessionService; @Resource AffiliateRepository affiliateRepository; @Resource ApplicationApprovedEmailSender applicationApprovedEmailSender; @Resource ApplicationApprovalStateChangeNotifier applicationApprovalStateChangeNotifier; @Resource UserRepository userRepository; @Resource ApplicationAuditEvents applicationAuditEvents; @Resource AffiliateAuditEvents affiliateAuditEvents; @Resource ApplicationAuthorizationChecker authorizationChecker; @Resource CountryRepository countryRepository; @Resource AffiliateDetailsRepository affiliateDetailsRepository; @Resource AffiliateDetailsResetter affiliateDetailsResetter; @Resource ApplicationService applicationService; @Resource RouteLinkBuilder routeLinkBuilder; @Resource ObjectMapper objectMapper; @Resource UserMembershipAccessor userMembershipAccessor; @Resource MemberRepository memberRepository; @Resource CommercialUsageService commercialUsageService; @RequestMapping(value="api/applications") @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public @ResponseBody Iterable<Application> getApplications() { return applicationRepository.findAll(); } @RequestMapping(value = Routes.APPLICATION_APPROVE, method=RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public @ResponseBody ResponseEntity<Application> approveApplication(@PathVariable long applicationId, @RequestBody String approvalStateString) throws CloneNotSupportedException { //FIXME why cant this be the body type? ApprovalState approvalState = ApprovalState.valueOf(approvalStateString); Application application = applicationRepository.findOne(applicationId); if (application == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } authorizationChecker.checkCanApproveApplication(application); //FIXME should there be state transition validation? application.setApprovalState(approvalState); //FIXME add flags to approval state? if (Objects.equal(approvalState, ApprovalState.APPROVED) || Objects.equal(approvalState, ApprovalState.REJECTED)) { application.setCompletedAt(Instant.now()); } Affiliate affiliate = findAffiliateByUsername(application.getUsername()); if (affiliate == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } if (Objects.equal(approvalState, ApprovalState.APPROVED)) { AffiliateDetails affiliateDetails = (AffiliateDetails) application.getAffiliateDetails().clone(); affiliateDetailsResetter.detach(affiliateDetails); affiliateDetails = affiliateDetailsRepository.save(affiliateDetails); affiliate.setAffiliateDetails(affiliateDetails); affiliateRepository.save(affiliate); } //FIXME MLDS-314 not sure where this code should be if (Objects.equal(application.getApplicationType(), ApplicationType.PRIMARY)) { StandingState newStandingState = null; if (Objects.equal(application.getApprovalState(), ApprovalState.APPROVED)) { // MLDS-902 When the authorizing user is a member (ie not IHTSDO) then the account standing // state should transition to "In Good Standing" if (authorizationChecker.isAdmin()) { newStandingState = StandingState.PENDING_INVOICE; } else { newStandingState = StandingState.IN_GOOD_STANDING; } } else if (Objects.equal(application.getApprovalState(), ApprovalState.REJECTED)) { newStandingState = StandingState.REJECTED; } if (Objects.equal(affiliate.getStandingState(), StandingState.APPLYING) && newStandingState != null) { affiliate.setStandingState(newStandingState); affiliateRepository.save(affiliate); affiliateAuditEvents.logStandingStateChange(affiliate); } } applicationRepository.save(application); if (Objects.equal(approvalState, ApprovalState.APPROVED)) { User user = userRepository.getUserByEmailIgnoreCase(application.getAffiliateDetails().getEmail()); applicationApprovedEmailSender.sendApplicationApprovalEmail(user, application.getMember().getKey(), affiliate.getAffiliateId()); } applicationAuditEvents.logApprovalStateChange(application); return new ResponseEntity<Application>(application, HttpStatus.OK); } private Affiliate findAffiliateByUsername(String username) { Affiliate affiliate = null; List<Affiliate> affiliates = affiliateRepository.findByCreatorIgnoreCase(username); if (affiliates.size() > 0) { affiliate = affiliates.get(0); } return affiliate; } private static final String FILTER_PENDING = "approvalState/pending eq true"; public static final String FILTER_HOME_MEMBER = "homeMember eq '(\\w+)'"; @RequestMapping(value = Routes.APPLICATIONS, method=RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public @ResponseBody ResponseEntity<ApplicationCollection> getApplications( @RequestParam(value="$page", defaultValue="0", required=false) Integer page, @RequestParam(value="$pageSize", defaultValue="50", required=false) Integer pageSize, @RequestParam(value="$orderby", required=false) String orderby, @RequestParam(value="$filter", required=false) List<String> filters) { Page<Application> applications; Sort sort = createApplicationsSort(orderby); PageRequest pageRequest = new PageRequest(page, pageSize, sort); Member member = null; List<ApprovalState> approvalStates = null; if (filters == null || filters.size() == 0 || StringUtils.isBlank(filters.get(0))) { applications = applicationRepository.findAll(pageRequest); } else { for (String filter : filters) { Matcher homeMemberMatcher = Pattern.compile(FILTER_HOME_MEMBER).matcher(filter); if (homeMemberMatcher.matches()) { String homeMember = homeMemberMatcher.group(1); member = memberRepository.findOneByKey(homeMember); continue; } Matcher pendingMatcher = Pattern.compile(FILTER_PENDING).matcher(filter); if (pendingMatcher.matches()) { approvalStates = Lists.newArrayList(ApprovalState.SUBMITTED, ApprovalState.RESUBMITTED, ApprovalState.REVIEW_REQUESTED, ApprovalState.CHANGE_REQUESTED); continue; } //FIXME Limited OData implementation - expand or use real OData library in the future return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } if (member != null) { if (approvalStates != null) { applications = applicationRepository.findByApprovalStateInAndMember(approvalStates, member, pageRequest); } else { applications = applicationRepository.findByMember(member, pageRequest); } } else { applications = applicationRepository.findByApprovalStateIn(approvalStates, pageRequest); } } return new ResponseEntity<ApplicationCollection>(new ApplicationCollection(applications), HttpStatus.OK); } private static final Map<String,List<String>> ORDER_BY_FIELD_MAPPINGS = new HashMap<String,List<String>>(); static { ORDER_BY_FIELD_MAPPINGS.put("applicationId", Arrays.asList("applicationId")); ORDER_BY_FIELD_MAPPINGS.put("affiliateName", Arrays.asList("affiliateDetails.firstName", "affiliateDetails.lastName")); ORDER_BY_FIELD_MAPPINGS.put("applicationType", Arrays.asList("applicationTypeValue")); ORDER_BY_FIELD_MAPPINGS.put("agreementType", Arrays.asList("affiliateDetails.agreementType", "affiliate.affiliateDetails.agreementType")); ORDER_BY_FIELD_MAPPINGS.put("useType", Arrays.asList("affiliateDetails.type", "affiliateDetails.subType")); ORDER_BY_FIELD_MAPPINGS.put("submittedAt", Arrays.asList("submittedAt")); ORDER_BY_FIELD_MAPPINGS.put("approvalState", Arrays.asList("approvalState")); ORDER_BY_FIELD_MAPPINGS.put("country", Arrays.asList("affiliateDetails.address.country.commonName")); ORDER_BY_FIELD_MAPPINGS.put("member", Arrays.asList("member.key")); ORDER_BY_FIELD_MAPPINGS.put("email", Arrays.asList("affiliateDetails.email")); } private Sort createApplicationsSort(String orderby) { Sort defaultSort = new Sort( new Order(Direction.ASC, "applicationId") ); return new SortBuilder().createSort(orderby, ORDER_BY_FIELD_MAPPINGS, defaultSort); } @RequestMapping(value = Routes.APPLICATION, method=RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER, AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public @ResponseBody ResponseEntity<Application> getApplication(@PathVariable long applicationId){ Application application = applicationRepository.findOne(applicationId); if (application == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } authorizationChecker.checkCanAccessApplication(application); return new ResponseEntity<Application>(application, HttpStatus.OK); } @RequestMapping(value = Routes.APPLICATION_ME, method=RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER}) @Timed public @ResponseBody ResponseEntity<Application> getApplicationForMe(){ List<Application> applications = applicationRepository.findByUsernameIgnoreCase(sessionService.getUsernameOrNull()); if (applications.size() > 0) { return new ResponseEntity<Application>(applications.get(0), HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @Transactional @RequestMapping(value="/api/application", method=RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER}) @Timed public @ResponseBody ResponseEntity<Application> getUserApplication(){ List<Application> applications = applicationRepository.findByUsernameIgnoreCase(sessionService.getUsernameOrNull()); if (applications.size() > 0) { return new ResponseEntity<Application>(applications.get(0), HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } @Transactional @RequestMapping(value=Routes.APPLICATION_REGISTRATION, method=RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER}) @Timed public @ResponseBody ResponseEntity<Application> submitApplication(@PathVariable long applicationId, @RequestBody JsonNode request) { PrimaryApplication application = (PrimaryApplication) applicationRepository.findOne(applicationId); if (application == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } ApprovalState originalApplicationState = application.getApprovalState(); application = saveApplicationFields(request,application); authorizationChecker.checkCanAccessApplication(application); // Mark application as submitted if (Objects.equal(application.getApprovalState(), ApprovalState.CHANGE_REQUESTED)) { application.setApprovalState(ApprovalState.RESUBMITTED); } else if (Objects.equal(application.getApprovalState(), ApprovalState.NOT_SUBMITTED)){ application.setApprovalState(ApprovalState.SUBMITTED); } else { return new ResponseEntity<>(HttpStatus.CONFLICT); } application.setSubmittedAt(Instant.now()); applicationRepository.save(application); Affiliate affiliate = application.getAffiliate(); affiliate.setCreator(application.getUsername()); affiliate.setApplication(application); affiliate.setType(application.getType()); affiliate.setHomeMember(application.getMember()); { AffiliateDetails affiliateDetails = (AffiliateDetails) application.getAffiliateDetails().clone(); affiliateDetailsResetter.detach(affiliateDetails); affiliate.setAffiliateDetails(affiliateDetails); } affiliateRepository.save(affiliate); applicationAuditEvents.logApprovalStateChange(application); if (application.getCommercialUsage() != null && Objects.equal(application.getCommercialUsage().getState(), UsageReportState.NOT_SUBMITTED)) { commercialUsageService.transitionCommercialUsageApproval(application.getCommercialUsage(), UsageReportTransition.SUBMIT); } applicationApprovalStateChangeNotifier.applicationApprovalStateChange(originalApplicationState, application); return new ResponseEntity<Application>(application, HttpStatus.OK); } @Transactional @RequestMapping(value=Routes.APPLICATION_REGISTRATION, method=RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER}) @Timed public @ResponseBody ResponseEntity<Application> saveApplication(@PathVariable long applicationId, @RequestBody JsonNode request) { Application application = applicationRepository.findOne(applicationId); if (application == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } application = saveApplicationFields(request,application); authorizationChecker.checkCanAccessApplication(application); ApprovalState preApprovalState = application.getApprovalState(); // Mark application as not submitted if (Objects.equal(application.getApprovalState(), ApprovalState.CHANGE_REQUESTED)) { application.setApprovalState(ApprovalState.CHANGE_REQUESTED); } else { application.setApprovalState(ApprovalState.NOT_SUBMITTED); } if (!Objects.equal(application.getApprovalState(), preApprovalState)) { return new ResponseEntity<>(HttpStatus.CONFLICT); } application.getAffiliate().setAffiliateDetails(application.getAffiliateDetails()); applicationRepository.save(application); return new ResponseEntity<Application>(application, HttpStatus.OK); } @Transactional @RequestMapping(value = Routes.APPLICATION_NOTES_INTERNAL, method=RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({ AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN }) @Timed public @ResponseBody ResponseEntity<Application> submitApplication(@PathVariable long applicationId, @RequestBody String notesInternal) { Application application = applicationRepository.findOne(applicationId); if (application == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } application.setNotesInternal(notesInternal); applicationRepository.save(application); return new ResponseEntity<Application>(application, HttpStatus.OK); } private <T extends Application> T saveApplicationFields(JsonNode request,T application) { JsonNode affiliateDetailsJsonNode = request.get("affiliateDetails"); JsonNode address = affiliateDetailsJsonNode.get("address"); JsonNode billing = affiliateDetailsJsonNode.get("billingAddress"); application.setUsername(sessionService.getUsernameOrNull()); AffiliateDetails affiliateDetails = application.getAffiliateDetails(); createAffiliateDetails(affiliateDetailsJsonNode, address, billing, affiliateDetails); affiliateDetailsRepository.save(affiliateDetails); application.setAffiliateDetails(affiliateDetails); application.setMember(findMemberFromAddressCountry(affiliateDetails)); if (application.getApprovalState() == null) { application.setApprovalState(ApprovalState.NOT_SUBMITTED); } if (application instanceof PrimaryApplication) { PrimaryApplication primaryApplication = (PrimaryApplication) application; // FIXME MLDS-32 MB we should update the client to match the new structure if (checkIfValidField(request, "type")){ primaryApplication.getAffiliateDetails().setType(AffiliateType.valueOf(getStringField(request, "type"))); } if (checkIfValidField(request, "subType")) { primaryApplication.getAffiliateDetails().setSubType(AffiliateSubType.valueOf(getStringField(request, "subType"))); } primaryApplication.getAffiliateDetails().setOtherText(getStringField(request, "otherText")); primaryApplication.setSnoMedLicense(Boolean.parseBoolean(getStringField(request, "snoMedTC"))); } return application; } private Member findMemberFromAddressCountry(AffiliateDetails affiliateDetails) { Validate.notNull(affiliateDetails.getAddress(), "Address is mandatory"); Validate.notNull(affiliateDetails.getAddress().getCountry(), "Address country is mandatory"); Validate.notNull(affiliateDetails.getAddress().getCountry().getMember(), "Address country member is mandatory"); return affiliateDetails.getAddress().getCountry().getMember(); } private void createAffiliateDetails(JsonNode affiliateDetailsJsonNode, JsonNode addressJsonNode, JsonNode billingJsonNode, AffiliateDetails affiliateDetails) { if (affiliateDetails == null) { affiliateDetails = new AffiliateDetails(); } affiliateDetails.setFirstName(getStringField(affiliateDetailsJsonNode, "firstName")); affiliateDetails.setLastName(getStringField(affiliateDetailsJsonNode, "lastName")); affiliateDetails.setLandlineNumber(getStringField(affiliateDetailsJsonNode, "landlineNumber")); affiliateDetails.setLandlineExtension(getStringField(affiliateDetailsJsonNode, "landlineExtension")); affiliateDetails.setMobileNumber(getStringField(affiliateDetailsJsonNode, "mobileNumber")); affiliateDetails.setEmail(getStringField(affiliateDetailsJsonNode, "email")); affiliateDetails.setAlternateEmail(getStringField(affiliateDetailsJsonNode, "alternateEmail")); affiliateDetails.setThirdEmail(getStringField(affiliateDetailsJsonNode, "thirdEmail")); affiliateDetails.setOrganizationName(getStringField(affiliateDetailsJsonNode, "organizationName")); affiliateDetails.setOrganizationTypeOther(getStringField(affiliateDetailsJsonNode, "organizationTypeOther")); if (checkIfValidField(affiliateDetailsJsonNode, "organizationType")) { affiliateDetails.setOrganizationType(OrganizationType.valueOf(getStringField(affiliateDetailsJsonNode, "organizationType"))); } String agreementTypeString = getStringField(affiliateDetailsJsonNode, "agreementType"); affiliateDetails.setAgreementType(Strings.isNullOrEmpty(agreementTypeString)?null:AgreementType.valueOf(agreementTypeString)); MailingAddress mailingAddress = new MailingAddress(); mailingAddress.setStreet(getStringField(addressJsonNode, "street")); mailingAddress.setCity(getStringField(addressJsonNode, "city")); mailingAddress.setPost(getStringField(addressJsonNode, "post")); if (addressJsonNode != null) { JsonNode country = addressJsonNode.get("country"); if (checkIfValidField(country, "isoCode2")) { mailingAddress.setCountry(countryRepository.findOne(getStringField(country, "isoCode2"))); } } affiliateDetails.setAddress(mailingAddress); MailingAddress billingAddress = new MailingAddress(); billingAddress.setStreet(getStringField(billingJsonNode, "street")); billingAddress.setCity(getStringField(billingJsonNode, "city")); billingAddress.setPost(getStringField(billingJsonNode, "post")); if(billingJsonNode != null) { JsonNode billingCountry = billingJsonNode.get("country"); if (checkIfValidField(billingCountry, "isoCode2")) { billingAddress.setCountry(countryRepository.findOne(getStringField(billingCountry, "isoCode2"))); } } affiliateDetails.setBillingAddress(billingAddress); } private String getStringField(JsonNode jsonNode, String attribute) { if (checkIfValidField(jsonNode, attribute)) { return jsonNode.get(attribute).asText(); } return ""; } private Boolean checkIfValidField(JsonNode jsonNode, String attribute) { if (jsonNode != null && jsonNode.get(attribute) != null) { String text = jsonNode.get(attribute).asText(); if(!Objects.equal(text, "") && !Objects.equal(text, "null")) { return true; } } return false; } public static class CreateApplicationDTO { Application.ApplicationType applicationType; String memberKey; public Application.ApplicationType getApplicationType() { return applicationType; } public void setApplicationType(Application.ApplicationType applicationType) { this.applicationType = applicationType; } public String getMemberKey() { return memberKey; } public void setMemberKey(String memberKey) { this.memberKey = memberKey; } } @RequestMapping(value = Routes.APPLICATIONS, method=RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER, AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public ResponseEntity<Application> createApplication(@RequestBody CreateApplicationDTO requestBody) { authorizationChecker.checkCanCreateApplication(requestBody); // FIXME MLDS-308 it is an error to try to create an extension application without a target member. Member member = (requestBody.getMemberKey() != null) ? memberRepository.findOneByKey(requestBody.getMemberKey()) : userMembershipAccessor.getMemberAssociatedWithUser(); Application application = applicationService.startNewApplication(requestBody.getApplicationType(), member); applicationAuditEvents.logCreationOf(application); HttpHeaders headers = new HttpHeaders(); headers.setLocation(routeLinkBuilder.toURLWithKeyValues(Routes.APPLICATION, "applicationId", application.getApplicationId())); ResponseEntity<Application> result = new ResponseEntity<Application>(application, headers, HttpStatus.CREATED); return result; } @RequestMapping(value = Routes.APPLICATION, method=RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER, AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Transactional @Timed public ResponseEntity<?> updateApplication(@PathVariable long applicationId, @RequestBody ObjectNode requestBody) throws IOException { Application original = applicationRepository.findOne(applicationId); Application updatedApplication = constructUpdatedApplication(requestBody, original); try { ApprovalState originalApprovalState = original.getApprovalState(); applicationService.doUpdate(original, updatedApplication); applicationAuditEvents.logApprovalStateChange(updatedApplication); applicationApprovalStateChangeNotifier.applicationApprovalStateChange(originalApprovalState, updatedApplication); } catch (IllegalArgumentException e) { return new ResponseEntity<String>("Forbidden change to application:" + e.getMessage(), HttpStatus.CONFLICT); } return new ResponseEntity<Application>(original, HttpStatus.OK); } @RequestMapping(value = Routes.APPLICATION, method=RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.USER, AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Transactional @Timed public ResponseEntity<?> deleteApplication(@PathVariable long applicationId) throws IOException { Application application = applicationRepository.findOne(applicationId); if (!application.getApplicationType().equals(ApplicationType.EXTENSION)){ return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } applicationRepository.delete(applicationId); applicationAuditEvents.logDeletionOf(application); return new ResponseEntity<Application>(HttpStatus.OK); } /** * Verify that the requestBody has the same applicationType, or default it to the original type * @param requestBody * @param original * @return a detached Application instance with all of the changes applied */ private Application constructUpdatedApplication(ObjectNode requestBody, Application original) throws IOException, JsonParseException, JsonMappingException, JsonProcessingException { ObjectNode treeCopyOfOriginal = objectMapper.readValue(objectMapper.writeValueAsString(original), ObjectNode.class); String typeTag = requestBody.get("applicationType")!= null?requestBody.get("applicationType").asText():null; String originalTypeTag = treeCopyOfOriginal.get("applicationType").asText(); if (!Strings.isNullOrEmpty(typeTag) && !typeTag.equals(originalTypeTag)) { throw new IllegalArgumentException("Can't change type of application via update"); } else { requestBody.put("applicationType", originalTypeTag); } Application updatedApplication = objectMapper.treeToValue(requestBody, Application.class); return updatedApplication; } }