diff --git a/app/src/main/java/app/Bootstrap.java b/app/src/main/java/app/Bootstrap.java index 3aa3a5f5..562f0962 100644 --- a/app/src/main/java/app/Bootstrap.java +++ b/app/src/main/java/app/Bootstrap.java @@ -19,25 +19,31 @@ import app.model.service.Service; import app.model.service.ServiceRepository; import app.model.service.ServiceType; -import app.model.service.servicedefinition.*; +import app.model.service.group.ServiceGroup; +import app.model.service.group.ServiceGroupRepository; +import app.model.service.keyword.ServiceKeyword; +import app.model.service.keyword.ServiceKeywordRepository; +import app.model.service.servicedefinition.AttributeDataType; +import app.model.service.servicedefinition.AttributeValue; +import app.model.service.servicedefinition.ServiceDefinition; +import app.model.service.servicedefinition.ServiceDefinitionAttribute; +import app.model.service.servicedefinition.ServiceDefinitionEntity; import app.model.servicerequest.ServiceRequest; import app.model.servicerequest.ServiceRequestStatus; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.Requires; import io.micronaut.core.convert.format.MapFormat; import io.micronaut.core.util.StringUtils; import io.micronaut.runtime.event.annotation.EventListener; import io.micronaut.runtime.server.event.ServerStartupEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import javax.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Requires(property = "app-data.bootstrap.data.enabled", value = StringUtils.TRUE) @ConfigurationProperties("app-data.bootstrap") @@ -48,14 +54,20 @@ public class Bootstrap { private final ServiceRepository serviceRepository; private final JurisdictionRepository jurisdictionRepository; + private final ServiceGroupRepository groupRepository; + private final ServiceKeywordRepository keywordRepository; private static final Logger LOG = LoggerFactory.getLogger(Bootstrap.class); - public Bootstrap(ServiceRepository serviceRepository, JurisdictionRepository jurisdictionRepository) { + public Bootstrap(ServiceRepository serviceRepository, JurisdictionRepository jurisdictionRepository, + ServiceGroupRepository groupRepository, ServiceKeywordRepository keywordRepository) { this.serviceRepository = serviceRepository; this.jurisdictionRepository = jurisdictionRepository; + this.groupRepository = groupRepository; + this.keywordRepository = keywordRepository; } @EventListener + @Transactional public void devData(ServerStartupEvent event) { if(data != null) { @@ -106,7 +118,20 @@ private void processAndStoreServices(List> services, boolean mult service.setServiceCode((String) svc.get("serviceCode")); service.setDescription((String) svc.get("description")); service.setType(ServiceType.valueOf(((String) svc.get("type")).toUpperCase())); - + List groups = (List) svc.get("groups"); + if (groups != null) { + groups.forEach(groupName -> { + ServiceGroup group = groupRepository.findByName(groupName).orElse(new ServiceGroup(groupName)); + service.addServiceGroup(group); + }); + } + List keywords = (List) svc.get("keywords"); + if (keywords != null) { + keywords.forEach(keywordName -> { + ServiceKeyword keyword = keywordRepository.findByName(keywordName).orElse(new ServiceKeyword(keywordName)); + service.getServiceKeywords().add(keyword); + }); + } Jurisdiction jurisdiction; if (multiJurisdictionIsSupported) { String jurisdictionStr = (String) svc.get("jurisdiction"); @@ -152,16 +177,16 @@ private void processAndStoreServices(List> services, boolean mult return serviceDefinitionAttribute; }).collect(Collectors.toList())); - ObjectMapper objectMapper = new ObjectMapper(); - try { - service.setServiceDefinitionJson(objectMapper.writeValueAsString(serviceDefinition)); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } + ServiceDefinitionEntity sde = new ServiceDefinitionEntity(); + sde.setActive(true); + sde.setVersion("1.0"); - Service savedService = serviceRepository.save(service); + sde.setDefinition(serviceDefinition); + service.getServiceDefinitions().add(sde); + sde.setService(service); + } + Service savedService = serviceRepository.update(service); if (svc.containsKey("serviceRequests")) { List serviceRequests = (List) svc.get("serviceRequests"); diff --git a/app/src/main/java/app/RootController.java b/app/src/main/java/app/RootController.java index c33648f2..2ccdfa6f 100644 --- a/app/src/main/java/app/RootController.java +++ b/app/src/main/java/app/RootController.java @@ -18,14 +18,17 @@ import app.dto.download.DownloadRequestsArgumentsDTO; import app.dto.service.ServiceDTO; import app.dto.service.ServiceList; -import app.dto.servicerequest.*; +import app.dto.servicerequest.GetServiceRequestsDTO; +import app.dto.servicerequest.PostRequestServiceRequestDTO; +import app.dto.servicerequest.PostResponseServiceRequestDTO; +import app.dto.servicerequest.ServiceRequestDTO; +import app.dto.servicerequest.ServiceRequestList; import app.model.service.servicedefinition.ServiceDefinition; import app.service.discovery.DiscoveryEndpointService; import app.service.jurisdiction.JurisdictionService; import app.service.service.ServiceService; import app.service.servicerequest.ServiceRequestService; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -36,17 +39,22 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.*; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Consumes; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Produces; +import io.micronaut.http.annotation.RequestBean; import io.micronaut.http.server.types.files.StreamedFile; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; - -import javax.validation.Valid; import java.net.MalformedURLException; import java.util.List; import java.util.Map; +import javax.validation.Valid; @Controller("/api") @Secured(SecurityRule.IS_ANONYMOUS) @@ -139,7 +147,7 @@ public HttpResponse indexXml(@Valid Pageable pageable, @Nullable String @Get(uris = {"/services/{serviceCode}{?jurisdiction_id}", "/services/{serviceCode}.json{?jurisdiction_id}"}) @Produces(MediaType.APPLICATION_JSON) @ExecuteOn(TaskExecutors.IO) - public String getServiceDefinitionJson(String serviceCode, @Nullable String jurisdiction_id) { + public ServiceDefinition getServiceDefinitionJson(String serviceCode, @Nullable String jurisdiction_id) { List errors = jurisdictionService.validateJurisdictionSupport(jurisdiction_id); if (!errors.isEmpty()) { return null; @@ -158,9 +166,7 @@ public String getServiceDefinitionXml(String serviceCode, @Nullable String juris } XmlMapper xmlMapper = XmlMapper.xmlBuilder().build(); - ObjectMapper objectMapper = new ObjectMapper(); - String serviceDefinitionStr = serviceService.getServiceDefinition(serviceCode, jurisdiction_id); - ServiceDefinition serviceDefinition = objectMapper.readValue(serviceDefinitionStr, ServiceDefinition.class); + ServiceDefinition serviceDefinition = serviceService.getServiceDefinition(serviceCode, jurisdiction_id); return xmlMapper.writeValueAsString(serviceDefinition); } diff --git a/app/src/main/java/app/model/service/Service.java b/app/src/main/java/app/model/service/Service.java index 09451e21..6cb51504 100644 --- a/app/src/main/java/app/model/service/Service.java +++ b/app/src/main/java/app/model/service/Service.java @@ -15,13 +15,20 @@ package app.model.service; import app.model.jurisdiction.Jurisdiction; +import app.model.service.group.ServiceGroup; +import app.model.service.keyword.ServiceKeyword; +import app.model.service.servicedefinition.ServiceDefinition; +import app.model.service.servicedefinition.ServiceDefinitionEntity; import app.model.servicerequest.ServiceRequest; +import java.util.HashSet; +import java.util.Set; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import javax.persistence.*; import java.util.ArrayList; import java.util.List; +import org.hibernate.annotations.Where; @Entity @Table(name = "services") @@ -37,14 +44,24 @@ public class Service { @JoinColumn(name = "jurisdiction_id") private Jurisdiction jurisdiction; - @Column(columnDefinition = "TEXT") - private String serviceDefinitionJson; - @Column(nullable = false, columnDefinition = "TEXT") private String serviceName; @Column(columnDefinition = "TEXT") private String description; + @Column(columnDefinition = "TEXT") + private String keywords; + @ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER) + @JoinTable(name="service_service_groups", + joinColumns={@JoinColumn(name="service_id")}, + inverseJoinColumns={@JoinColumn(name="service_group_id")}) + private Set serviceGroups = new HashSet<>(); + + @ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER) + @JoinTable(name="service_service_keywords", + joinColumns={@JoinColumn(name="service_id")}, + inverseJoinColumns={@JoinColumn(name="service_keyword_id")}) + private Set serviceKeywords = new HashSet<>(); private boolean metadata = false; @@ -55,6 +72,12 @@ public class Service { @OnDelete(action = OnDeleteAction.CASCADE) private List serviceRequests = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "") + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "service_id") + @Where(clause = "active = true") + private List serviceDefinitions = new ArrayList<>(); + public Service(String serviceName) { this.serviceName = serviceName; @@ -110,14 +133,10 @@ public void setType(ServiceType type) { this.type = type; } - public String getServiceDefinitionJson() { - return serviceDefinitionJson; - } - - public void setServiceDefinitionJson(String serviceDefinitionJson) { - this.serviceDefinitionJson = serviceDefinitionJson; + public ServiceDefinition getServiceDefinitionJson() { + if (serviceDefinitions.isEmpty()) return null; + return serviceDefinitions.get(0).getDefinition(); } - public Long getId() { return id; } @@ -137,4 +156,39 @@ public void setServiceRequests(List serviceRequests) { public void addServiceRequest(ServiceRequest serviceRequest) { serviceRequests.add(serviceRequest); } + + public List getServiceDefinitions() { + return serviceDefinitions; + } + + public void setServiceDefinitions( + List serviceDefinitions) { + this.serviceDefinitions = serviceDefinitions; + } + + public String getKeywords() { + return keywords; + } + + public void setKeywords(String keywords) { + this.keywords = keywords; + } + + public void addServiceGroup(ServiceGroup serviceGroup) { + if (serviceGroups.isEmpty()) { + serviceGroups.add(serviceGroup); + } + } + + public void setServiceGroups(Set serviceGroups) { + this.serviceGroups = serviceGroups; + } + + public Set getServiceKeywords() { + return serviceKeywords; + } + + public void setServiceKeywords(Set serviceKeywords) { + this.serviceKeywords = serviceKeywords; + } } diff --git a/app/src/main/java/app/model/service/group/ServiceGroup.java b/app/src/main/java/app/model/service/group/ServiceGroup.java new file mode 100644 index 00000000..603ab4b7 --- /dev/null +++ b/app/src/main/java/app/model/service/group/ServiceGroup.java @@ -0,0 +1,42 @@ +package app.model.service.group; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "service_groups") +public class ServiceGroup { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + + public ServiceGroup() { + } + + public ServiceGroup(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/model/service/group/ServiceGroupRepository.java b/app/src/main/java/app/model/service/group/ServiceGroupRepository.java new file mode 100644 index 00000000..3b4062b1 --- /dev/null +++ b/app/src/main/java/app/model/service/group/ServiceGroupRepository.java @@ -0,0 +1,11 @@ +package app.model.service.group; + +import io.micronaut.data.annotation.Repository; +import io.micronaut.data.repository.PageableRepository; +import java.util.Optional; + +@Repository +public interface ServiceGroupRepository extends PageableRepository { + + Optional findByName(String name); +} diff --git a/app/src/main/java/app/model/service/keyword/ServiceKeyword.java b/app/src/main/java/app/model/service/keyword/ServiceKeyword.java new file mode 100644 index 00000000..4fc1e4ea --- /dev/null +++ b/app/src/main/java/app/model/service/keyword/ServiceKeyword.java @@ -0,0 +1,45 @@ +package app.model.service.keyword; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "service_keywords") +public class ServiceKeyword { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + + public ServiceKeyword() { + } + + public ServiceKeyword(Long id) { + this.id = id; + } + + public ServiceKeyword(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/app/src/main/java/app/model/service/keyword/ServiceKeywordRepository.java b/app/src/main/java/app/model/service/keyword/ServiceKeywordRepository.java new file mode 100644 index 00000000..5e4dbae9 --- /dev/null +++ b/app/src/main/java/app/model/service/keyword/ServiceKeywordRepository.java @@ -0,0 +1,25 @@ +// Copyright 2023 Libre311 Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app.model.service.keyword; + +import io.micronaut.data.annotation.Repository; +import io.micronaut.data.repository.PageableRepository; +import java.util.Optional; + +@Repository +public interface ServiceKeywordRepository extends PageableRepository { + + Optional findByName(String name); +} diff --git a/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionConverter.java b/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionConverter.java new file mode 100644 index 00000000..e2ce07ea --- /dev/null +++ b/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionConverter.java @@ -0,0 +1,39 @@ +package app.model.service.servicedefinition; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.inject.Singleton; +import javax.persistence.AttributeConverter; + +@Singleton +public class ServiceDefinitionConverter implements + AttributeConverter { + private final ObjectMapper objectMapper; + + public ServiceDefinitionConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public String convertToDatabaseColumn(ServiceDefinition serviceDefinition) { + if (serviceDefinition != null) { + try { + return objectMapper.writeValueAsString(serviceDefinition); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return null; + } + + @Override + public ServiceDefinition convertToEntityAttribute(String s) { + if (s != null && !s.isBlank()) { + try { + return objectMapper.readValue(s, ServiceDefinition.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return null; + } +} diff --git a/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionEntity.java b/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionEntity.java new file mode 100644 index 00000000..3c191807 --- /dev/null +++ b/app/src/main/java/app/model/service/servicedefinition/ServiceDefinitionEntity.java @@ -0,0 +1,81 @@ +package app.model.service.servicedefinition; + + +import app.model.service.Service; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "service_definitions") +public class ServiceDefinitionEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String version; + + private Boolean active; + + @Column(columnDefinition = "TEXT") + @Convert(converter = ServiceDefinitionConverter.class) + private ServiceDefinition definition; + + @ManyToOne + private Service service; + + public ServiceDefinitionEntity() { + } + + public ServiceDefinitionEntity(String version, Boolean active, ServiceDefinition definition) { + this.version = version; + this.active = active; + this.definition = definition; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public ServiceDefinition getDefinition() { + return definition; + } + + public void setDefinition(ServiceDefinition definition) { + this.definition = definition; + } + + public Service getService() { + return service; + } + + public void setService(Service service) { + this.service = service; + } +} diff --git a/app/src/main/java/app/model/servicerequest/MissingActiveServiceDefinitionException.java b/app/src/main/java/app/model/servicerequest/MissingActiveServiceDefinitionException.java new file mode 100644 index 00000000..8b4c8843 --- /dev/null +++ b/app/src/main/java/app/model/servicerequest/MissingActiveServiceDefinitionException.java @@ -0,0 +1,5 @@ +package app.model.servicerequest; + +public class MissingActiveServiceDefinitionException extends RuntimeException { + +} diff --git a/app/src/main/java/app/model/servicerequest/ServiceRequest.java b/app/src/main/java/app/model/servicerequest/ServiceRequest.java index b6715eb6..1b216097 100644 --- a/app/src/main/java/app/model/servicerequest/ServiceRequest.java +++ b/app/src/main/java/app/model/servicerequest/ServiceRequest.java @@ -16,6 +16,7 @@ import app.model.jurisdiction.Jurisdiction; import app.model.service.Service; +import app.model.service.servicedefinition.ServiceDefinitionEntity; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.annotation.DateCreated; import io.micronaut.data.annotation.DateUpdated; @@ -42,6 +43,10 @@ public class ServiceRequest { @JoinColumn(name = "jurisdiction_id") private Jurisdiction jurisdiction; + @ManyToOne + @JoinColumn(name = "service_definition_id") + private ServiceDefinitionEntity serviceDefinition; + @Nullable @Column(columnDefinition = "TEXT") private String attributesJson; @@ -345,7 +350,6 @@ public Instant getClosedDate() { public void setClosedDate(@Nullable Instant closedDate) { this.closedDate = closedDate; } - public void setExpectedDate(@Nullable Instant expectedDate) { this.expectedDate = expectedDate; } @@ -354,6 +358,14 @@ public void setExpectedDate(@Nullable Instant expectedDate) { public String getAgencyEmail() { return agencyEmail; } + public ServiceDefinitionEntity getServiceDefinition() { + return serviceDefinition; + } + + public void setServiceDefinition( + ServiceDefinitionEntity serviceDefinition) { + this.serviceDefinition = serviceDefinition; + } public void setAgencyEmail(@Nullable String agencyEmail) { this.agencyEmail = agencyEmail; diff --git a/app/src/main/java/app/service/service/ServiceService.java b/app/src/main/java/app/service/service/ServiceService.java index 5ca7c33b..bf96f9e3 100644 --- a/app/src/main/java/app/service/service/ServiceService.java +++ b/app/src/main/java/app/service/service/ServiceService.java @@ -22,6 +22,7 @@ import app.model.service.Service; import app.model.service.ServiceRepository; import app.model.service.servicedefinition.ServiceDefinition; +import app.model.service.servicedefinition.ServiceDefinitionEntity; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.data.model.Page; @@ -54,7 +55,7 @@ public Page findAll(Pageable pageable, String jurisdictionId) { return servicePage.map(ServiceDTO::new); } - public String getServiceDefinition(String serviceCode, String jurisdictionId) { + public ServiceDefinition getServiceDefinition(String serviceCode, String jurisdictionId) { Optional serviceOptional; if (jurisdictionId == null) { serviceOptional = serviceRepository.findByServiceCode(serviceCode); @@ -65,7 +66,7 @@ public String getServiceDefinition(String serviceCode, String jurisdictionId) { if (serviceOptional.isEmpty()) { LOG.error("Service not found."); return null; - } else if (serviceOptional.get().getServiceDefinitionJson() == null) { + } else if (serviceOptional.get().getServiceDefinitions().isEmpty()) { LOG.error("Service Definition is null."); return null; } @@ -96,9 +97,10 @@ public ServiceDTO createService(CreateServiceDTO serviceDTO) { Service service = new Service(); if (serviceDTO.getServiceDefinitionJson() != null) { - if (serviceDefinitionFormatIsValid(serviceDTO.getServiceDefinitionJson())) { + ServiceDefinition sd = serviceDefinitionFormatIsValid(serviceDTO.getServiceDefinitionJson()); + if (sd != null) { service.setMetadata(true); - service.setServiceDefinitionJson(serviceDTO.getServiceDefinitionJson()); + service.getServiceDefinitions().add(new ServiceDefinitionEntity("1", true, sd)); } else { LOG.error("Service Definition JSON format is invalid."); return null; @@ -142,9 +144,18 @@ public ServiceDTO updateService(Long serviceId, UpdateServiceDTO serviceDTO) { service.setServiceName(serviceDTO.getServiceName()); } if (serviceDTO.getServiceDefinitionJson() != null) { - if (serviceDefinitionFormatIsValid(serviceDTO.getServiceDefinitionJson())) { + ServiceDefinition sd = serviceDefinitionFormatIsValid(serviceDTO.getServiceDefinitionJson()); + if (sd != null) { service.setMetadata(true); - service.setServiceDefinitionJson(serviceDTO.getServiceDefinitionJson()); + ServiceDefinitionEntity existingSde = null; + if (service.getServiceDefinitions().isEmpty()) { + existingSde = new ServiceDefinitionEntity("1", true, sd); + } else { + existingSde = service.getServiceDefinitions().get(0); + existingSde.setActive(false); + } + service.getServiceDefinitions().add(new ServiceDefinitionEntity( + getNextVersionNumber(existingSde), true, sd)); } else { LOG.error("Service Definition JSON format is invalid."); return null; @@ -154,6 +165,10 @@ public ServiceDTO updateService(Long serviceId, UpdateServiceDTO serviceDTO) { return new ServiceDTO(serviceRepository.update(service)); } + private static String getNextVersionNumber(ServiceDefinitionEntity existingSde) { + return ""+(Long.parseLong(existingSde.getVersion()) + 1); + } + private boolean serviceCodeAlreadyExists(boolean jurisdictionSupportEnabled, String serviceCode, String jurisdictionId) { boolean serviceCodeAlreadyExists; if (jurisdictionSupportEnabled) { @@ -168,12 +183,13 @@ private boolean serviceCodeAlreadyExists(boolean jurisdictionSupportEnabled, Str return false; } - private boolean serviceDefinitionFormatIsValid(String serviceDefinitionJson) { + private ServiceDefinition serviceDefinitionFormatIsValid(String serviceDefinitionJson) { + ServiceDefinition sd; try { - (new ObjectMapper()).readValue(serviceDefinitionJson, ServiceDefinition.class); + sd = (new ObjectMapper()).readValue(serviceDefinitionJson, ServiceDefinition.class); } catch (JsonProcessingException e) { - return false; + return null; } - return true; + return sd; } } diff --git a/app/src/main/java/app/service/servicerequest/ServiceRequestService.java b/app/src/main/java/app/service/servicerequest/ServiceRequestService.java index 3c0c6879..71f99ddd 100644 --- a/app/src/main/java/app/service/servicerequest/ServiceRequestService.java +++ b/app/src/main/java/app/service/servicerequest/ServiceRequestService.java @@ -23,6 +23,8 @@ import app.model.service.servicedefinition.AttributeValue; import app.model.service.servicedefinition.ServiceDefinition; import app.model.service.servicedefinition.ServiceDefinitionAttribute; +import app.model.service.servicedefinition.ServiceDefinitionEntity; +import app.model.servicerequest.MissingActiveServiceDefinitionException; import app.model.servicerequest.ServiceRequest; import app.model.servicerequest.ServiceRequestRepository; import app.model.servicerequest.ServiceRequestStatus; @@ -86,15 +88,15 @@ private static ServiceRequestDTO convertToDTO(ServiceRequest serviceRequest) { } public PostResponseServiceRequestDTO createServiceRequest(HttpRequest request, PostRequestServiceRequestDTO serviceRequestDTO) { - if (!reCaptchaService.verifyReCaptcha(serviceRequestDTO.getgRecaptchaResponse())) { - LOG.error("ReCaptcha verification failed."); - return null; - } + if (!reCaptchaService.verifyReCaptcha(serviceRequestDTO.getgRecaptchaResponse())) { + LOG.error("ReCaptcha verification failed."); + return null; + } - if (!validMediaUrl(serviceRequestDTO.getMediaUrl())) { - LOG.error("Media URL is invalid."); - return null; - } + if (!validMediaUrl(serviceRequestDTO.getMediaUrl())) { + LOG.error("Media URL is invalid."); + return null; + } Optional serviceByServiceCodeOptional; if (serviceRequestDTO.getJurisdictionId() == null) { @@ -104,55 +106,64 @@ public PostResponseServiceRequestDTO createServiceRequest(HttpRequest request serviceRequestDTO.getServiceCode(), serviceRequestDTO.getJurisdictionId()); } - if (serviceByServiceCodeOptional.isEmpty()) { - LOG.error("Corresponding service not found."); - return null; // todo return 'corresponding service not found - } - - if (serviceRequestDTO.getJurisdictionId() != null && - !serviceRequestDTO.getJurisdictionId().equals(serviceByServiceCodeOptional.get().getJurisdiction().getId())) { - LOG.error("Mismatch between jurisdiction_id provided and Service's associated jurisdiction."); - return null; - } - - // validate if a location is provided - boolean latLongProvided = StringUtils.hasText(serviceRequestDTO.getLatitude()) && - StringUtils.hasText(serviceRequestDTO.getLongitude()); - - if (!latLongProvided && - StringUtils.isEmpty(serviceRequestDTO.getAddressString()) && - StringUtils.isEmpty(serviceRequestDTO.getAddressId())) { - LOG.error("Address or lat/long not provided."); - return null; // todo throw exception - } - - // validate if additional attributes are required - List requestAttributes = null; - Service service = serviceByServiceCodeOptional.get(); - if (service.isMetadata()) { - // get service definition - String serviceDefinitionJson = service.getServiceDefinitionJson(); - if (serviceDefinitionJson == null || serviceDefinitionJson.isBlank()) { - LOG.error("Service definition does not exists despite service requiring it."); - return null; // should not be in this state and admin needs to be aware. - } - - requestAttributes = buildUserResponseAttributesFromRequest(request, serviceDefinitionJson); - if (requestAttributes.isEmpty()) { - LOG.error("Submitted Service Request does not contain any attribute values."); - return null; // todo throw exception - must provide attributes - } - if (!requestAttributesHasAllRequiredServiceDefinitionAttributes(serviceDefinitionJson, requestAttributes)) { - LOG.error("Submitted Service Request does not contain required attribute values."); - return null; // todo throw exception (validation) - } - } + if (serviceByServiceCodeOptional.isEmpty()) { + LOG.error("Corresponding service not found."); + return null; // todo return 'corresponding service not found + } + + if (serviceRequestDTO.getJurisdictionId() != null && + !serviceRequestDTO.getJurisdictionId() + .equals(serviceByServiceCodeOptional.get().getJurisdiction().getId())) { + LOG.error( + "Mismatch between jurisdiction_id provided and Service's associated jurisdiction."); + return null; + } + + // validate if a location is provided + boolean latLongProvided = StringUtils.hasText(serviceRequestDTO.getLatitude()) && + StringUtils.hasText(serviceRequestDTO.getLongitude()); + + if (!latLongProvided && + StringUtils.isEmpty(serviceRequestDTO.getAddressString()) && + StringUtils.isEmpty(serviceRequestDTO.getAddressId())) { + LOG.error("Address or lat/long not provided."); + return null; // todo throw exception + } + + // validate if additional attributes are required + List requestAttributes = null; + Service service = serviceByServiceCodeOptional.get(); + if (service.isMetadata()) { + // get service definition + List serviceDefinitions = service.getServiceDefinitions(); + if (serviceDefinitions.isEmpty() || serviceDefinitions.get(0).getDefinition() == null) { + LOG.error("Service definition does not exists despite service requiring it."); + return null; // should not be in this state and admin needs to be aware. + } + ServiceDefinition serviceDefinition = serviceDefinitions.get(0).getDefinition(); + requestAttributes = buildUserResponseAttributesFromRequest(request, serviceDefinition); + if (requestAttributes.isEmpty()) { + LOG.error("Submitted Service Request does not contain any attribute values."); + return null; // todo throw exception - must provide attributes + } + if (!requestAttributesHasAllRequiredServiceDefinitionAttributes(serviceDefinition, + requestAttributes)) { + LOG.error("Submitted Service Request does not contain required attribute values."); + return null; // todo throw exception (validation) + } + } ServiceRequest serviceRequest = transformDtoToServiceRequest(serviceRequestDTO, service); if (requestAttributes != null) { ObjectMapper objectMapper = new ObjectMapper(); try { serviceRequest.setAttributesJson(objectMapper.writeValueAsString(requestAttributes)); + serviceRequest.setServiceDefinition( + service.getServiceDefinitions().stream() + .filter(sde -> sde.getActive()).findFirst().orElseThrow( + ()-> new MissingActiveServiceDefinitionException() + ) + ); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -166,13 +177,10 @@ private boolean validMediaUrl(String mediaUrl) { return mediaUrl.startsWith(storageUrlUtil.getBucketUrlString()); } - private boolean requestAttributesHasAllRequiredServiceDefinitionAttributes(String serviceDefinitionJson, List requestAttributes) { + private boolean requestAttributesHasAllRequiredServiceDefinitionAttributes(ServiceDefinition serviceDefinition, List requestAttributes) { // deserialize - ObjectMapper objectMapper = new ObjectMapper(); boolean containsAllRequiredAttrs = false; - try { // collect all required attributes - ServiceDefinition serviceDefinition = objectMapper.readValue(serviceDefinitionJson, ServiceDefinition.class); List requiredCodes = serviceDefinition.getAttributes().stream() .filter(ServiceDefinitionAttribute::isRequired) .map(ServiceDefinitionAttribute::getCode) @@ -184,22 +192,11 @@ private boolean requestAttributesHasAllRequiredServiceDefinitionAttributes(Strin .collect(Collectors.toList()); containsAllRequiredAttrs = requestCodes.containsAll(requiredCodes); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } return containsAllRequiredAttrs; } - private List buildUserResponseAttributesFromRequest(HttpRequest request, String serviceDefinitionJson) { - ObjectMapper objectMapper = new ObjectMapper(); - ServiceDefinition serviceDefinition; - try { - serviceDefinition = objectMapper.readValue(serviceDefinitionJson, ServiceDefinition.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - + private List buildUserResponseAttributesFromRequest(HttpRequest request, ServiceDefinition serviceDefinition) { Optional body = request.getBody(Map.class); List attributes = new ArrayList<>(); diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index ae29af6c..1acf2241 100644 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -70,6 +70,7 @@ jpa: hibernate: hbm2ddl: auto: ${LIBRE311_AUTO_SCHEMA_GEN:update} +# show_sql: true netty: default: allocator: diff --git a/settings.gradle b/settings.gradle index 65dd9dd6..3e0ca93a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'app', 'frontend' \ No newline at end of file +include 'app' \ No newline at end of file