diff --git a/java/pom.xml b/java/pom.xml
index 5c7fab31d..045a2f7e5 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -5,12 +5,12 @@
4.0.0
- com.amido.stacks.modules
+ com.ensono.stacks.modules
stacks-modules-parent
- 3.0.26-RELEASE
+ 3.0.52
- com.amido.stacks.workloads
+ com.ensono.stacks.workloads
stacks-api
1.0.0
@@ -18,27 +18,41 @@
Demo project for Java
- 1.0.5.11-RELEASE
- 2.0.7
- no-aws
- no-azure
+
+
+
+
+
+
+
+
+
+
+
2.6.4
4.0.0
4.0.10
- 4.6.15
- 4.6.15
+ 4.6.15
+ 4.6.15
1.12.779
1.9.9.1
3.5.0
2022.0.4
3.5.24
- 3.4.0
+
+ 2.2.0
+ 3.4.0
11.1.0
5.11.3
+ 5.2.5
+
+ 2024.1.38
+ 2.25
+
@@ -46,6 +60,8 @@
http
localhost
9000
+
+ ../../stacks-preprocessor-output
@@ -69,24 +85,16 @@
-
-
- com.amido.stacks.modules
- stacks-core-api
- ${stacks.core.api.version}
-
-
- org.aspectj
- aspectjweaver
-
-
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc.openapi.version}
+
- com.amido.stacks.modules
- stacks-core-commons
- ${stacks.core.commons.version}
+ org.springframework
+ spring-context
@@ -220,7 +228,7 @@
au.com.dius.pact
consumer
- ${au.com.dius.pact.consumer-version}
+ ${au.com.dius.pact.consumer.version}
test
@@ -249,9 +257,21 @@
org.springframework.data
spring-data-commons
- ${spring.data.commons}
+ ${spring.data.commons.version}
+
+ systems.manifold
+ manifold
+ ${manifold.version}
+
+
+
+ com.spotify.fmt
+ fmt-maven-plugin
+ ${fmt-maven-plugin.version}
+
+
@@ -261,7 +281,7 @@
spring-boot-maven-plugin
${spring.boot.version}
- com.amido.stacks.workloads.Application
+ com.ensono.stacks.workloads.Application
@@ -319,7 +339,7 @@
au.com.dius.pact.provider
maven
- ${au.com.dius.pact.provider.maven-version}
+ ${au.com.dius.pact.provider.version}
${pact.broker.url}
${pact.broker.token}
@@ -336,20 +356,20 @@
-
- com.coveo
- fmt-maven-plugin
- ${fmt-maven-plugin.version}
-
- true
- true
- .*\.java
-
- false
- false
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
org.apache.maven.plugins
maven-checkstyle-plugin
@@ -423,6 +443,109 @@
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+ 17
+ 17
+ UTF-8
+
+
+ -Xplugin:Manifold
+ -Amanifold.source.target=${prop.output.dir}
+ ${prop.aws}
+ ${prop.azure}
+ ${prop.cosmosdb}
+ ${prop.dynamodb}
+
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ systems.manifold
+ manifold-preprocessor
+ ${manifold.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${org.mapstruct.binding.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+ com.ensono.stacks
+ stacks-maven-plugin
+ 1.0-SNAPSHOT
+
+
+
+
+ stacks-clean-project
+
+
+ stacks-prepare-project
+
+
+ stacks-prepare-project-tests
+
+
+
+
+
+ ${prop.output.dir}
+ src/main/resources/templates/project-builder-config.json
+ src/main/resources/templates/
+
+
+
+
+ com.spotify.fmt
+ fmt-maven-plugin
+ ${fmt-maven-plugin.version}
+
+
+ compile
+
+ format
+
+
+
+
+ ${prop.output.dir}/src/main/java
+ true
+ true
+ .*\.java
+
+ false
+ false
+
+
+
@@ -432,29 +555,159 @@
aws
-
-
- .
-
-
+ -AUSE_AWS=
aws
+
+ 2.4.4
+
+ 1.12.579
+ 2.14.0
+ 10.1.15
+
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ ${tomcat-embed.version}
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-starter-aws-secrets-manager-config
+ ${spring.cloud.starter.aws.secrets.manager.config.version}
+
+
+
+
+
+ com.amazonaws
+ aws-java-sdk-core
+ ${aws-java-sdk.version}
+
+
+
+ com.amazonaws
+ aws-java-sdk-dynamodb
+ ${aws-java-sdk.version}
+
+
+
+ com.amazonaws
+ aws-java-sdk-s3
+ ${aws-java-sdk.version}
+
+
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-spring
+ ${aws-xray-recorder-sdk.version}
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-metrics
+ ${aws-xray-recorder-sdk.version}
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-aws-sdk-instrumentor
+ ${aws-xray-recorder-sdk.version}
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-log4j
+ ${aws-xray-recorder-sdk.version}
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-apache-http
+ ${aws-xray-recorder-sdk.version}
+
+
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ ${spring.cloud.starter.aws.secrets.manager.config.version}
+ pom
+ import
+
+
+ com.amazonaws
+ aws-java-sdk-bom
+ ${aws-java-sdk.version}
+ pom
+ import
+
+
+ com.amazonaws
+ aws-xray-recorder-sdk-bom
+ ${aws-xray-recorder-sdk.version}
+ pom
+ import
+
+
+
azure
-
-
- .
-
-
+ -AUSE_AZURE=
azure
+
+ com.azure.spring
+ azure-spring-boot
+ ${azure.springboot.version}
+
+
+ net.minidev
+ json-smart
+
+
+
+
+
+
+
+ cosmosdb
+
+ -AUSE_COSMOSDB=
+ cosmosdb
+
+ 5.6.0
+
+
+
+ com.azure
+ azure-spring-data-cosmos
+ ${azure-spring-data-cosmos.version}
+
+
+
+
+
+ dynamodb
+
+ -AUSE_DYNAMODB=
+ dynamodb
+
+
+
+ io.github.boostchicken
+ spring-data-dynamodb
+ ${spring.data.dynamodb.version}
+
@@ -518,7 +771,7 @@
${exec-maven-plugin.version}
- Add execution rigths on bash scripts
+ Add execution rights on bash scripts
process-resources
exec
diff --git a/java/src/main/java/com/ensono/stacks/aws/tracing/XRayInspector.java b/java/src/main/java/com/ensono/stacks/aws/tracing/XRayInspector.java
new file mode 100644
index 000000000..9260f0947
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/aws/tracing/XRayInspector.java
@@ -0,0 +1,24 @@
+package com.ensono.stacks.aws.tracing;
+
+
+#if USE_AWS
+import com.amazonaws.xray.spring.aop.BaseAbstractXRayInterceptor;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+
+@Aspect
+@Component
+@ConditionalOnProperty(value = "aws.xray.enabled", havingValue = "true")
+public class XRayInspector extends BaseAbstractXRayInterceptor {
+ @Override
+ @Pointcut("within(com.amido.stacks..*) && bean(*Controller)")
+ public void xrayEnabledClasses() {
+ // Pointcut
+ }
+}
+#else
+public class XRayInspector { }
+#endif
diff --git a/java/src/main/java/com/ensono/stacks/aws/tracing/XRayTracingFilter.java b/java/src/main/java/com/ensono/stacks/aws/tracing/XRayTracingFilter.java
new file mode 100644
index 000000000..8a61f50a8
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/aws/tracing/XRayTracingFilter.java
@@ -0,0 +1,46 @@
+package com.ensono.stacks.aws.tracing;
+
+#if USE_AWS
+
+import com.amazonaws.xray.AWSXRay;
+import com.amazonaws.xray.AWSXRayRecorderBuilder;
+import com.amazonaws.xray.jakarta.servlet.AWSXRayServletFilter;
+import com.amazonaws.xray.log4j.Log4JSegmentListener;
+import com.amazonaws.xray.metrics.MetricsSegmentListener;
+import com.amazonaws.xray.plugins.EC2Plugin;
+import com.amazonaws.xray.plugins.EKSPlugin;
+import jakarta.servlet.Filter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnProperty(value = "aws.xray.enabled", havingValue = "true")
+@Slf4j
+public class XRayTracingFilter {
+
+ @Value("${spring.application.name:undefined}")
+ public String appName;
+
+ @Bean
+ public Filter xrayFilter() {
+
+ log.info("Initialising AWS XRay Support...");
+
+ AWSXRayRecorderBuilder builder =
+ AWSXRayRecorderBuilder.standard()
+ .withPlugin(new EC2Plugin())
+ .withPlugin(new EKSPlugin())
+ .withSegmentListener(new MetricsSegmentListener())
+ .withSegmentListener(new Log4JSegmentListener(appName));
+
+ AWSXRay.setGlobalRecorder(builder.build());
+
+ return new AWSXRayServletFilter(appName);
+ }
+}
+#else
+public class XRayTracingFilter {}
+#endif
diff --git a/java/src/main/java/com/ensono/stacks/core/api/annotations/CreateAPIResponses.java b/java/src/main/java/com/ensono/stacks/core/api/annotations/CreateAPIResponses.java
new file mode 100644
index 000000000..6a8cb01e6
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/annotations/CreateAPIResponses.java
@@ -0,0 +1,52 @@
+package com.ensono.stacks.core.api.annotations;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+import com.ensono.stacks.core.api.dto.response.ResourceCreatedResponse;
+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.security.SecurityRequirement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(
+ responseCode = "201",
+ description = "Resource created",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ResourceCreatedResponse.class)))
+@ApiResponse(
+ responseCode = "400",
+ description = "Bad Request",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized, Access token is missing or invalid",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "403",
+ description = "Forbidden, the user does not have permission to execute this operation",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "409",
+ description = "Conflict, an item already exists",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@SecurityRequirement(name = "bearerAuth")
+public @interface CreateAPIResponses {}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/annotations/DeleteAPIResponses.java b/java/src/main/java/com/ensono/stacks/core/api/annotations/DeleteAPIResponses.java
new file mode 100644
index 000000000..d03a47d00
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/annotations/DeleteAPIResponses.java
@@ -0,0 +1,59 @@
+package com.ensono.stacks.core.api.annotations;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+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.security.SecurityRequirement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(
+ responseCode = "200",
+ description = "Success",
+ content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
+@ApiResponse(
+ responseCode = "204",
+ description = "No Content",
+ content = @Content(schema = @Schema(hidden = true)))
+@ApiResponse(
+ responseCode = "400",
+ description = "Bad Request",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized, Access token is missing or invalid",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "403",
+ description = "Forbidden, the user does not have permission to execute this operation",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "404",
+ description = "Resource not found",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "409",
+ description = "Conflict, an item already exists",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@SecurityRequirement(name = "bearerAuth")
+public @interface DeleteAPIResponses {}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/annotations/ReadAPIResponses.java b/java/src/main/java/com/ensono/stacks/core/api/annotations/ReadAPIResponses.java
new file mode 100644
index 000000000..56ab7ccc3
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/annotations/ReadAPIResponses.java
@@ -0,0 +1,30 @@
+package com.ensono.stacks.core.api.annotations;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+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.security.SecurityRequirement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(
+ responseCode = "404",
+ description = "Resource not found",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "400",
+ description = "Bad Request",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@SecurityRequirement(name = "bearerAuth")
+public @interface ReadAPIResponses {}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/annotations/SearchAPIResponses.java b/java/src/main/java/com/ensono/stacks/core/api/annotations/SearchAPIResponses.java
new file mode 100644
index 000000000..a12262623
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/annotations/SearchAPIResponses.java
@@ -0,0 +1,23 @@
+package com.ensono.stacks.core.api.annotations;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+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.security.SecurityRequirement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(
+ responseCode = "400",
+ description = "Bad Request",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@SecurityRequirement(name = "bearerAuth")
+public @interface SearchAPIResponses {}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/annotations/UpdateAPIResponses.java b/java/src/main/java/com/ensono/stacks/core/api/annotations/UpdateAPIResponses.java
new file mode 100644
index 000000000..f4618f071
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/annotations/UpdateAPIResponses.java
@@ -0,0 +1,66 @@
+package com.ensono.stacks.core.api.annotations;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+import com.ensono.stacks.core.api.dto.response.ResourceUpdatedResponse;
+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.security.SecurityRequirement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ApiResponse(
+ responseCode = "200",
+ description = "Success",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ResourceUpdatedResponse.class)))
+@ApiResponse(
+ responseCode = "204",
+ description = "No Content",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "400",
+ description = "Bad Request",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized, Access token is missing or invalid",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "403",
+ description = "Forbidden, the user does not have permission to execute this operation",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "404",
+ description = "Resource not found",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@ApiResponse(
+ responseCode = "409",
+ description = "Conflict, an item already exists",
+ content =
+ @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = ErrorResponse.class)))
+@SecurityRequirement(name = "bearerAuth")
+public @interface UpdateAPIResponses {}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/dto/ErrorResponse.java b/java/src/main/java/com/ensono/stacks/core/api/dto/ErrorResponse.java
new file mode 100644
index 000000000..a1faf1ddb
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/dto/ErrorResponse.java
@@ -0,0 +1,16 @@
+package com.ensono.stacks.core.api.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErrorResponse {
+
+ int errorCode;
+ int operationCode;
+ String correlationId;
+ String description;
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/dto/request/GenerateTokenRequest.java b/java/src/main/java/com/ensono/stacks/core/api/dto/request/GenerateTokenRequest.java
new file mode 100644
index 000000000..c6286b0e8
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/dto/request/GenerateTokenRequest.java
@@ -0,0 +1,30 @@
+package com.ensono.stacks.core.api.dto.request;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class GenerateTokenRequest {
+
+ @JsonProperty("client_id")
+ @NotBlank
+ private String client_id = null;
+
+ @JsonProperty("client_secret")
+ @NotBlank
+ private String client_secret = null;
+
+ @JsonProperty("audience")
+ @NotNull
+ private String audience = null;
+
+ @JsonProperty("grant_type")
+ @NotNull
+ private String grant_type = null;
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/dto/response/GenerateTokenResponse.java b/java/src/main/java/com/ensono/stacks/core/api/dto/response/GenerateTokenResponse.java
new file mode 100644
index 000000000..6bea67da8
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/dto/response/GenerateTokenResponse.java
@@ -0,0 +1,21 @@
+package com.ensono.stacks.core.api.dto.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class GenerateTokenResponse {
+
+ @JsonProperty("access_token")
+ private String access_token = null;
+
+ @JsonProperty("expires_in")
+ private String expires_in = null;
+
+ @JsonProperty("token_type")
+ private String token_type = null;
+}
diff --git a/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceCreatedResponse.java b/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceCreatedResponse.java
new file mode 100644
index 000000000..8c5880be3
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceCreatedResponse.java
@@ -0,0 +1,16 @@
+package com.ensono.stacks.core.api.dto.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResourceCreatedResponse {
+
+ @JsonProperty("id")
+ private UUID id = null;
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceUpdatedResponse.java b/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceUpdatedResponse.java
new file mode 100644
index 000000000..702cd40e4
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/dto/response/ResourceUpdatedResponse.java
@@ -0,0 +1,15 @@
+package com.ensono.stacks.core.api.dto.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResourceUpdatedResponse {
+ @JsonProperty("id")
+ private UUID id = null;
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/exception/ApiException.java b/java/src/main/java/com/ensono/stacks/core/api/exception/ApiException.java
new file mode 100644
index 000000000..abbe52e00
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/exception/ApiException.java
@@ -0,0 +1,23 @@
+package com.ensono.stacks.core.api.exception;
+
+public abstract class ApiException extends RuntimeException {
+
+ final int operationCode;
+ final String correlationId;
+
+ protected ApiException(String message, int operationCode, String correlationId) {
+ super(message);
+ this.operationCode = operationCode;
+ this.correlationId = correlationId;
+ }
+
+ public int getOperationCode() {
+ return operationCode;
+ }
+
+ public String getCorrelationId() {
+ return correlationId;
+ }
+
+ public abstract int getExceptionCode();
+}
\ No newline at end of file
diff --git a/java/src/main/java/com/ensono/stacks/core/api/exception/ApiExceptionAdvice.java b/java/src/main/java/com/ensono/stacks/core/api/exception/ApiExceptionAdvice.java
new file mode 100644
index 000000000..7b37d57a7
--- /dev/null
+++ b/java/src/main/java/com/ensono/stacks/core/api/exception/ApiExceptionAdvice.java
@@ -0,0 +1,47 @@
+package com.ensono.stacks.core.api.exception;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
+
+import com.ensono.stacks.core.api.dto.ErrorResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@RestControllerAdvice
+public class ApiExceptionAdvice extends ResponseEntityExceptionHandler {
+
+ @Override
+ protected ResponseEntity