diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTController.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTController.java index daefed2c..d0dafe84 100755 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTController.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTController.java @@ -37,18 +37,17 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.io.ByteArrayOutputStream; -import java.net.URI; import java.util.List; import java.util.UUID; /** - * Modern RESTful controller for managing ApplicationInformation resources according to the + * Modern RESTful controller for reading ApplicationInformation resources according to the * Green Button Alliance ESPI (Energy Services Provider Interface) specification. *

- * This controller uses ApplicationInformationService and modern Spring Boot 3.5 patterns. + * GET-only. The CRUD write endpoints (POST/PUT/DELETE) are deferred — they are admin/sandbox-DB + * management APIs to be delivered in the separate admin-CRUD track (see issue #119 build plan). */ @RestController @RequestMapping("/espi/1_1/resource/ApplicationInformation") @@ -119,101 +118,4 @@ public ResponseEntity getApplicationInformation( .contentType(MediaType.APPLICATION_XML) .body(out.toByteArray()); } - - /** - * Creates a new ApplicationInformation resource. - * - * @param dto ApplicationInformationDto to create - * @return created ApplicationInformationDto - */ - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - summary = "Create ApplicationInformation", - description = "Creates a new ApplicationInformation resource" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Successfully created ApplicationInformation"), - @ApiResponse(responseCode = "400", description = "Invalid ApplicationInformation data") - }) - @PreAuthorize("hasAuthority('SCOPE_DataCustodian_Admin_Access')") - public ResponseEntity createApplicationInformation( - @Parameter(description = "ApplicationInformation data to create", required = true) - @RequestBody ApplicationInformationDto dto) { - - ApplicationInformationEntity entity = applicationInformationService.fromDto(dto); - ApplicationInformationEntity savedEntity = applicationInformationService.save(entity); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(savedEntity.getId()) - .toUri(); - - return ResponseEntity.created(location).body(savedEntity); - } - - /** - * Updates an existing ApplicationInformation resource. - * - * @param applicationInformationId Unique identifier for the ApplicationInformation to update - * @param dto Updated ApplicationInformationDto data - * @return updated ApplicationInformationEntity - */ - @PutMapping(value = "/{applicationInformationId}", - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - summary = "Update ApplicationInformation", - description = "Updates an existing ApplicationInformation resource" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully updated ApplicationInformation"), - @ApiResponse(responseCode = "404", description = "ApplicationInformation not found"), - @ApiResponse(responseCode = "400", description = "Invalid ApplicationInformation data") - }) - @PreAuthorize("hasAuthority('SCOPE_DataCustodian_Admin_Access')") - public ResponseEntity updateApplicationInformation( - @Parameter(description = "Unique identifier of the ApplicationInformation to update", required = true) - @PathVariable UUID applicationInformationId, - @Parameter(description = "Updated ApplicationInformation data", required = true) - @RequestBody ApplicationInformationDto dto) { - - ApplicationInformationEntity existing = applicationInformationService.findById(applicationInformationId); - if (existing == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "ApplicationInformation not found"); - } - - ApplicationInformationEntity entity = applicationInformationService.fromDto(dto); - entity.setId(applicationInformationId); - ApplicationInformationEntity updatedEntity = applicationInformationService.save(entity); - - return ResponseEntity.ok(updatedEntity); - } - - /** - * Deletes an ApplicationInformation resource. - * - * @param applicationInformationId Unique identifier for the ApplicationInformation to delete - */ - @DeleteMapping("/{applicationInformationId}") - @Operation( - summary = "Delete ApplicationInformation", - description = "Deletes an ApplicationInformation resource" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Successfully deleted ApplicationInformation"), - @ApiResponse(responseCode = "404", description = "ApplicationInformation not found") - }) - @PreAuthorize("hasAuthority('SCOPE_DataCustodian_Admin_Access')") - public ResponseEntity deleteApplicationInformation( - @Parameter(description = "Unique identifier of the ApplicationInformation to delete", required = true) - @PathVariable UUID applicationInformationId) { - - ApplicationInformationEntity existing = applicationInformationService.findById(applicationInformationId); - if (existing == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "ApplicationInformation not found"); - } - - applicationInformationService.deleteById(applicationInformationId); - return ResponseEntity.noContent().build(); - } -} \ No newline at end of file +} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/AuthorizationRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/AuthorizationRESTController.java.disabled deleted file mode 100755 index 22c2acb6..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/AuthorizationRESTController.java.disabled +++ /dev/null @@ -1,429 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - -import com.sun.syndication.io.FeedException; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.greenbuttonalliance.espi.common.domain.usage.AuthorizationEntity; -import org.greenbuttonalliance.espi.common.service.AuthorizationService; -import org.greenbuttonalliance.espi.common.service.RetailCustomerService; -import org.greenbuttonalliance.espi.common.service.DtoExportService; -import org.greenbuttonalliance.espi.datacustodian.utils.VerifyURLParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -/** - * RESTful controller for managing AuthorizationEntity resources according to the - * Green Button Alliance ESPI (Energy Services Provider Interface) specification. - * - * AuthorizationEntity represents OAuth2 access permissions and tokens for - * third-party applications to access customer energy data. - */ -// @RestController - COMMENTED OUT: Replaced with modern AuthorizationController -// @RequestMapping("/espi/1_1/resource") -// @Tag(name = "Authorization", description = "OAuth2 AuthorizationEntity and Access Token Management API") -// @Component -public class AuthorizationRESTController { - - private final AuthorizationService authorizationService; - private final RetailCustomerService retailCustomerService; - private final DtoExportService dtoExportService; - - @Autowired - public AuthorizationRESTController( - AuthorizationService authorizationService, - RetailCustomerService retailCustomerService, - DtoExportService dtoExportService) { - this.authorizationService = authorizationService; - this.retailCustomerService = retailCustomerService; - this.dtoExportService = dtoExportService; - } - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public void handleGenericException() { - // Generic exception handler - } - - // ================================ - // ROOT AuthorizationEntity Collection APIs - // ================================ - - /** - * Retrieves all AuthorizationEntity resources (root level access). - * - * @param request HTTP servlet request for authorization context - * @param response HTTP response for streaming ATOM XML content - * @param params Query parameters for filtering and pagination - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM feed generation fails - */ - @GetMapping(value = "/Authorization", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get AuthorizationEntity Collection", - description = "Retrieves all authorized AuthorizationEntity resources with optional filtering and pagination. " + - "Returns an ATOM feed containing OAuth2 authorization and access token information. " + - "Access scope depends on the requesting client's authorization level." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved AuthorizationEntity collection", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM feed containing AuthorizationEntity entries")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid query parameters provided" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to authorization data" - ) - }) - public void getAuthorizationCollection( - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Query parameters for filtering (published-max, published-min, updated-max, updated-min, max-results, start-index)") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/Authorization", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - String accessToken = request.getHeader("authorization").replace( - "Bearer ", ""); - AuthorizationEntity authorization = authorizationService.findByAccessToken(accessToken); - - // TODO: Implement authorization export methods in DtoExportService - // Determine access scope: data_custodian_admin gets everything, - // third-party clients get restricted scope - if (authorization.getApplicationInformation().getClientId() - .equals("data_custodian_admin")) { - // // dtoExportService.exportAuthorization(...); params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } else { - // Third-party access with restricted scope - // // dtoExportService.exportAuthorization(...); params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Retrieves a specific AuthorizationEntity resource by ID (root level access). - * - * @param response HTTP response for streaming ATOM XML content - * @param authorizationId Unique identifier for the Authorization - * @param params Query parameters for export filtering - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM entry generation fails - */ - @GetMapping(value = "/Authorization/{authorizationId}", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get AuthorizationEntity by ID", - description = "Retrieves a specific AuthorizationEntity resource by its unique identifier. " + - "Returns an ATOM entry containing OAuth2 authorization details including " + - "access tokens, scope, and application information." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved Authorization", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM entry containing AuthorizationEntity details")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid authorizationId or query parameters" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to this authorization data" - ), - @ApiResponse( - responseCode = "404", - description = "AuthorizationEntity not found" - ) - }) - public void getAuthorization( - HttpServletResponse response, - @Parameter(description = "Unique identifier of the Authorization", required = true) - @PathVariable Long authorizationId, - @Parameter(description = "Query parameters for export filtering") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/Authorization/{authorizationId}", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - // dtoExportService.exportAuthorization(...); params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PostMapping(value = "/Authorization", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Create Authorization", - description = "Creates a new AuthorizationEntity resource for OAuth2 access control." - ) - public void createAuthorization( - HttpServletResponse response, - @RequestParam Map params, - @RequestBody InputStream stream) throws IOException { - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - AuthorizationEntity authorization = - this.authorizationService.importResource(stream); - // dtoExportService.exportAuthorization(...); params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PutMapping(value = "/Authorization/{authorizationId}", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Update Authorization", - description = "Updates an existing AuthorizationEntity resource." - ) - public void updateAuthorization( - HttpServletResponse response, - @PathVariable Long authorizationId, - @RequestParam Map params, - @RequestBody InputStream stream) throws IOException, FeedException { - - try { - AuthorizationEntity authorization = - authorizationService.findById(authorizationId); - - if (authorization != null) { - AuthorizationEntity newAuthorizationEntity = - authorizationService.importResource(stream); - authorization.merge(newAuthorizationEntity); - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @DeleteMapping("/Authorization/{authorizationId}") - @Operation( - summary = "Delete Authorization", - description = "Removes an AuthorizationEntity resource and revokes associated OAuth2 tokens." - ) - public void deleteAuthorization( - HttpServletResponse response, - @PathVariable Long authorizationId) { - - try { - AuthorizationEntity authorization = authorizationService.findById(authorizationId); - - if (authorization != null) { - String accessToken = authorization.getAccessToken(); - authorizationService.delete(authorization); - // Note: Token revocation handled by OAuth2 server - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - // ============================================= - // RetailCustomer-scoped AuthorizationEntity Collection APIs - // ============================================= - - @GetMapping(value = "/RetailCustomer/{retailCustomerId}/Authorization", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get Customer Authorizations", - description = "Retrieves all AuthorizationEntity resources for a specific retail customer." - ) - public void getCustomerAuthorizations( - HttpServletResponse response, - @PathVariable Long retailCustomerId, - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/RetailCustomer/{retailCustomerId}/Authorization", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - // dtoExportService.exportAuthorization(...); params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @GetMapping(value = "/RetailCustomer/{retailCustomerId}/Authorization/{authorizationId}", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get Customer AuthorizationEntity by ID", - description = "Retrieves a specific AuthorizationEntity resource for a retail customer." - ) - public void getCustomerAuthorization( - HttpServletResponse response, - @PathVariable Long retailCustomerId, - @PathVariable Long authorizationId, - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/RetailCustomer/{retailCustomerId}/Authorization/{authorizationId}", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - // dtoExportService.exportAuthorization(retailCustomerId, authorizationId, response.getOutputStream(), params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PostMapping(value = "/RetailCustomer/{retailCustomerId}/Authorization", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Create Customer Authorization", - description = "Creates a new AuthorizationEntity resource for a specific retail customer." - ) - public void createCustomerAuthorization( - HttpServletResponse response, - @PathVariable Long retailCustomerId, - @RequestParam Map params, - @RequestBody InputStream stream) throws IOException { - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - AuthorizationEntity authorization = - this.authorizationService.importResource(stream); - // retailCustomerService.associateByUUID(retailCustomerId, authorization.getUUID());\n\t\t\t// TODO: Associate authorization with retail customer - // dtoExportService.exportAuthorization(retailCustomerId, authorization.getId(), response.getOutputStream(), params); - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PutMapping(value = "/RetailCustomer/{retailCustomerId}/Authorization/{authorizationId}", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Update Customer Authorization", - description = "Updates an existing AuthorizationEntity resource for a retail customer." - ) - public void updateCustomerAuthorization( - HttpServletResponse response, - @PathVariable Long retailCustomerId, - @PathVariable Long authorizationId, - @RequestParam Map params, - @RequestBody InputStream stream) throws IOException, FeedException { - - try { - AuthorizationEntity authorization = authorizationService.findById( - retailCustomerId, authorizationId); - - if (authorization != null) { - AuthorizationEntity newAuthorizationEntity = - authorizationService.importResource(stream); - authorization.merge(newAuthorizationEntity); - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @DeleteMapping("/RetailCustomer/{retailCustomerId}/Authorization/{authorizationId}") - @Operation( - summary = "Delete Customer Authorization", - description = "Removes an AuthorizationEntity resource for a retail customer and revokes associated OAuth2 tokens." - ) - public void deleteCustomerAuthorization( - HttpServletResponse response, - @PathVariable Long retailCustomerId, - @PathVariable Long authorizationId) { - - try { - AuthorizationEntity authorization = authorizationService.findById( - retailCustomerId, authorizationId); - - if (authorization != null) { - String accessToken = authorization.getAccessToken(); - authorizationService.delete(authorization); - // Note: Token revocation handled by OAuth2 server - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - -} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/BatchRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/BatchRESTController.java.disabled deleted file mode 100644 index 6d012774..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/BatchRESTController.java.disabled +++ /dev/null @@ -1,643 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - -import com.sun.syndication.io.FeedException; -import org.greenbuttonalliance.espi.common.domain.usage.ApplicationInformationEntity; -import org.greenbuttonalliance.espi.common.domain.usage.AuthorizationEntity; -import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; -import org.greenbuttonalliance.espi.common.service.*; -import org.greenbuttonalliance.espi.common.repositories.usage.*; -import org.greenbuttonalliance.espi.datacustodian.utils.VerifyURLParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; - -// TODO: Re-enable after implementing missing services (ImportService, NotificationService) -// @RestController -// @RequestMapping("/espi/1_1/resource") -// @Tag(name = "Batch Operations", description = "Green Button Bulk Data Processing API") -// COMMENTED OUT: Complex batch operations not needed for basic OAuth2 resource server functionality -// @Component -public class BatchRESTController { - - // TODO: Implement these services in openespi-common - // private final ImportService importService; - // private final ResourceRepository resourceService; - private final AuthorizationService authorizationService; - // private final NotificationService notificationService; - private final RetailCustomerService retailCustomerService; - private final DtoExportService exportService; - - @Autowired - public BatchRESTController( - // ImportService importService, - // ResourceRepository resourceService, - AuthorizationService authorizationService, - // NotificationService notificationService, - RetailCustomerService retailCustomerService, - DtoExportService exportService) { - // this.importService = importService; - // this.resourceService = resourceService; - this.authorizationService = authorizationService; - // this.notificationService = notificationService; - this.retailCustomerService = retailCustomerService; - this.exportService = exportService; - } - - /** - * Supports the upload (or import) of Green Button DMD files. Simply send - * the DMD file within the POST data. - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {upload_access_token}] - * - * @param response - * HTTP Servlet Response - * @param retailCustomerId - * The locally unique identifier of a Retail Customer - NOTE PII - * @param params - * HTTP Query Parameters - * @param stream - * An input stream - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * POST /espi/1_1/resource/Batch/RetailCustomer/{retailCustomerId}/UsagePoint - *

- */ - @PostMapping(value = "/Batch/RetailCustomer/{retailCustomerId}/UsagePoint", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Bulk Upload Green Button Data", - description = "Uploads Green Button DMD (Download My Data) files for batch processing. " + - "Supports bulk import of usage points, meter readings, and interval data." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Bulk upload successful"), - @ApiResponse(responseCode = "400", description = "Invalid ATOM XML or batch data"), - @ApiResponse(responseCode = "401", description = "Unauthorized batch operation"), - @ApiResponse(responseCode = "413", description = "File size exceeds limits") - }) - public void upload(HttpServletResponse response, - @Parameter(description = "Retail customer identifier", required = true) - @PathVariable Long retailCustomerId, - @RequestParam Map params, InputStream stream) - throws IOException, FeedException { - - try { - // retailCustomerService returns legacy RetailCustomer - var retailCustomer = retailCustomerService - .findById(retailCustomerId); - - // TODO: Implement ImportService - // importService.importData(stream, retailCustomerId); - throw new UnsupportedOperationException("ImportService not yet implemented"); - - // do any notifications - - // TODO: Implement NotificationService - // notificationService.notify(retailCustomer, - // importService.getMinUpdated(), - // importService.getMaxUpdated()); - - } catch (Exception e) { - System.out.printf("**** Batch Import Error: %s\n", e.toString()); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Supports Green Button Download My Data - A DMD file will be produced that - * contains all Usage Points for the requested Retail Customer. - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {upload_access_token}] - * - * @param response - * HTTP Servlet Response - * @param retailCustomerId - * The locally unique identifier of a Retail Customer - NOTE PII - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - *

- * Usage: - * GET /espi/1_1/resource/Batch/RetailCustomer/{retailCustomerId}/UsagePoint - *

- */ - @GetMapping(value = "/Batch/RetailCustomer/{retailCustomerId}/UsagePoint", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Download Green Button Data Collection", - description = "Downloads all usage points for a retail customer as Green Button DMD file" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "DMD file generated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - public void download_collection(HttpServletResponse response, - @Parameter(description = "Retail customer identifier", required = true) - @PathVariable Long retailCustomerId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/RetailCustomer/{retailCustomerId}/UsagePoint", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - try { - // TODO -- need authorization hook - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Supports Green Button Download My Data A DMD file for a particular Usage - * Point will be produced and returned to the Retail Customer - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {upload_access_token}] - * - * @param response - * HTTP Servlet Response - * @param retailCustomerId - * The locally unique identifier of a Retail Customer - NOTE PII - * @param usagePointId - * The locally unique identifier of a UsagePointEntity.id - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * GET /espi/1_1/resource/Batch/RetailCustomer/{retailCustomerId}/UsagePoint/{usagePointId} - *

- */ - @GetMapping(value = "/Batch/RetailCustomer/{retailCustomerId}/UsagePoint/{usagePointId}", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Download Green Button Data Member", - description = "Downloads specific usage point data for a retail customer as Green Button DMD file" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "DMD file generated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access"), - @ApiResponse(responseCode = "404", description = "Usage point not found") - }) - public void download_member(HttpServletResponse response, - @Parameter(description = "Retail customer identifier", required = true) - @PathVariable Long retailCustomerId, - @Parameter(description = "Usage point identifier", required = true) - @PathVariable Long usagePointId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/RetailCustomer/{retailCustomerId}/UsagePoint/{usagePointId}", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Produce a SubscriptionEntity for the requester. The resultant response will - * contain an Atom "feed" of the Usage Point(s) associated with the subscription. - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {access_token}] - * - * @param response - * HTTP Servlet Response - * @param subscriptionId - * Long identifying the SubscriptionEntity.id of the desired - * Authorization - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * GET /espi/1_1/resource/Batch/Subscription/{subscriptionId} - *

- */ - @Transactional(readOnly = true) - @GetMapping(value = "/Batch/Subscription/{subscriptionId}", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Download SubscriptionEntity Data", - description = "Downloads usage points associated with a subscription as Green Button feed" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Subscription data retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access"), - @ApiResponse(responseCode = "404", description = "Subscription not found") - }) - public void subscription(HttpServletResponse response, - @Parameter(description = "Subscription identifier", required = true) - @PathVariable Long subscriptionId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/Subscription/{subscriptionId}", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Produce a SubscriptionEntity for the requester. The resultant response will - * contain an Atom "feed" of the Usage Point(s) associated with the subscription. - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {access_token}] - * - * @param response - * HTTP Servlet Response - * @param subscriptionId - * Long identifying the SubscriptionEntity.id of the desired - * Authorization - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * GET /espi/1_1/resource/Batch/Subscription/{subscriptionId}/UsagePoint - *

- */ - @Transactional(readOnly = true) - @GetMapping(value = "/Batch/Subscription/{subscriptionId}/UsagePoint", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Download SubscriptionEntity Usage Points", - description = "Downloads all usage points for a specific subscription as Green Button feed" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Usage point data retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access"), - @ApiResponse(responseCode = "404", description = "Subscription not found") - }) - public void subscriptionUsagePoint(HttpServletResponse response, - @Parameter(description = "Subscription identifier", required = true) - @PathVariable Long subscriptionId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/Subscription/{subscriptionId}/UsagePoint", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Produce a SubscriptionEntity for the requester. The resultant response will - * contain an Atom "feed" of the Usage Point(s) associated with the subscription. - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {access_token}] - * - * @param response - * HTTP Servlet Response - * @param subscriptionId - * Long identifying the SubscriptionEntity.id of the desired - * Authorization - * @param usagePointId - * Long identifying the UsagePointEntity.id of the desired - * Authorization - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * GET /espi/1_1/resource/Batch/Subscription/{subscriptionId} - *

- */ - - @Transactional(readOnly = true) - @GetMapping(value = "/Batch/Subscription/{subscriptionId}/UsagePoint/{usagePointId}", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Download Specific SubscriptionEntity Usage Point", - description = "Downloads a specific usage point for a subscription as Green Button feed" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Usage point data retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access"), - @ApiResponse(responseCode = "404", description = "Subscription or usage point not found") - }) - public void subscriptionUsagePointMember(HttpServletResponse response, - @Parameter(description = "Subscription identifier", required = true) - @PathVariable Long subscriptionId, - @Parameter(description = "Usage point identifier", required = true) - @PathVariable Long usagePointId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/Subscription/{subscriptionId}/UsagePoint/{usagePointId}", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - /** - * Provide a Bulk delivery of information. The Third Party is provided with - * the XML representation of the Bulk. - * - * RESTful Pattern: /espi/1_1/resource/Batch/Subscription/{subscriptionId} - * - * Requires Authorization: Bearer [{data_custodian_access_token} | - * {client_access_token}] - * - * @param request - * HTTP Servlet Request - * @param response - * HTTP Servlet Response - * @param bulkId - * Long identifying the BR={bulkId} within the - * Authorization.scope string - * @param params - * HTTP Query Parameters - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - * @throws FeedException - * Exception thrown by WireFeedInput, WireFeedOutput, WireFeedParser - * and WireFeedGenerator instances if they can not parse or generate a feed. - * - *

- * Usage: - * GET /espi/1_1/resource/Batch/Bulk/{bulkId} - *

- */ - @GetMapping(value = "/Batch/Bulk/{bulkId}", - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Bulk Data Delivery", - description = "Provides bulk delivery of information for third-party applications. " + - "Supports both HTTPS and SFTP delivery methods." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Bulk data delivered successfully"), - @ApiResponse(responseCode = "202", description = "Bulk data queued for SFTP delivery"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - // TODO Add support for wildcard bulkId parameters - public void bulk(HttpServletRequest request, HttpServletResponse response, - @Parameter(description = "Bulk identifier", required = true) - @PathVariable Long bulkId, - @RequestParam Map params) - throws IOException, FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/Batch/Bulk/{bulkId}", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - // check to see if SFTP (or XML Caching) is turned on for this bulkId. - // - if (isSFTP(request)) { - if (isInCache(request, bulkId)) { - // TODO: Implement SFTP notification - response.setStatus(HttpServletResponse.SC_ACCEPTED); - return; - } else { - // build the file stream - // in parallel - // generate the xml to a file - // return 302 response code - // TODO: make SFTP cache location an configuration - File destinationFile = new File( - "/var/greenbutton/export-cache/" + bulkId); - FileOutputStream fileOutputStream = new FileOutputStream( - destinationFile); - - try { - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - if (destinationFile != null) { - try { - fileOutputStream.close(); - response.setStatus(HttpServletResponse.SC_ACCEPTED); - } catch (IOException e) { - // Ignore issues during closing - } - } - } - - } - } else { - // not SFTP, use HTTPS as bulk response - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - // note this default to a file just in case the handler doesn't want - // to directly - // parse the incoming stream. - - response.addHeader("Content-Disposition", - "attachment; filename=GreenButtonDownload.xml"); - - try { - // exportService.export(...); // TODO: Implement - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - } - - private String getAuthorizationThirdParty(HttpServletRequest request) { - String token = request.getHeader("authorization"); - String thirdParty = ""; - - if (token != null) { - token = token.replace("Bearer ", ""); - // authorizationService returns legacy Authorization - var authorization = authorizationService - .findByAccessToken(token); - // authorization returns legacy ApplicationInformation - var applicationInformation = authorization - .getApplicationInformation(); - // note that ApplicationInformation.clientId is a String - // - thirdParty = applicationInformation.getClientId(); - } - - return thirdParty; - - } - - private boolean isInCache(HttpServletRequest request, Long bulkId) { - System.out - .println(System.getProperty("/var/greenbutton/export-cache/")); - System.out.println(System.getProperty("/var/greenbutton/export-cache/" - + bulkId)); - return false; - - } - - private boolean isSFTP(HttpServletRequest request) { - Boolean result = false; - String accessToken = request.getHeader("authorization"); - if (accessToken != null) { - if (accessToken.contains("Bearer")) { - // has AuthorizationEntity header with Bearer type - accessToken = accessToken.replace("Bearer ", ""); - // ensure length is >12 characters (48 bits in hex at least) - if (accessToken.length() >= 12) { - // we have a valid token - AuthorizationEntity authorization = authorizationService - .findByAccessToken(accessToken); - ApplicationInformationEntity applicationInformation = authorization - .getApplicationInformation(); - String bulkRequestUri = applicationInformation - .getDataCustodianBulkRequestURI(); - if (bulkRequestUri.contains("sftp:")) { - result = true; - } - } - } - } - return result; - } - - -} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java index cd1b8b5a..907b0f3d 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java @@ -27,11 +27,9 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.greenbuttonalliance.espi.common.domain.customer.entity.CustomerAccountEntity; import org.greenbuttonalliance.espi.common.dto.customer.CustomerAccountDto; import org.greenbuttonalliance.espi.common.mapper.customer.CustomerAccountMapper; import org.greenbuttonalliance.espi.common.repositories.customer.CustomerAccountRepository; -import org.greenbuttonalliance.espi.common.service.customer.CustomerAccountService; import org.greenbuttonalliance.espi.common.service.impl.CustomerAccountExportService; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; @@ -39,19 +37,18 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.io.ByteArrayOutputStream; -import java.net.URI; import java.util.List; import java.util.UUID; /** - * Modern RESTful controller for managing CustomerAccount resources according to the + * Modern RESTful controller for reading CustomerAccount resources according to the * Green Button Alliance ESPI (Energy Services Provider Interface) specification. *

- * This controller handles CustomerAccount operations with modern Spring Boot 3.5 patterns, - * returning XML output via JAXB-marshalled responses. + * GET-only, returning XML via JAXB-marshalled responses. The CRUD write endpoints + * (POST/PUT/DELETE) are deferred — they are admin/sandbox-DB management APIs to be delivered + * in the separate admin-CRUD track (see issue #119 build plan). */ @RestController @RequestMapping("/espi/1_1/resource") @@ -63,7 +60,6 @@ public class CustomerAccountRESTController { private final CustomerAccountRepository customerAccountRepository; private final CustomerAccountMapper customerAccountMapper; private final CustomerAccountExportService customerAccountExportService; - private final CustomerAccountService customerAccountService; /** * Get all Customer Accounts (root collection). @@ -125,83 +121,4 @@ public ResponseEntity getCustomerAccount( .contentType(MediaType.APPLICATION_XML) .body(out.toByteArray()); } - - /** - * Create a new Customer Account. - */ - @PostMapping(value = "/CustomerAccount", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - summary = "Create CustomerAccount", - description = "Creates a new CustomerAccount resource.", - responses = { - @ApiResponse(responseCode = "201", description = "Successfully created CustomerAccount"), - @ApiResponse(responseCode = "400", description = "Invalid data"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity createCustomerAccount(@RequestBody CustomerAccountDto dto) { - CustomerAccountEntity entity = customerAccountMapper.toEntity(dto); - CustomerAccountEntity savedEntity = customerAccountService.save(entity); - CustomerAccountDto savedDto = customerAccountMapper.toDto(savedEntity); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(savedEntity.getId()) - .toUri(); - - return ResponseEntity.created(location).body(savedDto); - } - - /** - * Update an existing Customer Account. - */ - @PutMapping(value = "/CustomerAccount/{customerAccountId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @Operation( - summary = "Update CustomerAccount", - description = "Updates an existing CustomerAccount resource.", - responses = { - @ApiResponse(responseCode = "200", description = "Successfully updated CustomerAccount"), - @ApiResponse(responseCode = "404", description = "CustomerAccount not found"), - @ApiResponse(responseCode = "400", description = "Invalid data"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity updateCustomerAccount( - @PathVariable UUID customerAccountId, - @RequestBody CustomerAccountDto dto) { - - if (!customerAccountRepository.existsById(customerAccountId)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "CustomerAccount not found for id: " + customerAccountId); - } - - CustomerAccountEntity entity = customerAccountMapper.toEntity(dto); - entity.setId(customerAccountId); - CustomerAccountEntity updatedEntity = customerAccountService.save(entity); - - return ResponseEntity.ok(customerAccountMapper.toDto(updatedEntity)); - } - - /** - * Delete a Customer Account. - */ - @DeleteMapping("/CustomerAccount/{customerAccountId}") - @Operation( - summary = "Delete CustomerAccount", - description = "Deletes an existing CustomerAccount resource.", - responses = { - @ApiResponse(responseCode = "204", description = "Successfully deleted CustomerAccount"), - @ApiResponse(responseCode = "404", description = "CustomerAccount not found"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity deleteCustomerAccount(@PathVariable UUID customerAccountId) { - if (!customerAccountRepository.existsById(customerAccountId)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "CustomerAccount not found for id: " + customerAccountId); - } - customerAccountRepository.deleteById(customerAccountId); - return ResponseEntity.noContent().build(); - } -} \ No newline at end of file +} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java.disabled deleted file mode 100644 index 94a3ea77..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTController.java.disabled +++ /dev/null @@ -1,542 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - -import com.sun.syndication.io.FeedException; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import org.greenbuttonalliance.espi.common.domain.customer.entity.CustomerAccountEntity; -import org.greenbuttonalliance.espi.common.dto.customer.CustomerAccountDto; -import org.greenbuttonalliance.espi.common.service.customer.CustomerAccountService; -import org.greenbuttonalliance.espi.common.service.DtoExportService; -import org.greenbuttonalliance.espi.datacustodian.utils.VerifyURLParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.Map; -import java.util.UUID; - -/** - * RESTful controller for managing Customer Account resources according to the - * Green Button Alliance ESPI (Energy Services Provider Interface) specification. - * - * CustomerAccount represents a billing account relationship between a customer and - * utility company, including account status, billing preferences, payment history, - * and financial arrangements. - * - * Features: - * - Account lifecycle management (active, suspended, closed) - * - Billing and payment tracking - * - Service agreements and rate schedules - * - Account hierarchy for commercial customers - * - Financial reporting and statements - */ -// @RestController - COMMENTED OUT: Legacy controller disabled for simplification -// @Component -// @RequestMapping - DISABLED("/espi/1_1/resource") -@Tag(name = "Customer Account", description = "Customer Billing Account Management API") -@SecurityRequirement(name = "OAuth2") -public class CustomerAccountRESTController { - - private final CustomerAccountService customerAccountService; - private final DtoExportService exportService; - - @Autowired - public CustomerAccountRESTController( - CustomerAccountService customerAccountService, - DtoExportService exportService) { - this.customerAccountService = customerAccountService; - this.exportService = exportService; - } - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public void handleGenericException() { - // Generic exception handler - } - - // ================================ - // Customer Account Collection APIs - // ================================ - - /** - * Retrieves all CustomerAccount resources. - * - * @param request HTTP servlet request for authorization context - * @param response HTTP response for streaming ATOM XML content - * @param params Query parameters for filtering and pagination - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM feed generation fails - */ - @GetMapping(value = "/CustomerAccount", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_READ')") - @Operation( - summary = "Get CustomerAccount Collection", - description = "Retrieves all customer billing accounts with optional filtering and pagination. " + - "Returns an ATOM feed containing CustomerAccount entries including account status, " + - "billing information, and service relationships.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_READ"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved CustomerAccount collection", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM feed containing CustomerAccount entries")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid query parameters provided" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to account resources" - ) - }) - public void getCustomerAccountCollection( - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Query parameters for filtering (published-max, published-min, updated-max, updated-min, max-results, start-index)") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/CustomerAccount", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - exportService.exportCustomerAccounts(response.getOutputStream(), - params); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Retrieves a specific CustomerAccount by ID. - * - * @param customerAccountId Unique identifier for the CustomerAccount - * @param request HTTP servlet request for authorization context - * @param response HTTP response for streaming ATOM XML content - * @param params Query parameters for export filtering - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM entry generation fails - */ - @GetMapping(value = "/CustomerAccount/{customerAccountId}", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_READ') and @accountSecurityService.hasAccessToAccount(authentication, #customerAccountId)") - @Operation( - summary = "Get CustomerAccount by ID", - description = "Retrieves a specific customer billing account by its unique identifier. " + - "Returns an ATOM entry containing the CustomerAccount details including " + - "account status, billing cycle, payment methods, and service agreements.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_READ"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved CustomerAccount", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM entry containing CustomerAccount details")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid customerAccountId or query parameters" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to this account" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public void getCustomerAccount( - @Parameter(description = "Unique identifier of the CustomerAccount", required = true) - @PathVariable UUID customerAccountId, - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Query parameters for export filtering") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/CustomerAccount/{customerAccountId}", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - exportService.exportCustomerAccount(customerAccountId, response.getOutputStream(), - params); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Creates a new CustomerAccount resource. - * - * @param customerAccountDto CustomerAccount data - * @param request HTTP servlet request for authorization context - * @return Created CustomerAccount response - */ - @PostMapping(value = "/CustomerAccount", - consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE}, - produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE}) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_WRITE') and hasRole('DATA_CUSTODIAN')") - @Operation( - summary = "Create CustomerAccount", - description = "Creates a new customer billing account. Establishes the relationship " + - "between a customer and utility service including billing preferences, " + - "payment methods, and service level agreements.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_WRITE"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "201", - description = "Successfully created CustomerAccount", - content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = @Schema(implementation = CustomerAccountDto.class)) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid CustomerAccount data" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to create accounts" - ) - }) - public ResponseEntity createCustomerAccount( - @Parameter(description = "CustomerAccount data", required = true) - @Valid @RequestBody CustomerAccountDto customerAccountDto, - HttpServletRequest request) { - - try { - CustomerAccountEntity customerAccount = customerAccountService.createCustomerAccount(customerAccountDto); - CustomerAccountDto responseDto = customerAccountService.toDto(customerAccount); - return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); - } catch (Exception e) { - return ResponseEntity.badRequest().build(); - } - } - - /** - * Updates an existing CustomerAccount resource. - * - * @param customerAccountId Unique identifier for the CustomerAccount to update - * @param customerAccountDto Updated CustomerAccount data - * @param request HTTP servlet request for authorization context - * @return Updated CustomerAccount response - */ - @PutMapping(value = "/CustomerAccount/{customerAccountId}", - consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE}, - produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE}) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_WRITE') and @accountSecurityService.hasAccessToAccount(authentication, #customerAccountId)") - @Operation( - summary = "Update CustomerAccount", - description = "Updates an existing customer billing account. Allows modification of " + - "account status, billing preferences, payment methods, and service configurations.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_WRITE"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully updated CustomerAccount", - content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = @Schema(implementation = CustomerAccountDto.class)) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid CustomerAccount data" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to update this account" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity updateCustomerAccount( - @Parameter(description = "Unique identifier of the CustomerAccount to update", required = true) - @PathVariable UUID customerAccountId, - @Parameter(description = "Updated CustomerAccount data", required = true) - @Valid @RequestBody CustomerAccountDto customerAccountDto, - HttpServletRequest request) { - - try { - CustomerAccountEntity customerAccount = customerAccountService.updateCustomerAccount(customerAccountId, customerAccountDto); - CustomerAccountDto responseDto = customerAccountService.toDto(customerAccount); - return ResponseEntity.ok(responseDto); - } catch (Exception e) { - return ResponseEntity.badRequest().build(); - } - } - - /** - * Deletes a CustomerAccount resource. - * - * @param customerAccountId Unique identifier for the CustomerAccount to delete - * @param request HTTP servlet request for authorization context - * @return Deletion confirmation response - */ - @DeleteMapping("/CustomerAccount/{customerAccountId}") - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_DELETE') and hasRole('DATA_CUSTODIAN')") - @Operation( - summary = "Delete CustomerAccount", - description = "Removes a customer billing account. This will close the account, " + - "finalize billing, and archive transaction history while maintaining " + - "regulatory compliance for record retention.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_DELETE"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "204", - description = "Successfully deleted CustomerAccount" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to delete accounts" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity deleteCustomerAccount( - @Parameter(description = "Unique identifier of the CustomerAccount to delete", required = true) - @PathVariable UUID customerAccountId, - HttpServletRequest request) { - - try { - customerAccountService.deleteCustomerAccount(customerAccountId); - return ResponseEntity.noContent().build(); - } catch (Exception e) { - return ResponseEntity.notFound().build(); - } - } - - // ============================================ - // Customer Account Financial Management APIs - // ============================================ - - /** - * Retrieves the current account balance and billing status. - * - * @param customerAccountId Unique identifier for the CustomerAccount - * @param request HTTP servlet request for authorization context - * @return Account balance and billing information - */ - @GetMapping(value = "/CustomerAccount/{customerAccountId}/balance", produces = MediaType.APPLICATION_JSON_VALUE) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_READ') and @accountSecurityService.hasAccessToAccount(authentication, #customerAccountId)") - @Operation( - summary = "Get Account Balance", - description = "Retrieves current account balance, billing status, payment due dates, " + - "and outstanding charges for the specified customer account.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_READ"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved account balance" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to account financial data" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity> getAccountBalance( - @Parameter(description = "Unique identifier of the CustomerAccount", required = true) - @PathVariable UUID customerAccountId, - HttpServletRequest request) { - - try { - Map balanceInfo = customerAccountService.getAccountBalance(customerAccountId); - return ResponseEntity.ok(balanceInfo); - } catch (Exception e) { - return ResponseEntity.notFound().build(); - } - } - - /** - * Updates account billing preferences and payment methods. - * - * @param customerAccountId Unique identifier for the CustomerAccount - * @param billingPreferences Updated billing preferences - * @param request HTTP servlet request for authorization context - * @return Update confirmation response - */ - @PutMapping(value = "/CustomerAccount/{customerAccountId}/billing", - consumes = MediaType.APPLICATION_JSON_VALUE) - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_WRITE') and @accountSecurityService.hasAccessToAccount(authentication, #customerAccountId)") - @Operation( - summary = "Update Billing Preferences", - description = "Updates account billing preferences including billing cycle, " + - "payment methods, electronic billing options, and notification settings.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_WRITE"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully updated billing preferences" - ), - @ApiResponse( - responseCode = "400", - description = "Invalid billing preference data" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to modify billing preferences" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity updateBillingPreferences( - @Parameter(description = "Unique identifier of the CustomerAccount", required = true) - @PathVariable UUID customerAccountId, - @Parameter(description = "Updated billing preferences", required = true) - @RequestBody Map billingPreferences, - HttpServletRequest request) { - - try { - customerAccountService.updateBillingPreferences(customerAccountId, billingPreferences); - return ResponseEntity.ok().build(); - } catch (Exception e) { - return ResponseEntity.badRequest().build(); - } - } - - /** - * Suspends a customer account for non-payment or other issues. - * - * @param customerAccountId Unique identifier for the CustomerAccount - * @param suspensionReason Reason for account suspension - * @param request HTTP servlet request for authorization context - * @return Suspension confirmation response - */ - @PostMapping(value = "/CustomerAccount/{customerAccountId}/suspend") - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_ADMIN') and hasRole('DATA_CUSTODIAN')") - @Operation( - summary = "Suspend Customer Account", - description = "Suspends a customer account due to non-payment, policy violations, " + - "or other administrative reasons. This affects service delivery and billing.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_ADMIN"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully suspended account" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to suspend accounts" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity suspendAccount( - @Parameter(description = "Unique identifier of the CustomerAccount", required = true) - @PathVariable UUID customerAccountId, - @Parameter(description = "Reason for suspension", required = true) - @RequestParam String suspensionReason, - HttpServletRequest request) { - - try { - customerAccountService.suspendAccount(customerAccountId, suspensionReason); - return ResponseEntity.ok().build(); - } catch (Exception e) { - return ResponseEntity.notFound().build(); - } - } - - /** - * Reactivates a suspended customer account. - * - * @param customerAccountId Unique identifier for the CustomerAccount - * @param request HTTP servlet request for authorization context - * @return Reactivation confirmation response - */ - @PostMapping(value = "/CustomerAccount/{customerAccountId}/reactivate") - @PreAuthorize("hasAuthority('SCOPE_ACCOUNT_ADMIN') and hasRole('DATA_CUSTODIAN')") - @Operation( - summary = "Reactivate Customer Account", - description = "Reactivates a previously suspended customer account. " + - "Restores normal billing and service delivery operations.", - security = {@SecurityRequirement(name = "OAuth2", scopes = {"ACCOUNT_ADMIN"})} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully reactivated account" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to reactivate accounts" - ), - @ApiResponse( - responseCode = "404", - description = "CustomerAccount not found" - ) - }) - public ResponseEntity reactivateAccount( - @Parameter(description = "Unique identifier of the CustomerAccount", required = true) - @PathVariable UUID customerAccountId, - HttpServletRequest request) { - - try { - customerAccountService.reactivateAccount(customerAccountId); - return ResponseEntity.ok().build(); - } catch (Exception e) { - return ResponseEntity.notFound().build(); - } - } -} \ No newline at end of file diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTController.java b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTController.java index 5ca51712..7b6f4345 100644 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTController.java +++ b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTController.java @@ -26,13 +26,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.greenbuttonalliance.espi.common.domain.customer.entity.CustomerEntity; import org.greenbuttonalliance.espi.common.dto.customer.CustomerDto; import org.greenbuttonalliance.espi.common.mapper.customer.CustomerMapper; import org.greenbuttonalliance.espi.common.repositories.customer.CustomerRepository; -import org.greenbuttonalliance.espi.common.service.customer.CustomerService; import org.greenbuttonalliance.espi.common.service.impl.CustomerExportService; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; @@ -40,19 +37,18 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.io.ByteArrayOutputStream; -import java.net.URI; import java.util.List; import java.util.UUID; /** - * Modern RESTful controller for managing Customer resources according to the + * Modern RESTful controller for reading Customer resources according to the * Green Button Alliance ESPI (Energy Services Provider Interface) specification. *

- * This controller handles Customer operations with modern Spring Boot 3.5 patterns, - * returning XML output via JAXB-marshalled responses. + * GET-only, returning XML via JAXB-marshalled responses. The CRUD write endpoints + * (POST/PUT/DELETE) are deferred — they are admin/sandbox-DB management APIs to be delivered + * in the separate admin-CRUD track (see issue #119 build plan). */ @RestController @RequestMapping("/espi/1_1/resource") @@ -64,7 +60,6 @@ public class CustomerRESTController { private final CustomerRepository customerRepository; private final CustomerMapper customerMapper; private final CustomerExportService customerExportService; - private final CustomerService customerService; /** * Get all Customers (root collection). @@ -126,89 +121,4 @@ public ResponseEntity getCustomer( .contentType(MediaType.APPLICATION_XML) .body(out.toByteArray()); } - - /** - * Create a new Customer. - */ - @PostMapping(value = "/Customer", consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, - produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @Operation( - summary = "Create Customer", - description = "Creates a new Customer resource.", - responses = { - @ApiResponse(responseCode = "201", description = "Customer created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity createCustomer(@Valid @RequestBody CustomerDto customerDto) { - CustomerEntity entity = customerMapper.toEntity(customerDto); - CustomerEntity savedEntity = customerService.save(entity); - CustomerDto savedDto = customerMapper.toDto(savedEntity); - - URI location = ServletUriComponentsBuilder.fromCurrentRequest() - .path("/{id}") - .buildAndExpand(savedEntity.getId()) - .toUri(); - - return ResponseEntity.created(location).body(savedDto); - } - - /** - * Update an existing Customer. - */ - @PutMapping(value = "/Customer/{customerId}", consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, - produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) - @Operation( - summary = "Update Customer", - description = "Updates an existing Customer resource.", - responses = { - @ApiResponse(responseCode = "200", description = "Customer updated successfully"), - @ApiResponse(responseCode = "404", description = "Customer not found"), - @ApiResponse(responseCode = "400", description = "Invalid request"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity updateCustomer( - @Parameter(description = "Unique identifier of the Customer", required = true) - @PathVariable UUID customerId, - @Valid @RequestBody CustomerDto customerDto) { - - if (!customerService.existsById(customerId)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Customer not found"); - } - - CustomerEntity entity = customerMapper.toEntity(customerDto); - entity.setId(customerId); - CustomerEntity savedEntity = customerService.save(entity); - return ResponseEntity.ok(customerMapper.toDto(savedEntity)); - } - - /** - * Delete a Customer. - */ - @DeleteMapping(value = "/Customer/{customerId}") - @Operation( - summary = "Delete Customer", - description = "Deletes an existing Customer resource.", - responses = { - @ApiResponse(responseCode = "204", description = "Customer deleted successfully"), - @ApiResponse(responseCode = "404", description = "Customer not found"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden - insufficient scope") - } - ) - public ResponseEntity deleteCustomer( - @Parameter(description = "Unique identifier of the Customer", required = true) - @PathVariable UUID customerId) { - - if (!customerService.existsById(customerId)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Customer not found"); - } - - customerService.deleteById(customerId); - return ResponseEntity.noContent().build(); - } -} \ No newline at end of file +} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ManageRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ManageRESTController.java.disabled deleted file mode 100644 index b3bcf70f..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/ManageRESTController.java.disabled +++ /dev/null @@ -1,135 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - - -// import org.greenbuttonalliance.espi.common.domain.usage.Routes; // Missing class -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletResponse; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Map; - -/** - * A Controller that supports administrative management capabilities within the - * data custodian - * - * @author jat1 - * - */ -// @Controller - COMMENTED OUT: Admin management not needed for basic OAuth2 resource server -// @Component -public class ManageRESTController { - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public void handleGenericException() { - } - - /** - * Provides access to administrative commands through the pattern: - * DataCustodian/manage?command=[resetDataCustodianDB | - * initializeDataCustodianDB] - * - * @param response - * Contains text version of stdout of the command - * @param params - * [["command" . ["resetDataCustodianDB" | clear"initializeDataCustodianDB"]]] - * @param stream - * Contains I/O input - * @throws IOException - * Exception thrown by failed or interrupted I/O operations. - */ - @RequestMapping(value = "/espi/1_1/resource/DataCustodian/manage", method = RequestMethod.GET, produces = "text/plain") - @ResponseBody - public void doCommand(HttpServletResponse response, - @RequestParam Map params, InputStream stream) - throws IOException { - - response.setContentType(MediaType.TEXT_PLAIN_VALUE); - - try { - try { - String commandString = params.get("command"); - System.out.println("[Manage] " + commandString); - ServletOutputStream output = response.getOutputStream(); - - output.println("[Manage] Restricted Management Interface"); - output.println("[Manage] Request: " + commandString); - - String command = null; - - // parse command - if (commandString.contains("resetDataCustodianDB")) { - command = "/etc/OpenESPI/DataCustodian/resetDatabase.sh"; - } else if (commandString.contains("initializeDataCustodianDB")) { - command = "/etc/OpenESPI/DataCustodian/initializeDatabase.sh"; - - } - - if (command != null) { - Process p = Runtime.getRuntime().exec(command); - p.waitFor(); - output.println("[Manage] Result: "); - BufferedReader reader = new BufferedReader( - new InputStreamReader(p.getInputStream())); - - String line = reader.readLine(); - - while (line != null) { - System.out.println("[Manage] " + line); - output.println("[Manage]: " + line); - line = reader.readLine(); - } - reader = new BufferedReader(new InputStreamReader( - p.getErrorStream())); - output.println("[Manage] Errors: "); - line = reader.readLine(); - while (line != null) { - System.out.println("[Manage] " + line); - output.println("[Manage]: " + line); - line = reader.readLine(); - } - } - - } catch (IOException e1) { - } catch (InterruptedException e2) { - } - - System.out.println("[Manage] " + "Done"); - - } catch (Exception e) { - System.out.printf("**** [Manage] Error: %s\n", e.toString()); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - -} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/RetailCustomerRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/RetailCustomerRESTController.java.disabled deleted file mode 100644 index a1b1b93b..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/RetailCustomerRESTController.java.disabled +++ /dev/null @@ -1,395 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - -import com.sun.syndication.io.FeedException; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.greenbuttonalliance.espi.common.domain.usage.AuthorizationEntity; -import org.greenbuttonalliance.espi.common.domain.usage.RetailCustomerEntity; -import org.greenbuttonalliance.espi.common.domain.usage.SubscriptionEntity; -import org.greenbuttonalliance.espi.common.service.*; -import org.greenbuttonalliance.espi.common.repositories.usage.*; -import org.greenbuttonalliance.espi.datacustodian.utils.VerifyURLParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * RESTful controller for managing RetailCustomerEntity resources according to the - * Green Button Alliance ESPI (Energy Services Provider Interface) specification. - * - * RetailCustomerEntity represents the end consumer of utility services who may be - * either a person or organization. - */ -// @RestController - COMMENTED OUT: Legacy controller disabled for simplification -// @Component -// @RequestMapping - DISABLED("/espi/1_1/resource") -@Tag(name = "Retail Customer", description = "Utility Customer Account Management API") -public class RetailCustomerRESTController { - - // private final ImportService importService; // TODO: Implement - private final RetailCustomerService retailCustomerService; - private final UsagePointRepository usagePointService; - private final DtoExportService exportService; - private final AuthorizationService authorizationService; - - @Autowired - public RetailCustomerRESTController( - // ImportService importService, - RetailCustomerService retailCustomerService, - UsagePointRepository usagePointService, - DtoExportService exportService, - AuthorizationService authorizationService) { - // this.importService = importService; - this.retailCustomerService = retailCustomerService; - this.usagePointService = usagePointService; - this.exportService = exportService; - this.authorizationService = authorizationService; - } - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public void handleGenericException() { - // Generic exception handler - } - - // ================================ - // ROOT RetailCustomerEntity Collection APIs - // ================================ - - /** - * Retrieves all RetailCustomerEntity resources (root level access). - * - * @param request HTTP servlet request for authorization context - * @param response HTTP response for streaming ATOM XML content - * @param params Query parameters for filtering and pagination - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM feed generation fails - */ - @GetMapping(value = "/RetailCustomer", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get RetailCustomerEntity Collection", - description = "Retrieves all authorized RetailCustomerEntity resources with optional filtering and pagination. " + - "Returns an ATOM feed containing customer account information." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved RetailCustomerEntity collection", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM feed containing RetailCustomerEntity entries")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid query parameters provided" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to customer data" - ) - }) - public void getRetailCustomerCollection( - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Query parameters for filtering (published-max, published-min, updated-max, updated-min, max-results, start-index)") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/RetailCustomer", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - Long subscriptionId = getSubscriptionId(request); - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - exportService.exportRetailCustomers(subscriptionId, - response.getOutputStream(), params); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Retrieves a specific RetailCustomerEntity resource by ID (root level access). - * - * @param request HTTP servlet request for authorization context - * @param response HTTP response for streaming ATOM XML content - * @param retailCustomerId Unique identifier for the RetailCustomer - * @param params Query parameters for export filtering - * @throws IOException if output stream cannot be written - * @throws FeedException if ATOM entry generation fails - */ - @GetMapping(value = "/RetailCustomer/{retailCustomerId}", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get RetailCustomerEntity by ID", - description = "Retrieves a specific RetailCustomerEntity resource by its unique identifier. " + - "Returns an ATOM entry containing customer account details and contact information." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully retrieved RetailCustomer", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM entry containing RetailCustomerEntity details")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid retailCustomerId or query parameters" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized access to this customer data" - ), - @ApiResponse( - responseCode = "404", - description = "RetailCustomerEntity not found" - ) - }) - public void getRetailCustomer( - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Unique identifier of the RetailCustomer", required = true) - @PathVariable Long retailCustomerId, - @Parameter(description = "Query parameters for export filtering") - @RequestParam Map params) throws IOException, FeedException { - - // Verify request contains valid query parameters - if (!VerifyURLParams.verifyEntries("/espi/1_1/resource/RetailCustomer/{retailCustomerId}", params)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Request contains invalid query parameter values!"); - return; - } - - Long subscriptionId = getSubscriptionId(request); - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - exportService.exportRetailCustomer(subscriptionId, - retailCustomerId, response.getOutputStream(), - params); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Creates a new RetailCustomerEntity resource (root level). - * - * @param request HTTP servlet request for authorization context - * @param response HTTP response for returning created resource - * @param params Query parameters for export filtering - * @param stream Input stream containing ATOM XML data - * @throws IOException if input/output stream operations fail - */ - @PostMapping(value = "/RetailCustomer", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Create RetailCustomer", - description = "Creates a new RetailCustomerEntity resource representing a utility customer account. " + - "The request body should contain an ATOM entry with customer details including " + - "name, contact information, and account preferences." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "201", - description = "Successfully created RetailCustomer", - content = @Content(mediaType = MediaType.APPLICATION_ATOM_XML_VALUE, - schema = @Schema(description = "ATOM entry containing the created RetailCustomer")) - ), - @ApiResponse( - responseCode = "400", - description = "Invalid ATOM XML format or RetailCustomerEntity data" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to create customer accounts" - ) - }) - public void createRetailCustomer( - HttpServletRequest request, - HttpServletResponse response, - @Parameter(description = "Query parameters for export filtering") - @RequestParam Map params, - @Parameter(description = "ATOM XML containing RetailCustomerEntity data", required = true) - @RequestBody InputStream stream) throws IOException { - - Long subscriptionId = getSubscriptionId(request); - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - - try { - RetailCustomerEntity retailCustomer = - this.retailCustomerService.importResource(stream); - - // TODO: Implement UUID-based export in DtoExportService - // Current exportService.exportRetailCustomer() only accepts Long IDs - // but RetailCustomerEntity.getId() returns UUID - response.getWriter().write(""); - response.getWriter().write("RetailCustomerEntity created with UUID: " + - retailCustomer.getId() + ""); - response.setStatus(HttpServletResponse.SC_CREATED); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Updates an existing RetailCustomerEntity resource (root level). - * - * @param response HTTP response for returning updated resource - * @param retailCustomerId Unique identifier for the RetailCustomerEntity to update - * @param params Query parameters for export filtering - * @param stream Input stream containing updated ATOM XML data - * @throws IOException if input/output stream operations fail - * @throws FeedException if ATOM processing fails - */ - @PutMapping(value = "/RetailCustomer/{retailCustomerId}", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Update RetailCustomer", - description = "Updates an existing RetailCustomerEntity resource. The request body should contain " + - "an ATOM entry with updated customer account details." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully updated RetailCustomer" - ), - @ApiResponse( - responseCode = "400", - description = "Invalid ATOM XML format or RetailCustomerEntity data" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to update this customer account" - ), - @ApiResponse( - responseCode = "404", - description = "RetailCustomerEntity not found" - ) - }) - public void updateRetailCustomer( - HttpServletResponse response, - @Parameter(description = "Unique identifier of the RetailCustomerEntity to update", required = true) - @PathVariable UUID retailCustomerId, - @Parameter(description = "Query parameters for export filtering") - @RequestParam Map params, - @Parameter(description = "ATOM XML containing updated RetailCustomerEntity data", required = true) - @RequestBody InputStream stream) throws IOException, FeedException { - try { - // TODO: Update to UUID-based service methods when available - // Current retailCustomerService.findById() may not support UUID yet - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - response.getWriter().write("Update method requires UUID-based service implementation"); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - /** - * Deletes a RetailCustomerEntity resource (root level). - * - * @param response HTTP response - * @param retailCustomerId Unique identifier for the RetailCustomerEntity to delete - */ - @DeleteMapping("/RetailCustomer/{retailCustomerId}") - @Operation( - summary = "Delete RetailCustomer", - description = "Removes a RetailCustomerEntity resource. This will also remove all associated " + - "usage points, authorizations, and subscription data." - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "Successfully deleted RetailCustomer" - ), - @ApiResponse( - responseCode = "401", - description = "Unauthorized to delete this customer account" - ), - @ApiResponse( - responseCode = "404", - description = "RetailCustomerEntity not found" - ) - }) - public void deleteRetailCustomer( - HttpServletResponse response, - @Parameter(description = "Unique identifier of the RetailCustomerEntity to delete", required = true) - @PathVariable UUID retailCustomerId) { - - try { - // TODO: Update to UUID-based service methods when available - // Current retailCustomerService.findById() may not support UUID yet - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - response.getWriter().write("Delete method requires UUID-based service implementation"); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - // ============================================= - // Utility Methods - // ============================================= - - /** - * Extracts subscription ID from the HTTP request context. - * - * @param request HTTP servlet request - * @return SubscriptionEntity ID if available, 0L otherwise - */ - private Long getSubscriptionId(HttpServletRequest request) { - String token = request.getHeader("authorization"); - Long subscriptionId = 0L; - - if (token != null) { - token = token.replace("Bearer ", ""); - AuthorizationEntity authorization = authorizationService.findByAccessToken(token); - if (authorization != null) { - SubscriptionEntity subscription = authorization.getSubscription(); - if (subscription != null) { - subscriptionId = subscription.getId(); - } - } - } - - return subscriptionId; - } - - -} diff --git a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/TimeConfigurationRESTController.java.disabled b/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/TimeConfigurationRESTController.java.disabled deleted file mode 100755 index 1fd788ea..00000000 --- a/openespi-datacustodian/src/main/java/org/greenbuttonalliance/espi/datacustodian/web/api/TimeConfigurationRESTController.java.disabled +++ /dev/null @@ -1,219 +0,0 @@ -/* - * - * Copyright (c) 2025 Green Button Alliance, Inc. - * - * - * 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 org.greenbuttonalliance.espi.datacustodian.web.api; - -import com.sun.syndication.io.FeedException; -import org.greenbuttonalliance.espi.common.domain.usage.TimeConfigurationEntity; -import org.greenbuttonalliance.espi.common.service.*; -import org.greenbuttonalliance.espi.common.repositories.usage.*; -import org.greenbuttonalliance.espi.datacustodian.utils.VerifyURLParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; - -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -// @RestController - COMMENTED OUT: Legacy controller disabled for simplification -// @Component -// @RequestMapping - DISABLED("/espi/1_1/resource") -@Tag(name = "Time Configuration", description = "Time Configuration Management API") -public class TimeConfigurationRESTController { - - private final TimeConfigurationRepository timeConfigurationRepository; - private final RetailCustomerService retailCustomerService; - private final UsagePointRepository usagePointService; - private final DtoExportService exportService; - private final ResourceRepository resourceService; - - @Autowired - public TimeConfigurationRESTController( - TimeConfigurationRepository timeConfigurationRepository, - RetailCustomerService retailCustomerService, - UsagePointRepository usagePointService, - DtoExportService exportService, - ResourceRepository resourceService) { - this.timeConfigurationRepository = timeConfigurationRepository; - this.retailCustomerService = retailCustomerService; - this.usagePointService = usagePointService; - this.exportService = exportService; - this.resourceService = resourceService; - } - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public void handleGenericException() { - } - - // ROOT RESTFul Forms - // - @GetMapping(value = "/TimeConfiguration", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get Time Configurations", - description = "Retrieves all time configuration resources for the system" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Time configurations retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - public void index(HttpServletResponse response, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/TimeConfiguration", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - exportService.exportTimeConfigurations(response.getOutputStream(), - params); - } - - @GetMapping(value = "/TimeConfiguration/{timeConfigurationId}", produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Get Time Configuration", - description = "Retrieves a specific time configuration resource by ID" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Time configuration retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid request parameters"), - @ApiResponse(responseCode = "404", description = "Time configuration not found"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - public void show(HttpServletResponse response, - @Parameter(description = "Time configuration identifier", required = true) - @PathVariable Long timeConfigurationId, - @RequestParam Map params) throws IOException, - FeedException { - - // Verify request contains valid query parameters - if(!VerifyURLParams.verifyEntries("/espi/1_1/resource/TimeConfiguration/{timeConfigurationId}", params)) { - - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request contains invalid query parameter values!"); - return; - } - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - exportService.exportTimeConfiguration(timeConfigurationId, - response.getOutputStream(), params); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PostMapping(value = "/TimeConfiguration", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Create Time Configuration", - description = "Creates a new time configuration resource from ATOM XML data" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Time configuration created successfully"), - @ApiResponse(responseCode = "400", description = "Invalid ATOM XML data"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - public void create(HttpServletResponse response, - @RequestParam Map params, InputStream stream) - throws IOException { - - response.setContentType(MediaType.APPLICATION_ATOM_XML_VALUE); - try { - TimeConfigurationEntity timeConfiguration = this.timeConfigurationRepository - .importResource(stream); - exportService.exportTimeConfiguration(timeConfiguration.getId(), - response.getOutputStream(), new ExportFilter( - new HashMap())); - - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - @PutMapping(value = "/TimeConfiguration/{timeConfigurationId}", - consumes = MediaType.APPLICATION_ATOM_XML_VALUE, - produces = MediaType.APPLICATION_ATOM_XML_VALUE) - @Operation( - summary = "Update Time Configuration", - description = "Updates an existing time configuration resource with new ATOM XML data" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Time configuration updated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid ATOM XML data"), - @ApiResponse(responseCode = "404", description = "Time configuration not found"), - @ApiResponse(responseCode = "401", description = "Unauthorized access") - }) - public void update(HttpServletResponse response, - @Parameter(description = "Time configuration identifier", required = true) - @PathVariable Long timeConfigurationId, - @RequestParam Map params, InputStream stream) { - - try { - timeConfigurationRepository.importResource(stream); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - @DeleteMapping(value = "/TimeConfiguration/{timeConfigurationId}") - @Operation( - summary = "Delete Time Configuration", - description = "Deletes a specific time configuration resource" - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Time configuration deleted successfully"), - @ApiResponse(responseCode = "404", description = "Time configuration not found"), - @ApiResponse(responseCode = "401", description = "Unauthorized access"), - @ApiResponse(responseCode = "400", description = "Delete operation failed") - }) - public void delete(HttpServletResponse response, - @Parameter(description = "Time configuration identifier", required = true) - @PathVariable Long timeConfigurationId, - @RequestParam Map params, InputStream stream) { - try { - resourceService.deleteById(timeConfigurationId, - TimeConfigurationEntity.class); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - -} diff --git a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTControllerTest.java b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTControllerTest.java index 4a7d56e7..35c2a782 100644 --- a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTControllerTest.java +++ b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/ApplicationInformationRESTControllerTest.java @@ -21,7 +21,6 @@ package org.greenbuttonalliance.espi.datacustodian.web.api; import org.greenbuttonalliance.espi.common.domain.usage.ApplicationInformationEntity; -import org.greenbuttonalliance.espi.common.dto.usage.ApplicationInformationDto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -123,99 +122,4 @@ void shouldReturn404WhenNotExists() throws Exception { .andExpect(status().isNotFound()); } } - - @Nested - @DisplayName("POST /espi/1_1/resource/ApplicationInformation") - class CreateApplicationInformation { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 201 Created and the created application") - void shouldReturn201Created() throws Exception { - ApplicationInformationDto dto = new ApplicationInformationDto(); - dto.setClientId("test-client"); - ApplicationInformationEntity entity = new ApplicationInformationEntity(); - entity.setId(UUID.randomUUID()); - - when(applicationInformationService.fromDto(any(ApplicationInformationDto.class))).thenReturn(entity); - when(applicationInformationService.save(any(ApplicationInformationEntity.class))).thenReturn(entity); - - mockMvc.perform(post("/espi/1_1/resource/ApplicationInformation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(dto))) - .andExpect(status().isCreated()) - .andExpect(header().exists("Location")); - } - } - - @Nested - @DisplayName("PUT /espi/1_1/resource/ApplicationInformation/{applicationInformationId}") - class UpdateApplicationInformation { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 200 OK and the updated application") - void shouldReturn200Ok() throws Exception { - UUID id = UUID.randomUUID(); - ApplicationInformationDto dto = new ApplicationInformationDto(); - ApplicationInformationEntity entity = new ApplicationInformationEntity(); - entity.setId(id); - - when(applicationInformationService.findById(id)).thenReturn(entity); - when(applicationInformationService.fromDto(any(ApplicationInformationDto.class))).thenReturn(entity); - when(applicationInformationService.save(any(ApplicationInformationEntity.class))).thenReturn(entity); - - mockMvc.perform(put("/espi/1_1/resource/ApplicationInformation/" + id) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(dto))) - .andExpect(status().isOk()); - } - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 404 Not Found when application does not exist") - void shouldReturn404WhenNotExists() throws Exception { - UUID id = UUID.randomUUID(); - ApplicationInformationDto dto = new ApplicationInformationDto(); - - when(applicationInformationService.findById(id)).thenReturn(null); - - mockMvc.perform(put("/espi/1_1/resource/ApplicationInformation/" + id) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(dto))) - .andExpect(status().isNotFound()); - } - } - - @Nested - @DisplayName("DELETE /espi/1_1/resource/ApplicationInformation/{applicationInformationId}") - class DeleteApplicationInformation { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 204 No Content when application is deleted") - void shouldReturn204NoContent() throws Exception { - UUID id = UUID.randomUUID(); - ApplicationInformationEntity entity = new ApplicationInformationEntity(); - - when(applicationInformationService.findById(id)).thenReturn(entity); - - mockMvc.perform(delete("/espi/1_1/resource/ApplicationInformation/" + id)) - .andExpect(status().isNoContent()); - - verify(applicationInformationService).deleteById(id); - } - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 404 Not Found when application does not exist") - void shouldReturn404WhenNotExists() throws Exception { - UUID id = UUID.randomUUID(); - - when(applicationInformationService.findById(id)).thenReturn(null); - - mockMvc.perform(delete("/espi/1_1/resource/ApplicationInformation/" + id)) - .andExpect(status().isNotFound()); - } - } } diff --git a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTControllerTest.java b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTControllerTest.java index b26893e5..8814e633 100644 --- a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTControllerTest.java +++ b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerAccountRESTControllerTest.java @@ -118,69 +118,4 @@ void shouldReturn404WhenNotExists() throws Exception { .andExpect(status().isNotFound()); } } - - @Nested - @DisplayName("POST /espi/1_1/resource/CustomerAccount") - class CreateCustomerAccount { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 201 Created when data is valid and user is admin") - void shouldReturn201ForAdmin() throws Exception { - CustomerAccountDto dto = new CustomerAccountDto(); - CustomerAccountEntity entity = new CustomerAccountEntity(); - entity.setId(UUID.randomUUID()); - - when(customerAccountMapper.toEntity(any(CustomerAccountDto.class))).thenReturn(entity); - when(customerAccountService.save(any(CustomerAccountEntity.class))).thenReturn(entity); - when(customerAccountMapper.toDto(any(CustomerAccountEntity.class))).thenReturn(dto); - - mockMvc.perform(post("/espi/1_1/resource/CustomerAccount") - .contentType(MediaType.APPLICATION_JSON) - .content("{}")) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").doesNotExist()); // ID is in location header, DTO shouldn't have it per plan (if it's a creation DTO) - // Wait, CustomerAccountDto has id in some cases, but per guidelines creation DTO shouldn't have id. - } - } - - @Nested - @DisplayName("PUT /espi/1_1/resource/CustomerAccount/{customerAccountId}") - class UpdateCustomerAccount { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 200 OK when update is successful") - void shouldReturn200OnSuccess() throws Exception { - UUID id = UUID.randomUUID(); - CustomerAccountDto dto = new CustomerAccountDto(); - CustomerAccountEntity entity = new CustomerAccountEntity(); - - when(customerAccountRepository.existsById(id)).thenReturn(true); - when(customerAccountMapper.toEntity(any(CustomerAccountDto.class))).thenReturn(entity); - when(customerAccountService.save(any(CustomerAccountEntity.class))).thenReturn(entity); - when(customerAccountMapper.toDto(any(CustomerAccountEntity.class))).thenReturn(dto); - - mockMvc.perform(put("/espi/1_1/resource/CustomerAccount/" + id) - .contentType(MediaType.APPLICATION_JSON) - .content("{}")) - .andExpect(status().isOk()); - } - } - - @Nested - @DisplayName("DELETE /espi/1_1/resource/CustomerAccount/{customerAccountId}") - class DeleteCustomerAccount { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 204 No Content when deletion is successful") - void shouldReturn204OnSuccess() throws Exception { - UUID id = UUID.randomUUID(); - when(customerAccountRepository.existsById(id)).thenReturn(true); - - mockMvc.perform(delete("/espi/1_1/resource/CustomerAccount/" + id)) - .andExpect(status().isNoContent()); - } - } } diff --git a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTControllerTest.java b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTControllerTest.java index d9832c0b..7589ec29 100644 --- a/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTControllerTest.java +++ b/openespi-datacustodian/src/test/java/org/greenbuttonalliance/espi/datacustodian/web/api/CustomerRESTControllerTest.java @@ -162,68 +162,4 @@ void shouldReturn404WhenNotExists() throws Exception { .andExpect(status().isNotFound()); } } - - @Nested - @DisplayName("POST /espi/1_1/resource/Customer") - class CreateCustomer { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 201 Created when data is valid and user is admin") - void shouldReturn201ForAdmin() throws Exception { - CustomerDto dto = new CustomerDto(); - CustomerEntity entity = new CustomerEntity(); - entity.setId(UUID.randomUUID()); - - when(customerMapper.toEntity(any(CustomerDto.class))).thenReturn(entity); - when(customerService.save(any(CustomerEntity.class))).thenReturn(entity); - when(customerMapper.toDto(any(CustomerEntity.class))).thenReturn(dto); - - mockMvc.perform(post("/espi/1_1/resource/Customer") - .contentType(MediaType.APPLICATION_JSON) - .content("{}")) - .andExpect(status().isCreated()) - .andExpect(header().exists("Location")); - } - } - - @Nested - @DisplayName("PUT /espi/1_1/resource/Customer/{customerId}") - class UpdateCustomer { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 200 OK when update is successful") - void shouldReturn200OnSuccess() throws Exception { - UUID id = UUID.randomUUID(); - CustomerDto dto = new CustomerDto(); - CustomerEntity entity = new CustomerEntity(); - - when(customerService.existsById(id)).thenReturn(true); - when(customerMapper.toEntity(any(CustomerDto.class))).thenReturn(entity); - when(customerService.save(any(CustomerEntity.class))).thenReturn(entity); - when(customerMapper.toDto(any(CustomerEntity.class))).thenReturn(dto); - - mockMvc.perform(put("/espi/1_1/resource/Customer/" + id) - .contentType(MediaType.APPLICATION_JSON) - .content("{}")) - .andExpect(status().isOk()); - } - } - - @Nested - @DisplayName("DELETE /espi/1_1/resource/Customer/{customerId}") - class DeleteCustomer { - - @Test - @WithMockUser(authorities = "SCOPE_DataCustodian_Admin_Access") - @DisplayName("Should return 204 No Content when deletion is successful") - void shouldReturn204OnSuccess() throws Exception { - UUID id = UUID.randomUUID(); - when(customerService.existsById(id)).thenReturn(true); - - mockMvc.perform(delete("/espi/1_1/resource/Customer/" + id)) - .andExpect(status().isNoContent()); - } - } }