diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5379d05
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+**/.classpath
+.DS_Store
+**/nb-configuration.xml
+**/.springBeans
+**/testing.properties
+**/*.eml
+**/Scripts
+.idea
+*.iml
+.project
+.idea
+.metadata
+.DS_Store
+Servers
+.settings
+.classpath
+mvn-repo
+bin
+*.war
+*.log
+*/nbactions.xml
+*.eml
+nbactions.xml
+testing.properties
+*/test-output
+JargonVersion.java
+**/${test.option.mount.basedir}
+**/*.*~
+*.*~
+.dbeaver*
+test.*.properties
+**/target
+target
\ No newline at end of file
diff --git a/README.md b/README.md
index d074d38..d286a9b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,86 @@
-# notification-service
+# Notification Service(WIP)
A pluggable Spring Boot RESTful web service to enable Notification system in Metalnx.
+
+## Overview
+This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project.
+Refer: [OpenAPI-Spec](https://github.com/swagger-api/swagger-core)
+
+The underlying library integrating swagger to SpringBoot is [springfox](https://github.com/springfox/springfox)
+
+Start your server as an simple java application
+
+You can view the api documentation in swagger-ui by pointing to
+http://localhost:8888/swagger-ui.html
+
+Change default port value in application.properties
+
+
+## Database schema migration and setup
+Database schema should be configured using [flywaydb](https://flywaydb.org/). This project includes code that supports database migrations across versions.
+
+Reference [docs](https://flywaydb.org/documentation/)
+
+We'll use the Maven plugin in our own processes and documentation, but there are other options as well.
+
+>**N.B.:**
+User and password information needs to be kept secure. There are maven plugin configuration [options](https://flywaydb.org/documentation/maven/).
+
+1. Set up the database (we will use postgres in the spirit of the iRODS install)
+```bash
+$ (sudo) su - postgres
+postgres$ psql
+psql> CREATE USER irodsext WITH PASSWORD 'password';
+psql> CREATE DATABASE "notification";
+psql> GRANT ALL PRIVILEGES ON DATABASE "notifications" TO irodsext;
+psql> ALTER USER irodsext WITH SUPERUSER;
+psql>Alter user irodsext with SUPERUSER;
+```
+
+> need to add superuser role to use uuid-ossp
+
+1. Setting up Maven profile
+```bash
+vi ~/.m2/settings.xml
+```
+```xml
+
+ flyway-local
+
+ jdbc:postgresql://:/
+ irodsext
+ password
+ public
+
+
+```
+1. Clean database (**Warning** destructive! clears your database)
+```bash
+mvn flyway:clean
+```
+1. Migration, this is the first command to run from 0 on a clean database
+```bash
+mvn flyway:migrate
+```
+
+ For future migration use benchmark
+ ```bash
+ mvn flyway:benchmark
+ ```
+ > Looks at existing database to set to initial version. This is not for new databases.
+
+## Initial database table: "notification"
+
+|attribute_name|datatype|comment|
+|---|---|---|
+| id(PK) | BIGINT | auto generated sequence |
+| notification_id | uuid | using uuid-ossp module functions to generate universally unique identifiers (UUIDs) |
+| sender_id | VARCHAR | notification sender's userId extracted from JWT |
+| recipient_id | VARCHAR | notification recipient's userId |
+| subject | TEXT | notification subject |
+| message | TEXT | notification message |
+| seen | BOOLEAN | boolean for user's seen/unseen notification status |
+| deleted | BOOLEAN | boolean for deleted notification, notification will not be deleted from postgres db |
+| date_created | TIMESTAMP | timestamp auto-generated by postgres db |
+| severity_level | INTEGER | severity level of notification an integer value between 1 and 5 |
+| notification_type | VARCHAR | type of notification options are workflow, permission, system |
+| data_location_url | TEXT | notification associated logical location of data |
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6c3d817
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,135 @@
+
+ 4.0.0
+ gov.nih.niehs.notification
+ notification-service
+ jar
+ Notification Service
+ 1.0.0
+
+ 1.7
+ ${java.version}
+ ${java.version}
+ 2.9.2
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.22.RELEASE
+
+
+ src/main/java
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+ org.flywaydb
+ flyway-maven-plugin
+ 4.2.0
+
+ ${flyway.jdbc.url}
+ ${flyway.db.user}
+ ${flyway.db.password}
+
+ ${flyway.db.schema}
+
+
+ classpath:migrations
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+ org.springframework.security
+ spring-security-web
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+
+
+ org.springframework.security
+ spring-security-config
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.0
+
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+
+ io.springfox
+ springfox-swagger2
+ ${springfox-version}
+
+
+ io.springfox
+ springfox-swagger-ui
+ ${springfox-version}
+
+
+
+ com.github.joschi.jackson
+ jackson-datatype-threetenbp
+ 2.6.4
+
+
+
+ javax.validation
+ validation-api
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
diff --git a/src/main/java/gov/nih/niehs/notification/RFC3339DateFormat.java b/src/main/java/gov/nih/niehs/notification/RFC3339DateFormat.java
new file mode 100644
index 0000000..0b0d706
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/RFC3339DateFormat.java
@@ -0,0 +1,22 @@
+package gov.nih.niehs.notification;
+
+import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
+import com.fasterxml.jackson.databind.util.ISO8601Utils;
+
+import java.text.FieldPosition;
+import java.util.Date;
+
+
+public class RFC3339DateFormat extends ISO8601DateFormat {
+
+ private static final long serialVersionUID = 1L;
+
+ // Same as ISO8601DateFormat but serializing milliseconds.
+ @Override
+ public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
+ String value = ISO8601Utils.format(date, true);
+ toAppendTo.append(value);
+ return toAppendTo;
+ }
+
+}
diff --git a/src/main/java/gov/nih/niehs/notification/Swagger2SpringBoot.java b/src/main/java/gov/nih/niehs/notification/Swagger2SpringBoot.java
new file mode 100644
index 0000000..8c24224
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/Swagger2SpringBoot.java
@@ -0,0 +1,36 @@
+package gov.nih.niehs.notification;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.ExitCodeGenerator;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@SpringBootApplication
+@EnableSwagger2
+@ComponentScan(basePackages = { "gov.nih.niehs.notification", "gov.nih.niehs.notification.api" , "gov.nih.niehs.notification.configuration"})
+public class Swagger2SpringBoot implements CommandLineRunner {
+
+ @Override
+ public void run(String... arg0) throws Exception {
+ if (arg0.length > 0 && arg0[0].equals("exitcode")) {
+ throw new ExitException();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new SpringApplication(Swagger2SpringBoot.class).run(args);
+ }
+
+ class ExitException extends RuntimeException implements ExitCodeGenerator {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public int getExitCode() {
+ return 10;
+ }
+
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/api/ApiException.java b/src/main/java/gov/nih/niehs/notification/api/ApiException.java
new file mode 100644
index 0000000..f568e8e
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/ApiException.java
@@ -0,0 +1,11 @@
+package gov.nih.niehs.notification.api;
+
+
+public class ApiException extends Exception {
+ private int code;
+
+ public ApiException(int code, String msg) {
+ super(msg);
+ this.code = code;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/api/ApiOriginFilter.java b/src/main/java/gov/nih/niehs/notification/api/ApiOriginFilter.java
new file mode 100644
index 0000000..2af8109
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/ApiOriginFilter.java
@@ -0,0 +1,26 @@
+package gov.nih.niehs.notification.api;
+
+import java.io.IOException;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+
+public class ApiOriginFilter implements javax.servlet.Filter {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpServletResponse res = (HttpServletResponse) response;
+ res.addHeader("Access-Control-Allow-Origin", "*");
+ res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
+ res.addHeader("Access-Control-Allow-Headers", "Content-Type");
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/api/ApiResponseMessage.java b/src/main/java/gov/nih/niehs/notification/api/ApiResponseMessage.java
new file mode 100644
index 0000000..8fed828
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/ApiResponseMessage.java
@@ -0,0 +1,69 @@
+package gov.nih.niehs.notification.api;
+
+
+import javax.xml.bind.annotation.XmlTransient;
+
+@javax.xml.bind.annotation.XmlRootElement
+public class ApiResponseMessage {
+ public static final int ERROR = 1;
+ public static final int WARNING = 2;
+ public static final int INFO = 3;
+ public static final int OK = 4;
+ public static final int TOO_BUSY = 5;
+
+ int code;
+ String type;
+ String message;
+
+ public ApiResponseMessage(){}
+
+ public ApiResponseMessage(int code, String message){
+ this.code = code;
+ switch(code){
+ case ERROR:
+ setType("error");
+ break;
+ case WARNING:
+ setType("warning");
+ break;
+ case INFO:
+ setType("info");
+ break;
+ case OK:
+ setType("ok");
+ break;
+ case TOO_BUSY:
+ setType("too busy");
+ break;
+ default:
+ setType("unknown");
+ break;
+ }
+ this.message = message;
+ }
+
+ @XmlTransient
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/api/NotFoundException.java b/src/main/java/gov/nih/niehs/notification/api/NotFoundException.java
new file mode 100644
index 0000000..90ccb81
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/NotFoundException.java
@@ -0,0 +1,9 @@
+package gov.nih.niehs.notification.api;
+
+public class NotFoundException extends ApiException {
+ private int code;
+ public NotFoundException (int code, String msg) {
+ super(code, msg);
+ this.code = code;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/api/NotificationApi.java b/src/main/java/gov/nih/niehs/notification/api/NotificationApi.java
new file mode 100644
index 0000000..986ecdc
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/NotificationApi.java
@@ -0,0 +1,74 @@
+package gov.nih.niehs.notification.api;
+
+import javax.validation.Valid;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import gov.nih.niehs.notification.model.AddNotification;
+import gov.nih.niehs.notification.model.BulkNotificationOptStatus;
+import gov.nih.niehs.notification.model.NotificationArray;
+import gov.nih.niehs.notification.model.UuidList;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
+
+@Api(value = "notification", description = "the notification API")
+public interface NotificationApi {
+
+ @ApiOperation(value = "Add new notification", nickname = "addNotification", notes = "Query database to insert new notification", authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = { @ApiResponse(code = 201, message = "notification added successfully"),
+ @ApiResponse(code = 400, message = "bad request") })
+ @RequestMapping(value = "/addNotification", consumes = { "application/json" }, method = RequestMethod.POST)
+ ResponseEntity addNotification(
+ @ApiParam(value = "Schema to add new notification") @Valid @RequestBody AddNotification body);
+
+ @ApiOperation(value = "Reterieve notifications", nickname = "getNotifications", notes = "Query database to get all the notifications for session user", response = NotificationArray.class, authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "List of all notifications", response = NotificationArray.class) })
+ @RequestMapping(value = "/getNotification", produces = { "application/json" }, method = RequestMethod.GET)
+ ResponseEntity getNotifications();
+
+ @ApiOperation(value = "Update notification as seen", nickname = "markSeen", notes = "Query database to mark of notifications as seen once user is validated of ownership", response = BulkNotificationOptStatus.class, authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "notifications marked seen successfully", response = BulkNotificationOptStatus.class),
+ @ApiResponse(code = 409, message = "conflict in update", response = BulkNotificationOptStatus.class) })
+ @RequestMapping(value = "/markSeen", produces = { "application/json" }, consumes = {
+ "application/json" }, method = RequestMethod.PUT)
+ ResponseEntity markSeen(
+ @ApiParam(value = "List of uuids one for each notification") @Valid @RequestBody UuidList body);
+
+ @ApiOperation(value = "Reterieve all unseen notifications", nickname = "getUnseenNotifications", notes = "Query database to get all unseen notifications for session user", response = NotificationArray.class, authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "List of all notifications", response = NotificationArray.class) })
+ @RequestMapping(value = "/getUnseenNotifications", produces = { "application/json" }, method = RequestMethod.GET)
+ ResponseEntity getUnseenNotifications();
+
+ @ApiOperation(value = "Reterieve all unseen notifications", nickname = "getUnseenNotificationsCount", notes = "Query database to get all unseen notifications for session user", response = Integer.class, authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "count reterieved sucessfully", response = Integer.class) })
+ @RequestMapping(value = "/getUnseenNotificationsCount", produces = {
+ "application/json" }, method = RequestMethod.GET)
+ ResponseEntity getUnseenNotificationsCount();
+
+ @ApiOperation(value = "Delete notifications", nickname = "deleteNotifications", notes = "Query database to delete notifications once user is validated of ownership", response = BulkNotificationOptStatus.class, authorizations = {
+ @Authorization(value = "BearerAuth") }, tags = { "Notification", })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "notifications marked seen successfully", response = BulkNotificationOptStatus.class),
+ @ApiResponse(code = 409, message = "conflict in update", response = BulkNotificationOptStatus.class) })
+ @RequestMapping(value = "/deleteNotifications", produces = { "application/json" }, consumes = {
+ "application/json" }, method = RequestMethod.PUT)
+ ResponseEntity deleteNotifications(
+ @ApiParam(value = "List of uuids one for each notification") @Valid @RequestBody UuidList body);
+
+}
\ No newline at end of file
diff --git a/src/main/java/gov/nih/niehs/notification/api/NotificationApiController.java b/src/main/java/gov/nih/niehs/notification/api/NotificationApiController.java
new file mode 100644
index 0000000..09dea3c
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/api/NotificationApiController.java
@@ -0,0 +1,133 @@
+package gov.nih.niehs.notification.api;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import gov.nih.niehs.notification.model.AddNotification;
+import gov.nih.niehs.notification.model.BulkNotificationOptStatus;
+import gov.nih.niehs.notification.model.BulkNotificationOptStatusInner;
+import gov.nih.niehs.notification.model.NotificationArray;
+import gov.nih.niehs.notification.model.UuidList;
+import gov.nih.niehs.notification.security.UserDetails;
+import gov.nih.niehs.notification.service.NotificationServiceImpl;
+import io.swagger.annotations.ApiParam;
+
+@Controller
+public class NotificationApiController implements NotificationApi {
+
+ private static final Logger logger = LoggerFactory.getLogger(NotificationApiController.class);
+
+ private final ObjectMapper objectMapper;
+
+ private final HttpServletRequest request;
+
+ @Autowired
+ public NotificationApiController(ObjectMapper objectMapper, HttpServletRequest request) {
+ this.objectMapper = objectMapper;
+ this.request = request;
+ }
+
+ @Autowired
+ private NotificationServiceImpl notificationService;
+
+ @Autowired
+ private UserDetails userDetails;
+
+ public ResponseEntity addNotification(
+ @ApiParam(value = "Schema to add new notification") @Valid @RequestBody AddNotification body) {
+ logger.info("controller addNotification()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("sessionUser: {}", sessionUser);
+ logger.info("add notification: {}", body);
+ Integer status = notificationService.addNotification(userDetails.getSessionUserId(), body.getRecipientId(),
+ body.getSubject(), body.getMessage(), body.getSeverityLevel(), body.getNotificationType(),
+ body.getDataLocationUrl());
+ if (status > 0) {
+ logger.info("Notification added successfully");
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ } else {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ public ResponseEntity getNotifications() {
+ logger.info("controller getNotifications()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("Retrieve notifications for session user: {}", sessionUser);
+ NotificationArray notifications = notificationService.getAllNotifications(sessionUser);
+ return new ResponseEntity(notifications, HttpStatus.OK);
+ }
+
+ public ResponseEntity markSeen(
+ @ApiParam(value = "List of uuids one for each notification") @Valid @RequestBody UuidList body) {
+ logger.info("contoller markseen()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("User to validate ownership. userId: {}", sessionUser);
+ logger.info("markSeen notification uuidList: {}", body);
+ BulkNotificationOptStatus status = notificationService.markSeen(sessionUser, body);
+ logger.info("request status: {}", status);
+
+ // validating update status
+ Integer updateCount = 0;
+ for (BulkNotificationOptStatusInner entry : status) {
+ if (entry.isOperationStatus()) {
+ updateCount++;
+ }
+ }
+ if (updateCount == body.size()) {
+ return new ResponseEntity(status, HttpStatus.OK);
+ } else {
+ return new ResponseEntity(status, HttpStatus.CONFLICT);
+ }
+
+ }
+
+ public ResponseEntity getUnseenNotifications() {
+ logger.info("controller getUnseenNotifications()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("Reterieve unseen notifications for session user: {}", sessionUser);
+ NotificationArray notifications = notificationService.getUnseenNotifications(sessionUser);
+ return new ResponseEntity(notifications, HttpStatus.OK);
+ }
+
+ public ResponseEntity getUnseenNotificationsCount() {
+ logger.info("controller getUnseenNotificationsCount()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("Reterieve unseen notifications count for session user: {}", sessionUser);
+ Integer count = notificationService.getUnseenNotificationsCount(sessionUser);
+ return new ResponseEntity(count, HttpStatus.OK);
+ }
+
+ public ResponseEntity deleteNotifications(
+ @ApiParam(value = "List of uuids one for each notification") @Valid @RequestBody UuidList body) {
+ logger.info("controller deleteNotifications()");
+ String sessionUser = userDetails.getSessionUserId();
+ logger.info("User to validate ownership. userId: {}", sessionUser);
+ logger.info("markSeen notification uuidList: {}", body);
+ BulkNotificationOptStatus status = notificationService.deleteNotifications(sessionUser, body);
+ logger.info("request status: {}", status);
+
+ // validating update status
+ Integer updateCount = 0;
+ for (BulkNotificationOptStatusInner entry : status) {
+ if (entry.isOperationStatus()) {
+ updateCount++;
+ }
+ }
+ if (updateCount == body.size()) {
+ return new ResponseEntity(status, HttpStatus.OK);
+ } else {
+ return new ResponseEntity(status, HttpStatus.CONFLICT);
+ }
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/configuration/CustomInstantDeserializer.java b/src/main/java/gov/nih/niehs/notification/configuration/CustomInstantDeserializer.java
new file mode 100644
index 0000000..20323d5
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/configuration/CustomInstantDeserializer.java
@@ -0,0 +1,231 @@
+package gov.nih.niehs.notification.configuration;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonTokenId;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.datatype.threetenbp.DateTimeUtils;
+import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils;
+import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase;
+import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction;
+import com.fasterxml.jackson.datatype.threetenbp.function.Function;
+import org.threeten.bp.DateTimeException;
+import org.threeten.bp.Instant;
+import org.threeten.bp.OffsetDateTime;
+import org.threeten.bp.ZoneId;
+import org.threeten.bp.ZonedDateTime;
+import org.threeten.bp.format.DateTimeFormatter;
+import org.threeten.bp.temporal.Temporal;
+import org.threeten.bp.temporal.TemporalAccessor;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ * Deserializer for ThreeTen temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
+ * Adapted from the jackson threetenbp InstantDeserializer to add support for deserializing rfc822 format.
+ *
+ * @author Nick Williams
+ */
+public class CustomInstantDeserializer
+ extends ThreeTenDateTimeDeserializerBase {
+ private static final long serialVersionUID = 1L;
+
+ public static final CustomInstantDeserializer INSTANT = new CustomInstantDeserializer(
+ Instant.class, DateTimeFormatter.ISO_INSTANT,
+ new Function() {
+ @Override
+ public Instant apply(TemporalAccessor temporalAccessor) {
+ return Instant.from(temporalAccessor);
+ }
+ },
+ new Function() {
+ @Override
+ public Instant apply(FromIntegerArguments a) {
+ return Instant.ofEpochMilli(a.value);
+ }
+ },
+ new Function() {
+ @Override
+ public Instant apply(FromDecimalArguments a) {
+ return Instant.ofEpochSecond(a.integer, a.fraction);
+ }
+ },
+ null
+ );
+
+ public static final CustomInstantDeserializer OFFSET_DATE_TIME = new CustomInstantDeserializer(
+ OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
+ new Function() {
+ @Override
+ public OffsetDateTime apply(TemporalAccessor temporalAccessor) {
+ return OffsetDateTime.from(temporalAccessor);
+ }
+ },
+ new Function() {
+ @Override
+ public OffsetDateTime apply(FromIntegerArguments a) {
+ return OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId);
+ }
+ },
+ new Function() {
+ @Override
+ public OffsetDateTime apply(FromDecimalArguments a) {
+ return OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId);
+ }
+ },
+ new BiFunction() {
+ @Override
+ public OffsetDateTime apply(OffsetDateTime d, ZoneId z) {
+ return d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()));
+ }
+ }
+ );
+
+ public static final CustomInstantDeserializer ZONED_DATE_TIME = new CustomInstantDeserializer(
+ ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
+ new Function() {
+ @Override
+ public ZonedDateTime apply(TemporalAccessor temporalAccessor) {
+ return ZonedDateTime.from(temporalAccessor);
+ }
+ },
+ new Function() {
+ @Override
+ public ZonedDateTime apply(FromIntegerArguments a) {
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId);
+ }
+ },
+ new Function() {
+ @Override
+ public ZonedDateTime apply(FromDecimalArguments a) {
+ return ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId);
+ }
+ },
+ new BiFunction() {
+ @Override
+ public ZonedDateTime apply(ZonedDateTime zonedDateTime, ZoneId zoneId) {
+ return zonedDateTime.withZoneSameInstant(zoneId);
+ }
+ }
+ );
+
+ protected final Function fromMilliseconds;
+
+ protected final Function fromNanoseconds;
+
+ protected final Function parsedToValue;
+
+ protected final BiFunction adjust;
+
+ protected CustomInstantDeserializer(Class supportedType,
+ DateTimeFormatter parser,
+ Function parsedToValue,
+ Function fromMilliseconds,
+ Function fromNanoseconds,
+ BiFunction adjust) {
+ super(supportedType, parser);
+ this.parsedToValue = parsedToValue;
+ this.fromMilliseconds = fromMilliseconds;
+ this.fromNanoseconds = fromNanoseconds;
+ this.adjust = adjust == null ? new BiFunction() {
+ @Override
+ public T apply(T t, ZoneId zoneId) {
+ return t;
+ }
+ } : adjust;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected CustomInstantDeserializer(CustomInstantDeserializer base, DateTimeFormatter f) {
+ super((Class) base.handledType(), f);
+ parsedToValue = base.parsedToValue;
+ fromMilliseconds = base.fromMilliseconds;
+ fromNanoseconds = base.fromNanoseconds;
+ adjust = base.adjust;
+ }
+
+ @Override
+ protected JsonDeserializer withDateFormat(DateTimeFormatter dtf) {
+ if (dtf == _formatter) {
+ return this;
+ }
+ return new CustomInstantDeserializer(this, dtf);
+ }
+
+ @Override
+ public T deserialize(JsonParser parser, DeserializationContext context) throws IOException {
+ //NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only
+ //string values have to be adjusted to the configured TZ.
+ switch (parser.getCurrentTokenId()) {
+ case JsonTokenId.ID_NUMBER_FLOAT: {
+ BigDecimal value = parser.getDecimalValue();
+ long seconds = value.longValue();
+ int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
+ return fromNanoseconds.apply(new FromDecimalArguments(
+ seconds, nanoseconds, getZone(context)));
+ }
+
+ case JsonTokenId.ID_NUMBER_INT: {
+ long timestamp = parser.getLongValue();
+ if (context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
+ return this.fromNanoseconds.apply(new FromDecimalArguments(
+ timestamp, 0, this.getZone(context)
+ ));
+ }
+ return this.fromMilliseconds.apply(new FromIntegerArguments(
+ timestamp, this.getZone(context)
+ ));
+ }
+
+ case JsonTokenId.ID_STRING: {
+ String string = parser.getText().trim();
+ if (string.length() == 0) {
+ return null;
+ }
+ if (string.endsWith("+0000")) {
+ string = string.substring(0, string.length() - 5) + "Z";
+ }
+ T value;
+ try {
+ TemporalAccessor acc = _formatter.parse(string);
+ value = parsedToValue.apply(acc);
+ if (context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)) {
+ return adjust.apply(value, this.getZone(context));
+ }
+ } catch (DateTimeException e) {
+ throw _peelDTE(e);
+ }
+ return value;
+ }
+ }
+ throw context.mappingException("Expected type float, integer, or string.");
+ }
+
+ private ZoneId getZone(DeserializationContext context) {
+ // Instants are always in UTC, so don't waste compute cycles
+ return (_valueClass == Instant.class) ? null : DateTimeUtils.timeZoneToZoneId(context.getTimeZone());
+ }
+
+ private static class FromIntegerArguments {
+ public final long value;
+ public final ZoneId zoneId;
+
+ private FromIntegerArguments(long value, ZoneId zoneId) {
+ this.value = value;
+ this.zoneId = zoneId;
+ }
+ }
+
+ private static class FromDecimalArguments {
+ public final long integer;
+ public final int fraction;
+ public final ZoneId zoneId;
+
+ private FromDecimalArguments(long integer, int fraction, ZoneId zoneId) {
+ this.integer = integer;
+ this.fraction = fraction;
+ this.zoneId = zoneId;
+ }
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/configuration/HomeController.java b/src/main/java/gov/nih/niehs/notification/configuration/HomeController.java
new file mode 100644
index 0000000..91b2b34
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/configuration/HomeController.java
@@ -0,0 +1,16 @@
+package gov.nih.niehs.notification.configuration;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * Home redirection to swagger api documentation
+ */
+@Controller
+public class HomeController {
+ @RequestMapping(value = "/")
+ public String index() {
+ System.out.println("swagger-ui.html");
+ return "redirect:swagger-ui.html";
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/configuration/JacksonConfiguration.java b/src/main/java/gov/nih/niehs/notification/configuration/JacksonConfiguration.java
new file mode 100644
index 0000000..b5ddf93
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/configuration/JacksonConfiguration.java
@@ -0,0 +1,23 @@
+package gov.nih.niehs.notification.configuration;
+
+import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.threeten.bp.Instant;
+import org.threeten.bp.OffsetDateTime;
+import org.threeten.bp.ZonedDateTime;
+
+@Configuration
+public class JacksonConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(ThreeTenModule.class)
+ ThreeTenModule threeTenModule() {
+ ThreeTenModule module = new ThreeTenModule();
+ module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT);
+ module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME);
+ module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME);
+ return module;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/configuration/SwaggerDocumentationConfig.java b/src/main/java/gov/nih/niehs/notification/configuration/SwaggerDocumentationConfig.java
new file mode 100644
index 0000000..8f3bc30
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/configuration/SwaggerDocumentationConfig.java
@@ -0,0 +1,39 @@
+package gov.nih.niehs.notification.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+@Configuration
+public class SwaggerDocumentationConfig {
+
+ ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("Notification Service API")
+ .description("This is a notification microservice for Metalnx")
+ .license("Apache 2.0")
+ .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
+ .termsOfServiceUrl("")
+ .version("1.0.0")
+ .contact(new Contact("","", "deep.patel@nih.gov"))
+ .build();
+ }
+
+ @Bean
+ public Docket customImplementation(){
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("gov.nih.niehs.notification.api"))
+ .build()
+ .directModelSubstitute(org.threeten.bp.LocalDate.class, java.sql.Date.class)
+ .directModelSubstitute(org.threeten.bp.OffsetDateTime.class, java.util.Date.class)
+ .apiInfo(apiInfo());
+ }
+
+}
diff --git a/src/main/java/gov/nih/niehs/notification/interfaces/NotificationRepository.java b/src/main/java/gov/nih/niehs/notification/interfaces/NotificationRepository.java
new file mode 100644
index 0000000..ab01190
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/interfaces/NotificationRepository.java
@@ -0,0 +1,45 @@
+package gov.nih.niehs.notification.interfaces;
+
+import java.util.UUID;
+
+import javax.transaction.Transactional;
+
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+
+import gov.nih.niehs.notification.model.Notification;
+import gov.nih.niehs.notification.model.NotificationArray;
+
+public interface NotificationRepository extends CrudRepository {
+
+ @Transactional
+ @Modifying
+ @Query(value = "INSERT INTO public.notification (sender_id, recipient_id, subject, message, severity_level, notification_type, data_location_url) values(:sender_id, :recipient_id, :subject, :message, :severity_level, :notification_type, :data_location_url)", nativeQuery = true)
+ public Integer addNotification(@Param("sender_id") String sender_id, @Param("recipient_id") String recipient_id,
+ @Param("subject") String subject, @Param("message") String message,
+ @Param("severity_level") Integer severity_level, @Param("notification_type") String notification_type,
+ @Param("data_location_url") String data_location_url);
+
+ @Query(value = "SELECT n.* FROM public.notification as n WHERE n.recipient_id= :recipient_id", nativeQuery = true)
+ public NotificationArray getAllNotification(@Param("recipient_id") String recipient_id);
+
+ @Transactional
+ @Modifying
+ @Query(value = "UPDATE public.notification SET seen= :seenFlag WHERE notification_id= :notification_id and recipient_id= :recipient_id", nativeQuery = true)
+ public Integer markSeen(@Param("recipient_id") String recipient_id, @Param("notification_id") UUID notification_id,
+ @Param("seenFlag") Boolean seenFlag);
+
+ @Query(value = "SELECT n.* FROM public.notification as n WHERE n.recipient_id= :recipient_id and n.seen= false", nativeQuery = true)
+ public NotificationArray getUnseenNotifications(@Param("recipient_id") String recipient_id);
+
+ @Query(value = "SELECT COUNT(n) FROM public.notification as n WHERE n.recipient_id= :recipient_id and n.seen= false", nativeQuery = true)
+ public Integer getUnseenNotificationsCount(@Param("recipient_id") String recipient_id);
+
+ @Transactional
+ @Modifying
+ @Query(value = "UPDATE public.notification SET deleted= :deletedFlag WHERE notification_id= :notification_id and recipient_id= :recipient_id", nativeQuery = true)
+ public Integer deleteNotifications(@Param("recipient_id") String recipient_id,
+ @Param("notification_id") UUID notification_id, @Param("deletedFlag") Boolean deletedFlag);
+}
diff --git a/src/main/java/gov/nih/niehs/notification/interfaces/NotificationService.java b/src/main/java/gov/nih/niehs/notification/interfaces/NotificationService.java
new file mode 100644
index 0000000..f0ade82
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/interfaces/NotificationService.java
@@ -0,0 +1,20 @@
+package gov.nih.niehs.notification.interfaces;
+
+import gov.nih.niehs.notification.model.BulkNotificationOptStatus;
+import gov.nih.niehs.notification.model.NotificationArray;
+import gov.nih.niehs.notification.model.UuidList;
+
+public interface NotificationService {
+ public Integer addNotification(String senderId, String recipientId, String subject, String message,
+ Integer severityLevel, String notificationType, String dataLocationUrl);
+
+ public NotificationArray getAllNotifications(String userId);
+
+ public BulkNotificationOptStatus markSeen(String userId, UuidList uuid);
+
+ public NotificationArray getUnseenNotifications(String userId);
+
+ public Integer getUnseenNotificationsCount(String userId);
+
+ public BulkNotificationOptStatus deleteNotifications(String userId, UuidList uuid);
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/AddNotification.java b/src/main/java/gov/nih/niehs/notification/model/AddNotification.java
new file mode 100644
index 0000000..24668bf
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/AddNotification.java
@@ -0,0 +1,209 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.Objects;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+import org.springframework.validation.annotation.Validated;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * AddNotification
+ */
+@Validated
+public class AddNotification {
+ @JsonProperty("recipient_id")
+ private String recipientId = null;
+
+ @JsonProperty("subject")
+ private String subject = null;
+
+ @JsonProperty("message")
+ private String message = null;
+
+ @JsonProperty("severity_level")
+ private Integer severityLevel = null;
+
+ @JsonProperty("notification_type")
+ private String notificationType = null;
+
+ @JsonProperty("data_location_url")
+ private String dataLocationUrl = null;
+
+ public AddNotification recipientId(String recipientId) {
+ this.recipientId = recipientId;
+ return this;
+ }
+
+ /**
+ * notification recipient's userID
+ *
+ * @return recipientId
+ **/
+ @ApiModelProperty(required = true, value = "notification recipient's userId")
+ @NotNull
+
+ public String getRecipientId() {
+ return recipientId;
+ }
+
+ public void setRecipientId(String recipientId) {
+ this.recipientId = recipientId;
+ }
+
+ public AddNotification subject(String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ /**
+ * notification subject
+ *
+ * @return subject
+ **/
+ @ApiModelProperty(required = true, value = "notification subject")
+ @NotNull
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public AddNotification message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * notification message content
+ *
+ * @return message
+ **/
+ @ApiModelProperty(required = true, value = "notification message content")
+ @NotNull
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public AddNotification severityLevel(Integer severityLevel) {
+ this.severityLevel = severityLevel;
+ return this;
+ }
+
+ /**
+ * severity level of notification an integer value between 1 and 5 minimum: 1
+ * maximum: 5
+ *
+ * @return severityLevel
+ **/
+ @ApiModelProperty(required = true, value = "severity level of notification an integer value between 1 and 5")
+ @NotNull
+
+ @Min(1)
+ @Max(5)
+ public Integer getSeverityLevel() {
+ return severityLevel;
+ }
+
+ public void setSeverityLevel(Integer severityLevel) {
+ this.severityLevel = severityLevel;
+ }
+
+ public AddNotification notificationType(String notificationType) {
+ this.notificationType = notificationType;
+ return this;
+ }
+
+ /**
+ * type of notification options are workflow, permission, system
+ *
+ * @return notificationType
+ **/
+ @ApiModelProperty(required = true, value = "type of notification options are workflow, permission, system")
+ @NotNull
+
+ public String getNotificationType() {
+ return notificationType;
+ }
+
+ public void setNotificationType(String notificationType) {
+ this.notificationType = notificationType;
+ }
+
+ public AddNotification dataLocationUrl(String dataLocationUrl) {
+ this.dataLocationUrl = dataLocationUrl;
+ return this;
+ }
+
+ /**
+ * notification associated logical location of data
+ *
+ * @return dataLocationUrl
+ **/
+ @ApiModelProperty(value = "notification associated logical location of data")
+
+ public String getDataLocationUrl() {
+ return dataLocationUrl;
+ }
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AddNotification addNotification = (AddNotification) o;
+ return Objects.equals(this.recipientId, addNotification.recipientId)
+ && Objects.equals(this.subject, addNotification.subject)
+ && Objects.equals(this.message, addNotification.message)
+ && Objects.equals(this.severityLevel, addNotification.severityLevel)
+ && Objects.equals(this.notificationType, addNotification.notificationType)
+ && Objects.equals(this.dataLocationUrl, addNotification.dataLocationUrl);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(recipientId, subject, message, severityLevel, notificationType, dataLocationUrl);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AddNotification {\n");
+
+ sb.append(" recipientId: ").append(toIndentedString(recipientId)).append("\n");
+ sb.append(" subject: ").append(toIndentedString(subject)).append("\n");
+ sb.append(" message: ").append(toIndentedString(message)).append("\n");
+ sb.append(" severityLevel: ").append(toIndentedString(severityLevel)).append("\n");
+ sb.append(" notificationType: ").append(toIndentedString(notificationType)).append("\n");
+ sb.append(" dataLocationUrl: ").append(toIndentedString(dataLocationUrl)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatus.java b/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatus.java
new file mode 100644
index 0000000..cc59cd9
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatus.java
@@ -0,0 +1,50 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * BulkNotificationOptStatus
+ */
+@Validated
+@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2019-12-06T14:02:52.798Z[GMT]")
+public class BulkNotificationOptStatus extends ArrayList {
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class BulkNotificationOptStatus {\n");
+ sb.append(" ").append(toIndentedString(super.toString())).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatusInner.java b/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatusInner.java
new file mode 100644
index 0000000..4a59e4c
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/BulkNotificationOptStatusInner.java
@@ -0,0 +1,115 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+import org.springframework.validation.annotation.Validated;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * BulkNotificationOptStatusInner
+ */
+@Validated
+public class BulkNotificationOptStatusInner {
+
+ public BulkNotificationOptStatusInner(UUID notificationId, Boolean operationStatus) {
+ super();
+ this.notificationId = notificationId;
+ this.operationStatus = operationStatus;
+ }
+
+ @JsonProperty("notification_id")
+ private UUID notificationId = null;
+
+ @JsonProperty("operation_status")
+ private Boolean operationStatus = null;
+
+ public BulkNotificationOptStatusInner notificationId(UUID notificationId) {
+ this.notificationId = notificationId;
+ return this;
+ }
+
+ /**
+ * an unique uuid auto-generated by postgres db
+ *
+ * @return notificationId
+ **/
+ @ApiModelProperty(required = true, value = "an unique uuid auto-generated by postgres db")
+ @NotNull
+
+ @Valid
+ public UUID getNotificationId() {
+ return notificationId;
+ }
+
+ public void setNotificationId(UUID notificationId) {
+ this.notificationId = notificationId;
+ }
+
+ public BulkNotificationOptStatusInner operationStatus(Boolean operationStatus) {
+ this.operationStatus = operationStatus;
+ return this;
+ }
+
+ /**
+ * Get operationStatus
+ *
+ * @return operationStatus
+ **/
+ @ApiModelProperty(required = true, value = "")
+ @NotNull
+
+ public Boolean isOperationStatus() {
+ return operationStatus;
+ }
+
+ public void setOperationStatus(Boolean operationStatus) {
+ this.operationStatus = operationStatus;
+ }
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BulkNotificationOptStatusInner bulkNotificationOptStatusInner = (BulkNotificationOptStatusInner) o;
+ return Objects.equals(this.notificationId, bulkNotificationOptStatusInner.notificationId)
+ && Objects.equals(this.operationStatus, bulkNotificationOptStatusInner.operationStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(notificationId, operationStatus);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class BulkNotificationOptStatusInner {\n");
+
+ sb.append(" notificationId: ").append(toIndentedString(notificationId)).append("\n");
+ sb.append(" operationStatus: ").append(toIndentedString(operationStatus)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/Count.java b/src/main/java/gov/nih/niehs/notification/model/Count.java
new file mode 100644
index 0000000..6137f46
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/Count.java
@@ -0,0 +1,52 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.Objects;
+
+import org.springframework.validation.annotation.Validated;
+
+import io.swagger.annotations.ApiModel;
+
+/**
+ * count of unseen notifications
+ */
+@ApiModel(description = "count of unseen notifications")
+@Validated
+@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2019-12-09T17:17:18.165Z[GMT]")
+public class Count {
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class Count {\n");
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/Notification.java b/src/main/java/gov/nih/niehs/notification/model/Notification.java
new file mode 100644
index 0000000..125b442
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/Notification.java
@@ -0,0 +1,362 @@
+package gov.nih.niehs.notification.model;
+
+import java.sql.Timestamp;
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.validation.Valid;
+
+import org.springframework.validation.annotation.Validated;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Notification
+ */
+@Validated
+@Entity
+public class Notification {
+ @JsonProperty("id")
+ @Id
+ private Integer id = null;
+
+ @JsonProperty("notification_id")
+ private UUID notificationId = null;
+
+ @JsonProperty("sender_id")
+ private String senderId = null;
+
+ @JsonProperty("recipient_id")
+ private String recipientId = null;
+
+ @JsonProperty("subject")
+ private String subject = null;
+
+ @JsonProperty("message")
+ private String message = null;
+
+ @JsonProperty("seen")
+ private Boolean seen = false;
+
+ @JsonProperty("deleted")
+ private Boolean deleted = false;
+
+ @JsonProperty("date_created")
+ private Timestamp dateCreated = null;
+
+ @JsonProperty("severity_level")
+ private Integer severityLevel = null;
+
+ @JsonProperty("notification_type")
+ private String notificationType = null;
+
+ @JsonProperty("data_location_url")
+ private String dataLocationUrl = null;
+
+ public Notification id(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * an auto-generated sequence by postgres db
+ *
+ * @return id
+ **/
+ @ApiModelProperty(value = "an auto-generated sequence by postgres db")
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Notification notificationId(UUID notificationId) {
+ this.notificationId = notificationId;
+ return this;
+ }
+
+ /**
+ * an unique uuid auto-generated by postgres db
+ *
+ * @return notificationId
+ **/
+ @ApiModelProperty(value = "an unique uuid auto-generated by postgres db")
+
+ @Valid
+ public UUID getNotificationId() {
+ return notificationId;
+ }
+
+ public void setNotificationId(UUID notificationId) {
+ this.notificationId = notificationId;
+ }
+
+ public Notification senderId(String senderId) {
+ this.senderId = senderId;
+ return this;
+ }
+
+ /**
+ * notification sender's userId
+ *
+ * @return senderId
+ **/
+ @ApiModelProperty(value = "notification sender's userId")
+
+ public String getSenderId() {
+ return senderId;
+ }
+
+ public void setSenderId(String senderId) {
+ this.senderId = senderId;
+ }
+
+ public Notification recipientId(String recipientId) {
+ this.recipientId = recipientId;
+ return this;
+ }
+
+ /**
+ * notification recipient's userId
+ *
+ * @return recipientId
+ **/
+ @ApiModelProperty(value = "notification recipient's userId")
+
+ public String getRecipientId() {
+ return recipientId;
+ }
+
+ public void setRecipientId(String recipientId) {
+ this.recipientId = recipientId;
+ }
+
+ public Notification subject(String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ /**
+ * notification subject
+ *
+ * @return subject
+ **/
+ @ApiModelProperty(value = "notification subject")
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public Notification message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * notification message content
+ *
+ * @return message
+ **/
+ @ApiModelProperty(value = "notification message content")
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Notification seen(Boolean seen) {
+ this.seen = seen;
+ return this;
+ }
+
+ /**
+ * boolean for user's seen/unseen notification status
+ *
+ * @return seen
+ **/
+ @ApiModelProperty(value = "boolean for user's seen/unseen notification status")
+
+ public Boolean isSeen() {
+ return seen;
+ }
+
+ public void setSeen(Boolean seen) {
+ this.seen = seen;
+ }
+
+ public Notification deleted(Boolean deleted) {
+ this.deleted = deleted;
+ return this;
+ }
+
+ /**
+ * boolean for deleted notification, notification will not be deleted from
+ * postgres db
+ *
+ * @return deleted
+ **/
+ @ApiModelProperty(value = "boolean for deleted notification, notification will not be deleted from postgres db")
+
+ public Boolean isDeleted() {
+ return deleted;
+ }
+
+ public void setDeleted(Boolean deleted) {
+ this.deleted = deleted;
+ }
+
+ public Notification dateCreated(Timestamp dateCreated) {
+ this.dateCreated = dateCreated;
+ return this;
+ }
+
+ /**
+ * timestamp auto-generated by postgres db
+ *
+ * @return dateCreated
+ **/
+ @ApiModelProperty(value = "timestamp auto-generated by postgres db")
+
+ @Valid
+ public Timestamp getDateCreated() {
+ return dateCreated;
+ }
+
+ public void setDateCreated(Timestamp dateCreated) {
+ this.dateCreated = dateCreated;
+ }
+
+ public Notification severityLevel(Integer severityLevel) {
+ this.severityLevel = severityLevel;
+ return this;
+ }
+
+ /**
+ * severity level of notification an integer value between 1 and 5
+ *
+ * @return severityLevel
+ **/
+ @ApiModelProperty(value = "severity level of notification an integer value between 1 and 5")
+
+ public Integer getSeverityLevel() {
+ return severityLevel;
+ }
+
+ public void setSeverityLevel(Integer severityLevel) {
+ this.severityLevel = severityLevel;
+ }
+
+ public Notification notificationType(String notificationType) {
+ this.notificationType = notificationType;
+ return this;
+ }
+
+ /**
+ * type of notification options are workflow, permission, system
+ *
+ * @return notificationType
+ **/
+ @ApiModelProperty(value = "type of notification options are workflow, permission, system")
+
+ public String getNotificationType() {
+ return notificationType;
+ }
+
+ public void setNotificationType(String notificationType) {
+ this.notificationType = notificationType;
+ }
+
+ public Notification dataLocationUrl(String dataLocationUrl) {
+ this.dataLocationUrl = dataLocationUrl;
+ return this;
+ }
+
+ /**
+ * notification associated logical location of data
+ *
+ * @return dataLocationUrl
+ **/
+ @ApiModelProperty(value = "notification associated logical location of data")
+
+ public String getDataLocationUrl() {
+ return dataLocationUrl;
+ }
+
+ public void setDataLocationUrl(String dataLocationUrl) {
+ this.dataLocationUrl = dataLocationUrl;
+ }
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Notification notification = (Notification) o;
+ return Objects.equals(this.id, notification.id)
+ && Objects.equals(this.notificationId, notification.notificationId)
+ && Objects.equals(this.senderId, notification.senderId)
+ && Objects.equals(this.recipientId, notification.recipientId)
+ && Objects.equals(this.subject, notification.subject)
+ && Objects.equals(this.message, notification.message) && Objects.equals(this.seen, notification.seen)
+ && Objects.equals(this.deleted, notification.deleted)
+ && Objects.equals(this.dateCreated, notification.dateCreated)
+ && Objects.equals(this.severityLevel, notification.severityLevel)
+ && Objects.equals(this.notificationType, notification.notificationType)
+ && Objects.equals(this.dataLocationUrl, notification.dataLocationUrl);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, notificationId, senderId, recipientId, subject, message, seen, deleted, dateCreated,
+ severityLevel, notificationType);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class Notification {\n");
+
+ sb.append(" id: ").append(toIndentedString(id)).append("\n");
+ sb.append(" notificationId: ").append(toIndentedString(notificationId)).append("\n");
+ sb.append(" senderId: ").append(toIndentedString(senderId)).append("\n");
+ sb.append(" recipientId: ").append(toIndentedString(recipientId)).append("\n");
+ sb.append(" subject: ").append(toIndentedString(subject)).append("\n");
+ sb.append(" message: ").append(toIndentedString(message)).append("\n");
+ sb.append(" seen: ").append(toIndentedString(seen)).append("\n");
+ sb.append(" deleted: ").append(toIndentedString(deleted)).append("\n");
+ sb.append(" dateCreated: ").append(toIndentedString(dateCreated)).append("\n");
+ sb.append(" severityLevel: ").append(toIndentedString(severityLevel)).append("\n");
+ sb.append(" notificationType: ").append(toIndentedString(notificationType)).append("\n");
+ sb.append(" dataLocationUrl: ").append(toIndentedString(dataLocationUrl)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/NotificationArray.java b/src/main/java/gov/nih/niehs/notification/model/NotificationArray.java
new file mode 100644
index 0000000..fc059f1
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/NotificationArray.java
@@ -0,0 +1,49 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * NotificationArray
+ */
+@Validated
+public class NotificationArray extends ArrayList {
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class NotificationArray {\n");
+ sb.append(" ").append(toIndentedString(super.toString())).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/model/UuidList.java b/src/main/java/gov/nih/niehs/notification/model/UuidList.java
new file mode 100644
index 0000000..70dd047
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/model/UuidList.java
@@ -0,0 +1,50 @@
+package gov.nih.niehs.notification.model;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * UuidList
+ */
+@Validated
+public class UuidList extends ArrayList {
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class UuidList {\n");
+ sb.append(" ").append(toIndentedString(super.toString())).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/security/JwtAuthEntryPoint.java b/src/main/java/gov/nih/niehs/notification/security/JwtAuthEntryPoint.java
new file mode 100644
index 0000000..b4bfa74
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/security/JwtAuthEntryPoint.java
@@ -0,0 +1,27 @@
+package gov.nih.niehs.notification.security;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
+
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+ throws IOException, ServletException {
+
+ logger.error("Unauthorized error. Message - {}", e.getMessage());
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/security/JwtAuthFilter.java b/src/main/java/gov/nih/niehs/notification/security/JwtAuthFilter.java
new file mode 100644
index 0000000..68af91b
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/security/JwtAuthFilter.java
@@ -0,0 +1,64 @@
+package gov.nih.niehs.notification.security;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+public class JwtAuthFilter extends OncePerRequestFilter {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class);
+
+ @Autowired
+ private JwtValidator jwtValidator;
+
+ @Autowired
+ private UserDetails userDetails;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ logger.info("doFilterInternal(): filtering jwts on each request");
+ try {
+
+ String jwt = getJwt(request);
+ if (jwt != null && jwtValidator.validateJwtToken(jwt)) {
+ String username = jwtValidator.getUserNameFromJwtToken(jwt);
+ logger.info("username found in JWT: {}", username);
+ userDetails.setSessionUserId(username);
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username,
+ null, null);
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ }
+ } catch (Exception e) {
+ logger.error("Can NOT set user authentication -> Message: {}", e);
+ }
+ logger.info("request: " + request);
+ logger.info("response: " + response);
+
+ filterChain.doFilter(request, response);
+ }
+
+ private String getJwt(HttpServletRequest request) {
+ String authHeader = request.getHeader("Authorization");
+
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ return authHeader.replace("Bearer ", "");
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/security/JwtValidator.java b/src/main/java/gov/nih/niehs/notification/security/JwtValidator.java
new file mode 100644
index 0000000..82d3aea
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/security/JwtValidator.java
@@ -0,0 +1,44 @@
+package gov.nih.niehs.notification.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.UnsupportedJwtException;
+
+@Component
+public class JwtValidator {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtValidator.class);
+
+ @Value("${app.jwtSecret}")
+ private String jwtSecret;
+
+ public boolean validateJwtToken(String authToken) {
+ logger.info("validateJwtToken()");
+ try {
+ Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
+ return true;
+ } catch (MalformedJwtException e) {
+ logger.error("Invalid JWT token -> Message: {}", e);
+ } catch (ExpiredJwtException e) {
+ logger.error("Expired JWT token -> Message: {}", e);
+ } catch (UnsupportedJwtException e) {
+ logger.error("Unsupported JWT token -> Message: {}", e);
+ } catch (IllegalArgumentException e) {
+ logger.error("JWT claims string is empty -> Message: {}", e);
+ }
+ logger.info("returning false");
+ return false;
+ }
+
+ public String getUserNameFromJwtToken(String token) {
+ logger.info("getUserNameFromJwtToken()");
+ return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
+ }
+
+}
diff --git a/src/main/java/gov/nih/niehs/notification/security/SecurityConfiguration.java b/src/main/java/gov/nih/niehs/notification/security/SecurityConfiguration.java
new file mode 100644
index 0000000..2382f8a
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/security/SecurityConfiguration.java
@@ -0,0 +1,50 @@
+package gov.nih.niehs.notification.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private JwtAuthEntryPoint unauthorizedHandler;
+
+ @Bean
+ public JwtAuthFilter authenticationJwtTokenFilter() {
+ return new JwtAuthFilter();
+ }
+
+ private static final String[] AUTH_WHITELIST = {
+ // -- swagger ui
+ "/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui", "/configuration/security",
+ "/swagger-ui.html", "/webjars/**"
+ // other public endpoints of your API may be appended to this array
+ };
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.cors().and().csrf().disable().authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll()
+ .antMatchers("/**").authenticated().and().exceptionHandling()
+ .authenticationEntryPoint(unauthorizedHandler).and().sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+
+ http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+ }
+
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
+ return source;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/security/UserDetails.java b/src/main/java/gov/nih/niehs/notification/security/UserDetails.java
new file mode 100644
index 0000000..d0bb6ec
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/security/UserDetails.java
@@ -0,0 +1,31 @@
+package gov.nih.niehs.notification.security;
+
+import org.springframework.stereotype.Service;
+
+import io.swagger.annotations.ApiModelProperty;
+
+@Service
+public class UserDetails {
+
+ private String sessionUserId = null;
+
+ public UserDetails sessionUserId(String sessionUserId) {
+ this.sessionUserId = sessionUserId;
+ return this;
+ }
+
+ /**
+ * Get senderID
+ *
+ * @return senderID
+ **/
+ @ApiModelProperty(value = "")
+
+ public String getSessionUserId() {
+ return sessionUserId;
+ }
+
+ public void setSessionUserId(String username) {
+ this.sessionUserId = username;
+ }
+}
diff --git a/src/main/java/gov/nih/niehs/notification/service/NotificationServiceImpl.java b/src/main/java/gov/nih/niehs/notification/service/NotificationServiceImpl.java
new file mode 100644
index 0000000..31d5266
--- /dev/null
+++ b/src/main/java/gov/nih/niehs/notification/service/NotificationServiceImpl.java
@@ -0,0 +1,94 @@
+package gov.nih.niehs.notification.service;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import gov.nih.niehs.notification.interfaces.NotificationRepository;
+import gov.nih.niehs.notification.interfaces.NotificationService;
+import gov.nih.niehs.notification.model.BulkNotificationOptStatus;
+import gov.nih.niehs.notification.model.BulkNotificationOptStatusInner;
+import gov.nih.niehs.notification.model.NotificationArray;
+import gov.nih.niehs.notification.model.UuidList;
+
+@Service
+public class NotificationServiceImpl implements NotificationService {
+ private static final Logger logger = LoggerFactory.getLogger(NotificationServiceImpl.class);
+
+ @Autowired
+ private NotificationRepository notificationRepository;
+
+ @Override
+ public Integer addNotification(String senderId, String recipientId, String subject, String message,
+ Integer severityLevel, String notificationType, String dataLocationUrl) {
+ logger.info("Initiating addNotification()");
+ logger.info("Querying database");
+ Integer affectedRows = notificationRepository.addNotification(senderId, recipientId, subject, message,
+ severityLevel, notificationType, dataLocationUrl);
+ logger.info("Number of rows affected: {}", affectedRows);
+ return affectedRows;
+ }
+
+ @Override
+ public NotificationArray getAllNotifications(String userId) {
+ logger.info("Initiating getAllNotifications()");
+ logger.info("Querying database");
+ NotificationArray notifications = notificationRepository.getAllNotification(userId);
+ return notifications;
+ }
+
+ @Override
+ public BulkNotificationOptStatus markSeen(String userId, UuidList uuids) {
+ logger.info("Initiating markSeen()");
+ BulkNotificationOptStatus updateStatus = new BulkNotificationOptStatus();
+ Boolean seenFlag = true;
+ logger.info("Querying database");
+ for (UUID uuid : uuids) {
+ logger.info("Marking notifications: " + uuid + "as seen: " + seenFlag);
+ Integer status = notificationRepository.markSeen(userId, uuid, seenFlag);
+ if (status == 1) {
+ updateStatus.add(new BulkNotificationOptStatusInner(uuid, true));
+ } else {
+ logger.error("Error updating notification with uuid: {}", uuid);
+ updateStatus.add(new BulkNotificationOptStatusInner(uuid, false));
+ }
+ }
+ return updateStatus;
+ }
+
+ @Override
+ public NotificationArray getUnseenNotifications(String userId) {
+ logger.info("Initiating getUnseenNotifications()");
+ NotificationArray notifications = notificationRepository.getUnseenNotifications(userId);
+ return notifications;
+ }
+
+ @Override
+ public Integer getUnseenNotificationsCount(String userId) {
+ logger.info("Initiating getUnseenNotificationsCount()");
+ Integer result = notificationRepository.getUnseenNotificationsCount(userId);
+ return result;
+ }
+
+ @Override
+ public BulkNotificationOptStatus deleteNotifications(String userId, UuidList uuids) {
+ logger.info("Initiating markSeen()");
+ BulkNotificationOptStatus updateStatus = new BulkNotificationOptStatus();
+ Boolean deletedFlag = true;
+ logger.info("Querying database");
+ for (UUID uuid : uuids) {
+ logger.info("Marking notifications: " + uuid + "as deleted: " + deletedFlag);
+ Integer status = notificationRepository.deleteNotifications(userId, uuid, deletedFlag);
+ if (status == 1) {
+ updateStatus.add(new BulkNotificationOptStatusInner(uuid, true));
+ } else {
+ logger.error("Error updating notification with uuid: {}", uuid);
+ updateStatus.add(new BulkNotificationOptStatusInner(uuid, false));
+ }
+ }
+ return updateStatus;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..feb4de3
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+springfox.documentation.swagger.v2.path=/api-docs
+server.contextPath=/
+server.port=8888
+spring.jackson.date-format=gov.nih.niehs.notification.RFC3339DateFormat
+spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
+
+spring.datasource.platform=postgres
+spring.datasource.url=jdbc:postgresql://localhost:5432/notification
+spring.datasource.username=irodsext
+spring.datasource.password=password
+
+app.jwtSecret=ThisIsAVeryLongSecret
diff --git a/src/main/resources/migrations/V1__Base_version.sql b/src/main/resources/migrations/V1__Base_version.sql
new file mode 100644
index 0000000..0829e4a
--- /dev/null
+++ b/src/main/resources/migrations/V1__Base_version.sql
@@ -0,0 +1,33 @@
+--
+-- Extentsion for auto generating UUID.
+--
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+--
+-- ID sequence for the notifications table.
+--
+CREATE SEQUENCE notifications_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MAXVALUE
+ NO MINVALUE
+ CACHE 1;
+
+--
+-- Stores notification information.
+--
+CREATE TABLE notification (
+ id BIGINT DEFAULT nextval('notifications_id_seq'::regclass) NOT NULL,
+ notification_id uuid NOT NULL DEFAULT uuid_generate_v1(),
+ sender_id VARCHAR(512) NOT NULL,
+ recipient_id VARCHAR(512) NOT NULL,
+ subject TEXT NOT NULL,
+ message TEXT NOT NULL,
+ seen BOOLEAN DEFAULT FALSE NOT NULL,
+ deleted BOOLEAN DEFAULT FALSE NOT NULL,
+ date_created TIMESTAMP DEFAULT now() NOT NULL,
+ severity_level INTEGER NOT NULL CHECK (severity_level > 0 AND severity_level < 6),
+ notification_type VARCHAR(50) NOT NULL,
+ data_location_url TEXT,
+ PRIMARY KEY(id)
+);
\ No newline at end of file
diff --git a/swagger/notification-service.yaml b/swagger/notification-service.yaml
new file mode 100644
index 0000000..fb95b6d
--- /dev/null
+++ b/swagger/notification-service.yaml
@@ -0,0 +1,267 @@
+openapi: 3.0.2
+info:
+ version: '1.0.0'
+ title: 'Notification Service API'
+ description: 'This is a notification microservice for Metalnx'
+ contact:
+ email: deep.patel@nih.gov
+ license:
+ name: Apache 2.0
+ url: http://www.apache.org/licenses/LICENSE-2.0.html
+servers:
+- url: http://localhost:8888
+ description: Local dev server
+
+# Apply the security globally to all operations
+security:
+ - BearerAuth:
+ - read
+
+tags:
+- name: "Notification"
+ description: "Notification Operations"
+
+paths:
+ /addNotification:
+ post:
+ tags:
+ - Notification
+ summary: Add new notification
+ description: Query database to insert new notification
+ operationId: addNotification
+ requestBody:
+ description: Schema to add new notification
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/addNotification'
+ example:
+ recipient_id: user_1
+ subject: notification subject
+ message: notification message
+ severity_level: 2
+ notification_type: workflow
+ responses:
+ 201:
+ description: notification added successfully
+ 400:
+ description: bad request
+
+ /getNotification:
+ get:
+ tags:
+ - Notification
+ summary: Reterieve notifications
+ description: Query database to get all the notifications for session user
+ operationId: getNotifications
+ responses:
+ 200:
+ description: List of all notifications
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/notificationArray'
+
+ /markSeen:
+ put:
+ tags:
+ - Notification
+ summary: Update notification as seen
+ description: Query database to mark of notifications as seen once user is validated of ownership
+ operationId: markSeen
+ requestBody:
+ description: List of uuids one for each notification
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/uuidList'
+ responses:
+ 200:
+ description: notifications marked seen successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/bulkNotificationOptStatus'
+ 409:
+ description: conflict in update
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/bulkNotificationOptStatus'
+
+ /getUnseenNotifications:
+ get:
+ tags:
+ - Notification
+ summary: Reterieve all unseen notifications
+ description: Query database to get all unseen notifications for session user
+ operationId: getUnseenNotifications
+ responses:
+ 200:
+ description: List of all notifications
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/notificationArray'
+
+ /getUnseenNotificationsCount:
+ get:
+ tags:
+ - Notification
+ summary: Reterieve all unseen notifications
+ description: Query database to get all unseen notifications for session user
+ operationId: getUnseenNotificationsCount
+ responses:
+ 200:
+ description: count reterieved sucessfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/count'
+
+ /deleteNotifications:
+ put:
+ tags:
+ - Notification
+ summary: Delete notifications
+ description: Query database to delete notifications once user is validated of ownership
+ operationId: deleteNotifications
+ requestBody:
+ description: List of uuids one for each notification
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/uuidList'
+ responses:
+ 200:
+ description: notifications marked seen successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/bulkNotificationOptStatus'
+ 409:
+ description: conflict in update
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/bulkNotificationOptStatus'
+
+
+components:
+ schemas:
+ addNotification:
+ type: object
+ required:
+ - recipient_id
+ - subject
+ - message
+ - severity_level
+ - notification_type
+ properties:
+ recipient_id:
+ type: string
+ description: notification recipient's userId
+ subject:
+ type: string
+ description: notification subject
+ message:
+ type: string
+ description: notification message content
+ severity_level:
+ type: integer
+ format: int32
+ minimum: 1
+ maximum: 5
+ description: severity level of notification an integer value between 1 and 5
+ notification_type:
+ type: string
+ description: type of notification options are workflow, permission, system
+ data_location_url:
+ type: string
+ format: uri
+ description: notification associated logical location of data
+
+ notification:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int32
+ description: an auto-generated sequence by postgres db
+ notification_id:
+ type: string
+ format: uuid
+ description: an unique uuid auto-generated by postgres db
+ sender_id:
+ type: string
+ description: notification sender's userId
+ recipient_id:
+ type: string
+ description: notification recipient's userId
+ subject:
+ type: string
+ description: notification subject
+ message:
+ type: string
+ description: notification message content
+ seen:
+ type: boolean
+ default: false
+ description: boolean for user's seen/unseen notification status
+ deleted:
+ type: boolean
+ default: false
+ description: boolean for deleted notification, notification will not be deleted from postgres db
+ date_created:
+ type: string
+ format: date-time
+ description: timestamp auto-generated by postgres db
+ severity_level:
+ type: integer
+ format: int32
+ description: severity level of notification an integer value between 1 and 5
+ notification_type:
+ type: string
+ description: type of notification options are workflow, permission, system
+ data_location_url:
+ type: string
+ format: uri
+ description: notification associated logical location of data
+
+ notificationArray:
+ type: array
+ items:
+ $ref: '#/components/schemas/notification'
+
+ uuidList:
+ type: array
+ items:
+ type: string
+ format: uuid
+ description: list of unique uuid one for each notification
+
+ bulkNotificationOptStatus:
+ type: array
+ items:
+ type: object
+ required:
+ - notification_id
+ - operation_status
+ properties:
+ notification_id:
+ type: string
+ format: uuid
+ description: an unique uuid auto-generated by postgres db
+ operation_status:
+ type: boolean
+
+ count:
+ type: integer
+ format: int32
+ description: count of unseen notifications
+
+ securitySchemes:
+ BearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
\ No newline at end of file