├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ └── maven-wrapper.properties
├── .travis.yml
├── .travis_after_success.sh
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── build.sh
├── config.yml
├── maven_deploy_settings.xml
├── mvnw
├── mvnw.cmd
├── notification-api
├── LICENSE
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── smoketurner
│ │ └── notification
│ │ └── api
│ │ ├── Notification.java
│ │ └── Rule.java
│ └── test
│ ├── java
│ └── com
│ │ └── smoketurner
│ │ └── notification
│ │ └── api
│ │ ├── NotificationTest.java
│ │ └── RuleTest.java
│ └── resources
│ └── fixtures
│ ├── notification.json
│ ├── rule.json
│ ├── rule_invalid_duration.json
│ ├── rule_invalid_matchon.json
│ └── rule_invalid_timeunit.json
├── notification-application
├── LICENSE
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── smoketurner
│ │ │ └── notification
│ │ │ └── application
│ │ │ ├── NotificationApplication.java
│ │ │ ├── config
│ │ │ └── NotificationConfiguration.java
│ │ │ ├── core
│ │ │ ├── IdGenerator.java
│ │ │ ├── Matcher.java
│ │ │ ├── RangeHeader.java
│ │ │ ├── Rollup.java
│ │ │ ├── StringSetParam.java
│ │ │ ├── UserNotifications.java
│ │ │ └── WebSecurityFilter.java
│ │ │ ├── exceptions
│ │ │ ├── NotificationException.java
│ │ │ ├── NotificationExceptionMapper.java
│ │ │ └── NotificationStoreException.java
│ │ │ ├── graphql
│ │ │ ├── CreateNotificationMutation.java
│ │ │ ├── CreateRuleMutation.java
│ │ │ ├── NotificationDataFetcher.java
│ │ │ ├── RemoveAllNotificationsMutation.java
│ │ │ ├── RemoveAllRulesMutation.java
│ │ │ ├── RemoveNotificationMutation.java
│ │ │ ├── RemoveRuleMutation.java
│ │ │ ├── RuleDataFetcher.java
│ │ │ ├── Scalars.java
│ │ │ └── UsernameFieldValidation.java
│ │ │ ├── managed
│ │ │ ├── CursorStoreManager.java
│ │ │ └── NotificationStoreManager.java
│ │ │ ├── resources
│ │ │ ├── NotificationResource.java
│ │ │ ├── PingResource.java
│ │ │ ├── RuleResource.java
│ │ │ └── VersionResource.java
│ │ │ ├── riak
│ │ │ ├── CursorObject.java
│ │ │ ├── CursorResolver.java
│ │ │ ├── CursorUpdate.java
│ │ │ ├── NotificationListAddition.java
│ │ │ ├── NotificationListConverter.java
│ │ │ ├── NotificationListDeletion.java
│ │ │ ├── NotificationListObject.java
│ │ │ └── NotificationListResolver.java
│ │ │ └── store
│ │ │ ├── CursorStore.java
│ │ │ ├── NotificationStore.java
│ │ │ └── RuleStore.java
│ ├── proto
│ │ └── notification.proto
│ └── resources
│ │ ├── Notification.graphql
│ │ └── banner.txt
│ └── test
│ ├── java
│ └── com
│ │ └── smoketurner
│ │ └── notification
│ │ └── application
│ │ ├── NotificationApplicationTest.java
│ │ ├── benchmarks
│ │ ├── NotificationStoreBenchmark.java
│ │ └── RollupBenchmark.java
│ │ ├── core
│ │ ├── MatcherTest.java
│ │ ├── RangeHeaderTest.java
│ │ ├── RollupTest.java
│ │ ├── StringSetParamTest.java
│ │ └── UserNotificationsTest.java
│ │ ├── graphql
│ │ ├── CreateNotificationMutationTest.java
│ │ ├── CreateRuleMutationTest.java
│ │ ├── NotificationDataFetcherTest.java
│ │ ├── RemoveAllNotificationsMutationTest.java
│ │ ├── RemoveAllRulesMutationTest.java
│ │ ├── RemoveNotificationMutationTest.java
│ │ ├── RemoveRuleMutationTest.java
│ │ ├── RuleDataFetcherTest.java
│ │ └── UsernameFieldValidationTest.java
│ │ ├── integration
│ │ └── NotificationsIT.java
│ │ ├── managed
│ │ ├── CursorStoreManagerTest.java
│ │ └── NotificationStoreManagerTest.java
│ │ ├── resources
│ │ ├── NotificationResourceTest.java
│ │ ├── PingResourceTest.java
│ │ ├── RuleResourceTest.java
│ │ └── VersionResourceTest.java
│ │ ├── riak
│ │ ├── CursorObjectTest.java
│ │ ├── CursorResolverTest.java
│ │ ├── CursorUpdateTest.java
│ │ ├── NotificationListAdditionTest.java
│ │ ├── NotificationListConverterTest.java
│ │ ├── NotificationListDeletionTest.java
│ │ ├── NotificationListObjectTest.java
│ │ └── NotificationListResolverTest.java
│ │ └── store
│ │ ├── CursorStoreTest.java
│ │ └── NotificationStoreTest.java
│ └── resources
│ ├── Notification.graphql
│ ├── fixtures
│ └── cursor.json
│ ├── logback-test.xml
│ └── notification-test.yml
├── notification-client
├── LICENSE
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── smoketurner
│ │ └── notification
│ │ └── client
│ │ ├── NotificationClient.java
│ │ ├── NotificationClientBuilder.java
│ │ └── NotificationClientConfiguration.java
│ └── test
│ ├── java
│ └── com
│ │ └── smoketurner
│ │ └── notification
│ │ └── client
│ │ ├── NotificationClientTest.java
│ │ └── integration
│ │ └── NotificationGenerator.java
│ └── resources
│ └── logback-test.xml
├── pom.xml
├── riak_schemas
└── maps.dt
├── run.sh
├── spotbugs.xml
└── start_riak.sh
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .idea
3 | .gitattributes
4 | .gitignore
5 | .java-version
6 | .travis.yml
7 | .travis_after_success.sh
8 | .checkstyle.xml
9 | .project
10 | .classpath
11 | .settings
12 | target
13 | *.class
14 | CONTRIBUTING.md
15 | maven_deploy_settings.xml
16 | Procfile
17 | setup.sh
18 | system.properties
19 | Dockerfile
20 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Configuration file for EditorConfig: http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 4
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.properties]
13 | charset = latin1
14 |
15 | [travis.yml]
16 | indent_size = 2
17 | indent_style = space
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text auto
2 |
3 | *.java text eol=lf
4 | *.yml text eol=lf
5 | *.xml text eol=lf
6 | *.cmd text eol=lf
7 | *.md text eol=lf
8 | *.sh text eol=lf
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | .settings
4 | .idea
5 | *.iml
6 | .DS_Store
7 | *.class
8 | *.jar
9 | hs_err_pid*
10 | target
11 | dependency-reduced-pom.xml
12 | .apt_generated_tests
13 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | import java.net.*;
21 | import java.io.*;
22 | import java.nio.channels.*;
23 | import java.util.Properties;
24 |
25 | public class MavenWrapperDownloader {
26 |
27 | /**
28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
29 | */
30 | private static final String DEFAULT_DOWNLOAD_URL =
31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
32 |
33 | /**
34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
35 | * use instead of the default one.
36 | */
37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
38 | ".mvn/wrapper/maven-wrapper.properties";
39 |
40 | /**
41 | * Path where the maven-wrapper.jar will be saved to.
42 | */
43 | private static final String MAVEN_WRAPPER_JAR_PATH =
44 | ".mvn/wrapper/maven-wrapper.jar";
45 |
46 | /**
47 | * Name of the property which should be used to override the default download url for the wrapper.
48 | */
49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
50 |
51 | public static void main(String args[]) {
52 | System.out.println("- Downloader started");
53 | File baseDirectory = new File(args[0]);
54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
55 |
56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
57 | // wrapperUrl parameter.
58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
59 | String url = DEFAULT_DOWNLOAD_URL;
60 | if(mavenWrapperPropertyFile.exists()) {
61 | FileInputStream mavenWrapperPropertyFileInputStream = null;
62 | try {
63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
64 | Properties mavenWrapperProperties = new Properties();
65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
67 | } catch (IOException e) {
68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
69 | } finally {
70 | try {
71 | if(mavenWrapperPropertyFileInputStream != null) {
72 | mavenWrapperPropertyFileInputStream.close();
73 | }
74 | } catch (IOException e) {
75 | // Ignore ...
76 | }
77 | }
78 | }
79 | System.out.println("- Downloading from: : " + url);
80 |
81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
82 | if(!outputFile.getParentFile().exists()) {
83 | if(!outputFile.getParentFile().mkdirs()) {
84 | System.out.println(
85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
86 | }
87 | }
88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
89 | try {
90 | downloadFileFromURL(url, outputFile);
91 | System.out.println("Done");
92 | System.exit(0);
93 | } catch (Throwable e) {
94 | System.out.println("- Error downloading");
95 | e.printStackTrace();
96 | System.exit(1);
97 | }
98 | }
99 |
100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
101 | URL website = new URL(urlString);
102 | ReadableByteChannel rbc;
103 | rbc = Channels.newChannel(website.openStream());
104 | FileOutputStream fos = new FileOutputStream(destination);
105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
106 | fos.close();
107 | rbc.close();
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | os:
3 | - linux
4 | jdk:
5 | - openjdk11
6 | before_install:
7 | - rm ~/.m2/settings.xml || true
8 | - ulimit -c unlimited -S
9 | - mvn -N io.takari:maven:wrapper
10 | - sudo riak-admin bucket-type create maps '{"props":{"datatype":"map"}}'
11 | - sudo riak-admin bucket-type activate maps
12 | after_success:
13 | - bash .travis_after_success.sh
14 | cache:
15 | directories:
16 | - $HOME/.m2
17 |
18 | services:
19 | - riak
20 |
21 | notifications:
22 | email: false
23 |
24 | env:
25 | global:
26 | # CI_DEPLOY_USERNAME
27 | - secure: "S8HDSF/GUdzEt9ZVxr0zVYJSNt2uMXbE3xV8vCV8j40ri9/0z0ZJfvcZx7p6k7sscxhpRsX9KPzYvQwM9ex2O/Aw190XATDiXHbJ0jzCii0agzTSlolJ/HiGCkYzSt7rPbjzB2NKmww2qMeMvzyEoWRFV9P5171Jsm99w+qmVhpwWBogPr9FD5ayRZF78BDoU96ChbLbJxOzjg1N4IaJ6KmAbGxsQ9MLyhV+iJjRIWe9DgR8dd6ja+J4RHF+HfqYg8NJMfg7JGpTO1ULbZHoqaO5e++FkIrn/Srp/qRwfEZxWs4TxTVTMxfKQ6ss8mduOVp1nahu6OC+47pJG3MWZsdU1CDCdjbQ/CrLcVduQtS2+TZoiwdwgyYJFiSim1HWivrNGuhk4ySvonmXVZzuWXMUqAHmbq2nq4/a0HrwfF+mth5B/wgOIuYMwtwP7ibUsunc9+Rx/c+gL3X/Fr49oHvIOySkbQ6FKK23H0MF+lNEUfuqpRREOkbIyhO0sEItCIsoaeDdOw9JqicTiYodE+OkLT3c2oYN+PNtUVV8ZSxdAI5bolArwoVPMxNhSBLPqJIAHfjmAAzGbmttNGrbuARsG3xbFaQcDMDdAfxKsQLMiY2EHajMdQ6ShswMbgGWvIbsksPzYIUJw4FBWuQZcXWt/MIF86ACthPBpBxEiJc="
28 | # CI_DEPLOY_PASSWORD
29 | - secure: "WcOQLsXDzayW9bbU3mlte9vD7+gd5UmSELR8Sus+HUAdl7DphhQHS0obWNPcJJNcRCDvjoDRUFVaP+KVvAAPwIrBu4HMjhIuAJkiLu3QF382rHTSwKvZvzUA99XJPautoqmdzZofjsdMwOoQZZ/Q32kjc3qqR3MYzs9PtzWt0/oM6YNW590r+cks9MXIZ1K5UPaEIRGO6Q+IMDRzgsq5UvNtLWFAUqlU+xY5xCgubOnkGIUZfMrm1M+Jl4FJg7rT2DOuBVvMjPPlzjjAQXH3HZG/i9ET+2A/zDtxepl2QqJhc/ZhBLLywSdAhQhYIhaa0zpscv7B5e/iqbjR+1TVUimxqWjvasUVqi969iWVyTnw30LeJwuOH/DmJQ/DPguENTWMFBrl4YNSaty4L3heLsF87kJ+4mHOmuAbGEyyCSwLmAFBWRsaLH0gb5f/ng7TpElZ9FrOn+njT1Sg4t5+J7ftTPp9CujET7F6UzfD0aJU10P3qsuU1E7r9YJoaAGWdbJ4N4fKHRkuwd3EJyc0KCKaVHVHZ890tyvzrveDXHon0Mh7/Yv/lBWJ0hdvFtHTOmF1zOUjkKPdJFGTB8pKSthY9+Qlruqym9CDMc0icFB4ciwMJQaP4d4QLrHWBiRlGA2qG4mJKnAcJMyYZN2VF6V8YOiCMk9hJ06d03Aq0q8="
30 | - JAVA_OPTS: "'-Xms512m -Xmx2g'"
31 |
--------------------------------------------------------------------------------
/.travis_after_success.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ "${TRAVIS_JDK_VERSION}" != "openjdk11" ]]; then
4 | echo "Skipping after_success actions for JDK version \"${TRAVIS_JDK_VERSION}\""
5 | exit
6 | fi
7 |
8 | ./mvnw -B cobertura:cobertura coveralls:report
9 |
10 | if [[ -n ${TRAVIS_TAG} ]]; then
11 | echo "Skipping deployment for tag \"${TRAVIS_TAG}\""
12 | exit
13 | fi
14 |
15 | if [[ ${TRAVIS_BRANCH} != 'master' ]]; then
16 | echo "Skipping deployment for branch \"${TRAVIS_BRANCH}\""
17 | exit
18 | fi
19 |
20 | if [[ "$TRAVIS_PULL_REQUEST" = "true" ]]; then
21 | echo "Skipping deployment for pull request"
22 | exit
23 | fi
24 |
25 | ./mvnw -B deploy --settings maven_deploy_settings.xml -Dmaven.test.skip=true -Dfindbugs.skip=true
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Guidelines for contributing to Notification.
2 |
3 | Please create a pull request against `master`.
4 |
5 | Try to make the code in the pull request as focused and clean as possible. See
6 | our [style guide](http://google.github.io/styleguide/javaguide.html). If the pull
7 | request is too large, we may ask you to split it into smaller ones.
8 |
9 | # License
10 | By contributing your code, you agree to license your contribution under the
11 | terms of the Apache Public License v2:
12 | https://github.com/smoketurner/notification/blob/master/LICENSE
13 |
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | FROM openjdk:11-jdk-slim AS BUILD_IMAGE
18 |
19 | WORKDIR /app
20 |
21 | RUN mkdir -p notification-api notification-application notification-client
22 |
23 | COPY pom.xml mvnw ./
24 | COPY .mvn ./.mvn/
25 | COPY notification-api/pom.xml ./notification-api/
26 | COPY notification-application/pom.xml ./notification-application/
27 | COPY notification-client/pom.xml ./notification-client/
28 |
29 | RUN ./mvnw install
30 |
31 | COPY . .
32 |
33 | RUN ./mvnw clean package -DskipTests=true -Dmaven.javadoc.skip=true -Dmaven.source.skip=true && \
34 | rm notification-application/target/original-*.jar && \
35 | mv notification-application/target/*.jar app.jar
36 |
37 | FROM openjdk:11-jre-slim
38 |
39 | ARG VERSION="1.3.1-SNAPSHOT"
40 |
41 | LABEL name="notification" version=$VERSION
42 |
43 | ENV PORT 8080
44 |
45 | RUN apk add --no-cache curl
46 |
47 | WORKDIR /app
48 | COPY --from=BUILD_IMAGE /app/app.jar .
49 | COPY --from=BUILD_IMAGE /app/config.yml .
50 |
51 | HEALTHCHECK --interval=10s --timeout=5s CMD curl --fail http://127.0.0.1:8080/admin/healthcheck || exit 1
52 |
53 | EXPOSE 8080
54 |
55 | ENTRYPOINT ["java", "-d64", "-server", "-jar", "app.jar"]
56 | CMD ["server", "config.yml"]
57 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | VERSION=`xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml`
4 |
5 | docker build --build-arg VERSION=${VERSION} -t "smoketurner/notification:${VERSION}" .
6 |
--------------------------------------------------------------------------------
/config.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | # GraphQL-specific options.
18 | graphql:
19 |
20 | enableTracing: false
21 | queryCache: maximumSize=10000
22 | schemaFiles:
23 | - Notification.graphql
24 |
25 | # Riak-specific options.
26 | riak:
27 |
28 | nodes:
29 | #- 127.0.0.1:8087
30 | - 10.0.57.33:8087
31 | - 10.0.57.34:8087
32 | - 10.0.57.35:8087
33 | - 10.0.57.36:8087
34 | - 10.0.57.37:8087
35 |
36 | # HTTP-specific options.
37 | server:
38 |
39 | type: simple
40 | rootPath: /api/
41 | applicationContextPath: /
42 | connector:
43 | type: http
44 | port: ${PORT:-8080}
45 |
46 | requestLog:
47 | appenders:
48 | - type: console
49 | timeZone: UTC
50 | target: stdout
51 |
52 | logging:
53 | level: INFO
54 | loggers:
55 | com.smoketurner.notification: DEBUG
56 | com.basho.riak: INFO
57 | appenders:
58 | - type: console
59 | timeZone: UTC
60 | target: stdout
61 |
--------------------------------------------------------------------------------
/maven_deploy_settings.xml:
--------------------------------------------------------------------------------
1 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | sonatype-nexus-snapshots
30 | ${env.CI_DEPLOY_USERNAME}
31 | ${env.CI_DEPLOY_PASSWORD}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/notification-api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 | 4.0.0
21 |
22 |
23 | com.smoketurner.notification
24 | notification-parent
25 | 2.0.1-SNAPSHOT
26 |
27 |
28 | notification-api
29 | Notification API
30 |
31 |
32 |
33 | io.dropwizard
34 | dropwizard-jackson
35 |
36 |
37 | io.dropwizard
38 | dropwizard-validation
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/notification-api/src/main/java/com/smoketurner/notification/api/Rule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.api;
17 |
18 | import com.fasterxml.jackson.annotation.JsonCreator;
19 | import com.fasterxml.jackson.annotation.JsonIgnore;
20 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
21 | import com.fasterxml.jackson.annotation.JsonInclude;
22 | import com.fasterxml.jackson.annotation.JsonProperty;
23 | import io.dropwizard.jackson.JsonSnakeCase;
24 | import io.dropwizard.util.Duration;
25 | import java.util.Objects;
26 | import java.util.Optional;
27 | import java.util.StringJoiner;
28 | import javax.annotation.Nullable;
29 |
30 | @JsonSnakeCase
31 | @JsonIgnoreProperties(ignoreUnknown = true)
32 | @JsonInclude(JsonInclude.Include.NON_EMPTY)
33 | public final class Rule {
34 |
35 | public static final String MAX_SIZE = "max_size";
36 | public static final String MAX_DURATION = "max_duration";
37 | public static final String MATCH_ON = "match_on";
38 |
39 | private final Optional maxSize;
40 | private final Optional maxDuration;
41 | private final Optional matchOn;
42 |
43 | /**
44 | * Constructor
45 | *
46 | * @param maxSize Maximum number of notifications to include in a roll-up
47 | * @param maxDuration Maximum time duration between the first and last notifications in a roll-up
48 | * @param matchOn Group notifications by a specific category
49 | */
50 | @JsonCreator
51 | private Rule(
52 | @JsonProperty(MAX_SIZE) @Nullable final Integer maxSize,
53 | @JsonProperty(MAX_DURATION) @Nullable final Duration maxDuration,
54 | @JsonProperty(MATCH_ON) @Nullable final String matchOn) {
55 | this.maxSize = Optional.ofNullable(maxSize);
56 | this.maxDuration = Optional.ofNullable(maxDuration);
57 | if (matchOn != null && !matchOn.isEmpty()) {
58 | this.matchOn = Optional.of(matchOn);
59 | } else {
60 | this.matchOn = Optional.empty();
61 | }
62 | }
63 |
64 | public static Builder builder() {
65 | return new Builder();
66 | }
67 |
68 | public static class Builder {
69 | @Nullable private Integer maxSize;
70 |
71 | @Nullable private Duration maxDuration;
72 |
73 | @Nullable private String matchOn;
74 |
75 | public Builder withMaxSize(@Nullable final Integer maxSize) {
76 | this.maxSize = maxSize;
77 | return this;
78 | }
79 |
80 | public Builder withMaxDuration(@Nullable final Duration maxDuration) {
81 | this.maxDuration = maxDuration;
82 | return this;
83 | }
84 |
85 | public Builder withMatchOn(@Nullable final String matchOn) {
86 | if (matchOn != null && matchOn.isEmpty()) {
87 | this.matchOn = null;
88 | } else {
89 | this.matchOn = matchOn;
90 | }
91 | return this;
92 | }
93 |
94 | public Rule build() {
95 | return new Rule(maxSize, maxDuration, matchOn);
96 | }
97 | }
98 |
99 | @JsonProperty(MAX_SIZE)
100 | public Optional getMaxSize() {
101 | return maxSize;
102 | }
103 |
104 | @JsonProperty(MAX_DURATION)
105 | public Optional getMaxDuration() {
106 | return maxDuration;
107 | }
108 |
109 | @JsonProperty(MATCH_ON)
110 | public Optional getMatchOn() {
111 | return matchOn;
112 | }
113 |
114 | @JsonIgnore
115 | public boolean isValid() {
116 | return maxSize.isPresent() || maxDuration.isPresent() || matchOn.isPresent();
117 | }
118 |
119 | @Override
120 | public boolean equals(final Object obj) {
121 | if (this == obj) {
122 | return true;
123 | }
124 | if ((obj == null) || (getClass() != obj.getClass())) {
125 | return false;
126 | }
127 |
128 | final Rule other = (Rule) obj;
129 | return Objects.equals(maxSize, other.maxSize)
130 | && Objects.equals(maxDuration, other.maxDuration)
131 | && Objects.equals(matchOn, other.matchOn);
132 | }
133 |
134 | @Override
135 | public int hashCode() {
136 | return Objects.hash(maxSize, maxDuration, matchOn);
137 | }
138 |
139 | @Override
140 | public String toString() {
141 | return new StringJoiner(", ", Rule.class.getSimpleName() + "{", "}")
142 | .add("maxSize=" + maxSize)
143 | .add("maxDuration=" + maxDuration)
144 | .add("matchOn=" + matchOn)
145 | .toString();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/notification-api/src/test/java/com/smoketurner/notification/api/NotificationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.api;
17 |
18 | import static io.dropwizard.testing.FixtureHelpers.fixture;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | import com.fasterxml.jackson.databind.ObjectMapper;
22 | import com.google.common.collect.ImmutableList;
23 | import com.google.common.collect.ImmutableMap;
24 | import com.google.common.collect.Sets;
25 | import io.dropwizard.jackson.Jackson;
26 | import java.time.ZonedDateTime;
27 | import java.util.TreeSet;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 |
31 | public class NotificationTest {
32 | private final ObjectMapper MAPPER = Jackson.newObjectMapper();
33 | private Notification notification;
34 |
35 | @Before
36 | public void setUp() throws Exception {
37 | final Notification notification2 =
38 | Notification.builder("new-follower", "you have a new follower")
39 | .withId("12346")
40 | .withCreatedAt(ZonedDateTime.parse("2015-06-29T21:04:12Z"))
41 | .withUnseen(true)
42 | .withProperties(ImmutableMap.of("first_name", "Test 2", "last_name", "User 2"))
43 | .build();
44 |
45 | notification =
46 | Notification.builder("new-follower", "you have a new follower")
47 | .withId("12345")
48 | .withCreatedAt(ZonedDateTime.parse("2015-06-29T21:04:12Z"))
49 | .withUnseen(true)
50 | .withProperties(ImmutableMap.of("first_name", "Test", "last_name", "User"))
51 | .withNotifications(ImmutableList.of(notification2))
52 | .build();
53 | }
54 |
55 | @Test
56 | public void serializesToJSON() throws Exception {
57 | final String actual = MAPPER.writeValueAsString(notification);
58 | final String expected =
59 | MAPPER.writeValueAsString(
60 | MAPPER.readValue(fixture("fixtures/notification.json"), Notification.class));
61 | assertThat(actual).isEqualTo(expected);
62 | }
63 |
64 | @Test
65 | public void deserializesFromJSON() throws Exception {
66 | final Notification actual =
67 | MAPPER.readValue(fixture("fixtures/notification.json"), Notification.class);
68 | assertThat(actual).isEqualTo(notification);
69 | }
70 |
71 | @Test
72 | public void testGetId() throws Exception {
73 | final Notification n1 = Notification.builder().build();
74 | assertThat(n1.getId("5")).isEqualTo("5");
75 | final Notification n2 = Notification.create("1000");
76 | assertThat(n2.getId("6")).isEqualTo("1000");
77 | }
78 |
79 | @Test
80 | public void testToString() throws Exception {
81 | final ZonedDateTime now = ZonedDateTime.parse("2015-08-14T21:25:19.533Z");
82 | final Notification n1 =
83 | Notification.builder("test-category", "").withId("1").withCreatedAt(now).build();
84 | assertThat(n1.toString())
85 | .isEqualTo(
86 | "Notification{id=Optional[1],"
87 | + " category=test-category, message=,"
88 | + " createdAt=2015-08-14T21:25:19.533Z, unseen=Optional.empty,"
89 | + " properties={}, notifications=[]}");
90 | }
91 |
92 | @Test
93 | public void testBuilder() throws Exception {
94 | final Notification n1 = Notification.create("1");
95 | final Notification n2 = Notification.builder(n1).build();
96 | assertThat(n1).isEqualTo(n2);
97 | }
98 |
99 | @Test
100 | public void testComparison() throws Exception {
101 | final Notification n1 = Notification.create("1");
102 | final Notification n2 = Notification.create("2");
103 | final Notification n3 = Notification.create("3");
104 | final Notification n4 = Notification.create("");
105 |
106 | assertThat(n1.compareTo(n2)).isEqualTo(1);
107 | assertThat(n2.compareTo(n1)).isEqualTo(-1);
108 |
109 | final TreeSet notifications = Sets.newTreeSet();
110 | notifications.add(n1);
111 | notifications.add(n2);
112 | notifications.add(n3);
113 | notifications.add(n4);
114 |
115 | assertThat(notifications).containsExactly(n3, n2, n1, n4);
116 |
117 | final Notification n1b = Notification.builder().withId("1").withUnseen(true).build();
118 |
119 | assertThat(n1.compareTo(n1b) == 0).isEqualTo(n1.equals(n1b));
120 | assertThat(n1.equals(null)).isFalse();
121 | }
122 |
123 | @Test
124 | public void testNaturalOrdering() {
125 | final Notification n1 = Notification.builder("test").withId("1").build();
126 | final Notification n2 = Notification.builder("test").withId("2").build();
127 | final Notification n3 = Notification.builder("test").withId("1").build();
128 | assertThat(n1.equals(n2)).isEqualTo(n1.compareTo(n2) == 0);
129 | assertThat(n2.equals(n3)).isEqualTo(n2.compareTo(n3) == 0);
130 | assertThat(n1.equals(n3)).isEqualTo(n1.compareTo(n3) == 0);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/notification-api/src/test/java/com/smoketurner/notification/api/RuleTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.api;
17 |
18 | import static io.dropwizard.testing.FixtureHelpers.fixture;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | import com.fasterxml.jackson.databind.JsonMappingException;
22 | import com.fasterxml.jackson.databind.ObjectMapper;
23 | import io.dropwizard.jackson.Jackson;
24 | import io.dropwizard.util.Duration;
25 | import org.junit.Test;
26 |
27 | public class RuleTest {
28 | private final ObjectMapper MAPPER = Jackson.newObjectMapper();
29 | private final Rule rule =
30 | Rule.builder()
31 | .withMatchOn("first_name")
32 | .withMaxSize(3)
33 | .withMaxDuration(Duration.minutes(10))
34 | .build();
35 |
36 | @Test
37 | public void serializesToJSON() throws Exception {
38 | final String actual = MAPPER.writeValueAsString(rule);
39 | final String expected =
40 | MAPPER.writeValueAsString(MAPPER.readValue(fixture("fixtures/rule.json"), Rule.class));
41 | assertThat(actual).isEqualTo(expected);
42 | }
43 |
44 | @Test
45 | public void deserializesFromJSON() throws Exception {
46 | final Rule actual = MAPPER.readValue(fixture("fixtures/rule.json"), Rule.class);
47 | assertThat(actual).isEqualTo(rule);
48 | }
49 |
50 | @Test(expected = JsonMappingException.class)
51 | public void testInvalidDuration() throws Exception {
52 | MAPPER.readValue(fixture("fixtures/rule_invalid_duration.json"), Rule.class);
53 | }
54 |
55 | @Test(expected = JsonMappingException.class)
56 | public void testInvalidTimeUnit() throws Exception {
57 | MAPPER.readValue(fixture("fixtures/rule_invalid_timeunit.json"), Rule.class);
58 | }
59 |
60 | @Test
61 | public void testInvalidMatchOn() throws Exception {
62 | final Rule actual = MAPPER.readValue(fixture("fixtures/rule_invalid_matchon.json"), Rule.class);
63 | assertThat(actual.getMatchOn().isPresent()).isFalse();
64 | }
65 |
66 | @Test
67 | public void testIsValidMatchOn() throws Exception {
68 | Rule rule = Rule.builder().build();
69 | assertThat(rule.isValid()).isFalse();
70 |
71 | rule = Rule.builder().withMatchOn(null).build();
72 | assertThat(rule.isValid()).isFalse();
73 |
74 | rule = Rule.builder().withMatchOn("").build();
75 | assertThat(rule.isValid()).isFalse();
76 |
77 | rule = Rule.builder().withMatchOn("like").build();
78 | assertThat(rule.isValid()).isTrue();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/notification-api/src/test/resources/fixtures/notification.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "12345",
3 | "category": "new-follower",
4 | "message": "you have a new follower",
5 | "created_at": "2015-06-29T21:04:12Z",
6 | "unseen": true,
7 | "properties": {
8 | "first_name": "Test",
9 | "last_name": "User"
10 | },
11 | "notifications": [
12 | {
13 | "id": "12346",
14 | "category": "new-follower",
15 | "message": "you have a new follower",
16 | "created_at": "2015-06-29T21:04:12Z",
17 | "unseen": true,
18 | "properties": {
19 | "first_name": "Test 2",
20 | "last_name": "User 2"
21 | }
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/notification-api/src/test/resources/fixtures/rule.json:
--------------------------------------------------------------------------------
1 | {
2 | "match_on": "first_name",
3 | "max_size": 3,
4 | "max_duration": "10 minutes"
5 | }
--------------------------------------------------------------------------------
/notification-api/src/test/resources/fixtures/rule_invalid_duration.json:
--------------------------------------------------------------------------------
1 | {
2 | "match_on": "first_name",
3 | "max_size": 3,
4 | "max_duration": "test"
5 | }
--------------------------------------------------------------------------------
/notification-api/src/test/resources/fixtures/rule_invalid_matchon.json:
--------------------------------------------------------------------------------
1 | {
2 | "match_on": "",
3 | "max_size": 3,
4 | "max_duration": "3 days"
5 | }
--------------------------------------------------------------------------------
/notification-api/src/test/resources/fixtures/rule_invalid_timeunit.json:
--------------------------------------------------------------------------------
1 | {
2 | "match_on": "first_name",
3 | "max_size": 3,
4 | "max_duration": "3 lightyears"
5 | }
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/config/NotificationConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.config;
17 |
18 | import com.fasterxml.jackson.annotation.JsonProperty;
19 | import com.smoketurner.dropwizard.graphql.GraphQLFactory;
20 | import com.smoketurner.dropwizard.riak.RiakFactory;
21 | import io.dropwizard.Configuration;
22 | import io.dropwizard.util.Duration;
23 | import io.dropwizard.validation.MinDuration;
24 | import java.util.concurrent.TimeUnit;
25 | import javax.validation.Valid;
26 | import javax.validation.constraints.NotNull;
27 |
28 | public class NotificationConfiguration extends Configuration {
29 |
30 | @NotNull
31 | @MinDuration(value = 1, unit = TimeUnit.SECONDS)
32 | private Duration ruleCacheTimeout = Duration.minutes(5);
33 |
34 | @NotNull
35 | @MinDuration(value = 1, unit = TimeUnit.MILLISECONDS)
36 | private Duration riakTimeout = Duration.seconds(60);
37 |
38 | @NotNull
39 | @MinDuration(value = 1, unit = TimeUnit.MILLISECONDS)
40 | private Duration riakRequestTimeout = Duration.seconds(5);
41 |
42 | @Valid @NotNull @JsonProperty private final RiakFactory riak = new RiakFactory();
43 |
44 | @Valid @NotNull @JsonProperty private final GraphQLFactory graphql = new GraphQLFactory();
45 |
46 | @JsonProperty
47 | public Duration getRiakTimeout() {
48 | return riakTimeout;
49 | }
50 |
51 | @JsonProperty
52 | public void setRiakTimeout(final Duration timeout) {
53 | this.riakTimeout = timeout;
54 | }
55 |
56 | @JsonProperty
57 | public Duration getRiakRequestTimeout() {
58 | return riakRequestTimeout;
59 | }
60 |
61 | @JsonProperty
62 | public void setRiakRequestTimeout(final Duration timeout) {
63 | this.riakRequestTimeout = timeout;
64 | }
65 |
66 | @JsonProperty
67 | public Duration getRuleCacheTimeout() {
68 | return ruleCacheTimeout;
69 | }
70 |
71 | @JsonProperty
72 | public void setRuleCacheTimeout(final Duration timeout) {
73 | this.ruleCacheTimeout = timeout;
74 | }
75 |
76 | @JsonProperty
77 | public RiakFactory getRiak() {
78 | return riak;
79 | }
80 |
81 | @JsonProperty
82 | public GraphQLFactory getGraphQL() {
83 | return graphql;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/core/IdGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.core;
17 |
18 | import com.amirkhawaja.Ksuid;
19 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
20 | import java.io.IOException;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | public class IdGenerator {
25 |
26 | private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerator.class);
27 | private final Ksuid ksuid;
28 |
29 | /** Constructor */
30 | public IdGenerator() {
31 | this.ksuid = new Ksuid();
32 | }
33 |
34 | /**
35 | * Generate a new notification ID
36 | *
37 | * @return the new notification ID
38 | * @throws NotificationStoreException if unable to generate an ID
39 | */
40 | public String nextId() throws NotificationStoreException {
41 | try {
42 | return ksuid.generate();
43 | } catch (IOException e) {
44 | LOGGER.error("Unable to generate new ID", e);
45 | throw new NotificationStoreException(e);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/core/Rollup.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.core;
17 |
18 | import com.smoketurner.notification.api.Notification;
19 | import com.smoketurner.notification.api.Rule;
20 | import java.util.Map;
21 | import java.util.Objects;
22 | import java.util.TreeSet;
23 | import java.util.stream.Stream;
24 |
25 | public class Rollup {
26 |
27 | private final Map rules;
28 | private final TreeSet matchers = new TreeSet<>();
29 |
30 | /**
31 | * Constructor
32 | *
33 | * @param rules Map of rules
34 | */
35 | public Rollup(final Map rules) {
36 | this.rules = Objects.requireNonNull(rules, "rules == null");
37 | }
38 |
39 | /**
40 | * Iterates over the notifications and uses the {@link Rule} and {@link Matcher} objects to roll
41 | * up the notifications based on the rules.
42 | *
43 | * @param notifications Notifications to roll up
44 | * @return Rolled up notifications
45 | */
46 | public Stream rollup(final Stream notifications) {
47 | Objects.requireNonNull(notifications, "notifications == null");
48 |
49 | if (rules.isEmpty()) {
50 | return notifications;
51 | }
52 |
53 | final TreeSet rollups = new TreeSet<>();
54 |
55 | notifications.forEachOrdered(
56 | notification -> {
57 | final Rule rule = rules.get(notification.getCategory());
58 |
59 | // If the notification category doesn't match any rule categories,
60 | // add the notification as-is to the list of rollups.
61 | if (rule == null || !rule.isValid()) {
62 | rollups.add(notification);
63 | } else if (matchers.isEmpty()) {
64 | // If we don't have any matchers yet, add the first one
65 | matchers.add(new Matcher(rule, notification));
66 | } else {
67 | // Loop through the existing matchers to see if this
68 | // notification falls into any previous rollups
69 | boolean matched = false;
70 | for (final Matcher matcher : matchers) {
71 | if (matcher.test(notification)) {
72 | matched = true;
73 |
74 | // if the matcher is now full, add it to the rollups and
75 | // remove it from the available matchers which still
76 | // have empty space.
77 | if (matcher.isFull()) {
78 | matchers.remove(matcher);
79 | rollups.add(matcher.getNotification());
80 | }
81 | break;
82 | }
83 | }
84 |
85 | // If the notification didn't match any existing rollups, add it
86 | // as a new matcher
87 | if (!matched) {
88 | matchers.add(new Matcher(rule, notification));
89 | }
90 | }
91 | });
92 |
93 | // Pull out the rolled up notifications out of the matchers
94 | for (final Matcher match : matchers) {
95 | rollups.add(match.getNotification());
96 | }
97 |
98 | return rollups.stream();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/core/StringSetParam.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.core;
17 |
18 | import com.google.common.base.Splitter;
19 | import com.google.common.base.Strings;
20 | import com.google.common.collect.ImmutableSet;
21 | import io.dropwizard.jersey.params.AbstractParam;
22 | import java.util.Collections;
23 | import java.util.Set;
24 | import javax.annotation.Nullable;
25 |
26 | public class StringSetParam extends AbstractParam> {
27 |
28 | public StringSetParam(String input) {
29 | super(input);
30 | }
31 |
32 | @Override
33 | protected Set parse(@Nullable final String input) throws Exception {
34 | if (Strings.isNullOrEmpty(input)) {
35 | return Collections.emptySet();
36 | }
37 |
38 | final Iterable splitter =
39 | Splitter.on(',').omitEmptyStrings().trimResults().split(input);
40 |
41 | return ImmutableSet.copyOf(splitter);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/core/UserNotifications.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.core;
17 |
18 | import com.google.common.collect.ImmutableSortedSet;
19 | import com.google.common.collect.Iterables;
20 | import com.smoketurner.notification.api.Notification;
21 | import java.util.Collections;
22 | import java.util.Objects;
23 | import java.util.StringJoiner;
24 | import java.util.TreeSet;
25 | import java.util.stream.Collectors;
26 | import java.util.stream.Stream;
27 |
28 | public final class UserNotifications {
29 |
30 | private final Iterable unseen;
31 | private final Iterable seen;
32 |
33 | /**
34 | * Constructor
35 | *
36 | * @param unseen Unseen notifications
37 | * @param seen Seen notifications
38 | */
39 | public UserNotifications(final Iterable unseen, final Iterable seen) {
40 | this.unseen = Objects.requireNonNull(unseen, "unseen == null");
41 | this.seen = Objects.requireNonNull(seen, "seen == null");
42 | }
43 |
44 | /**
45 | * Constructor
46 | *
47 | * @param unseen Unseen notifications
48 | * @param seen Seen notifications
49 | */
50 | public UserNotifications(final Stream unseen, final Stream seen) {
51 | Objects.requireNonNull(unseen, "unseen == null");
52 | Objects.requireNonNull(seen, "seen == null");
53 |
54 | this.unseen = unseen.collect(Collectors.toCollection(TreeSet::new));
55 | this.seen = seen.collect(Collectors.toCollection(TreeSet::new));
56 | }
57 |
58 | /**
59 | * Constructor
60 | *
61 | * @param unseen Unseen notifications
62 | */
63 | public UserNotifications(final Iterable unseen) {
64 | this.unseen = Objects.requireNonNull(unseen, "unseen == null");
65 | this.seen = Collections.emptySortedSet();
66 | }
67 |
68 | /**
69 | * Constructor
70 | *
71 | * @param unseen Unseen notifications
72 | */
73 | public UserNotifications(final Stream unseen) {
74 | Objects.requireNonNull(unseen, "unseen == null");
75 | this.unseen = unseen.collect(Collectors.toCollection(TreeSet::new));
76 | this.seen = Collections.emptySortedSet();
77 | }
78 |
79 | /** Constructor */
80 | public UserNotifications() {
81 | this.unseen = Collections.emptySortedSet();
82 | this.seen = Collections.emptySortedSet();
83 | }
84 |
85 | public boolean isEmpty() {
86 | return Iterables.isEmpty(unseen) && Iterables.isEmpty(seen);
87 | }
88 |
89 | public Iterable getUnseen() {
90 | return unseen;
91 | }
92 |
93 | public Iterable getSeen() {
94 | return seen;
95 | }
96 |
97 | public ImmutableSortedSet getNotifications() {
98 | return ImmutableSortedSet.naturalOrder().addAll(unseen).addAll(seen).build();
99 | }
100 |
101 | @Override
102 | public boolean equals(final Object obj) {
103 | if (this == obj) {
104 | return true;
105 | }
106 | if ((obj == null) || (getClass() != obj.getClass())) {
107 | return false;
108 | }
109 |
110 | final UserNotifications other = (UserNotifications) obj;
111 | return Objects.equals(unseen, other.unseen) && Objects.equals(seen, other.seen);
112 | }
113 |
114 | @Override
115 | public int hashCode() {
116 | return Objects.hash(unseen, seen);
117 | }
118 |
119 | @Override
120 | public String toString() {
121 | return new StringJoiner(", ", UserNotifications.class.getSimpleName() + "{", "}")
122 | .add("unseen=" + unseen)
123 | .add("seen=" + seen)
124 | .toString();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/core/WebSecurityFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.core;
17 |
18 | import com.google.common.net.HttpHeaders;
19 | import java.io.IOException;
20 | import javax.annotation.Priority;
21 | import javax.ws.rs.Priorities;
22 | import javax.ws.rs.container.ContainerRequestContext;
23 | import javax.ws.rs.container.ContainerResponseContext;
24 | import javax.ws.rs.container.ContainerResponseFilter;
25 | import javax.ws.rs.core.MultivaluedMap;
26 | import javax.ws.rs.ext.Provider;
27 |
28 | @Provider
29 | @Priority(Priorities.USER)
30 | public class WebSecurityFilter implements ContainerResponseFilter {
31 |
32 | @Override
33 | public void filter(ContainerRequestContext request, ContainerResponseContext response)
34 | throws IOException {
35 |
36 | final MultivaluedMap headers = response.getHeaders();
37 |
38 | headers.putSingle(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff");
39 | headers.putSingle(HttpHeaders.X_FRAME_OPTIONS, "deny");
40 | headers.putSingle(HttpHeaders.X_XSS_PROTECTION, "1; mode=block");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/exceptions/NotificationException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.exceptions;
17 |
18 | import javax.ws.rs.WebApplicationException;
19 | import javax.ws.rs.core.Response;
20 |
21 | public class NotificationException extends WebApplicationException {
22 |
23 | private static final long serialVersionUID = 4359514441284675684L;
24 | private final int code;
25 | private final Response.Status status;
26 | private final String message;
27 |
28 | /**
29 | * Constructor
30 | *
31 | * @param code Status code to return
32 | * @param message Error message to return
33 | */
34 | public NotificationException(final int code, final String message) {
35 | super(code);
36 | this.code = code;
37 | this.status = Response.Status.fromStatusCode(code);
38 | this.message = message;
39 | }
40 |
41 | /**
42 | * Constructor
43 | *
44 | * @param status Status code to return
45 | * @param message Error message to return
46 | */
47 | public NotificationException(final Response.Status status, final String message) {
48 | super(status);
49 | this.code = status.getStatusCode();
50 | this.status = status;
51 | this.message = message;
52 | }
53 |
54 | /**
55 | * Constructor
56 | *
57 | * @param status Status code to return
58 | * @param message Error message to return
59 | * @param cause Throwable which caused the exception
60 | */
61 | public NotificationException(
62 | final Response.Status status, final String message, final Throwable cause) {
63 | super(cause, status);
64 | this.code = status.getStatusCode();
65 | this.status = status;
66 | this.message = message;
67 | }
68 |
69 | public int getCode() {
70 | return code;
71 | }
72 |
73 | public Response.Status getStatus() {
74 | return status;
75 | }
76 |
77 | @Override
78 | public String getMessage() {
79 | return message;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/exceptions/NotificationExceptionMapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.exceptions;
17 |
18 | import io.dropwizard.jersey.errors.ErrorMessage;
19 | import javax.ws.rs.core.MediaType;
20 | import javax.ws.rs.core.Response;
21 | import javax.ws.rs.ext.ExceptionMapper;
22 | import org.slf4j.Logger;
23 | import org.slf4j.LoggerFactory;
24 |
25 | public class NotificationExceptionMapper implements ExceptionMapper {
26 |
27 | private static final Logger LOGGER = LoggerFactory.getLogger(NotificationExceptionMapper.class);
28 |
29 | @Override
30 | public Response toResponse(final NotificationException exception) {
31 | LOGGER.debug("Error response ({}): {}", exception.getCode(), exception.getMessage());
32 |
33 | return Response.status(exception.getCode())
34 | .entity(new ErrorMessage(exception.getCode(), exception.getMessage()))
35 | .type(MediaType.APPLICATION_JSON)
36 | .build();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/exceptions/NotificationStoreException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.exceptions;
17 |
18 | public class NotificationStoreException extends Exception {
19 |
20 | private static final long serialVersionUID = 1L;
21 |
22 | public NotificationStoreException() {
23 | super();
24 | }
25 |
26 | public NotificationStoreException(final Throwable cause) {
27 | super(cause);
28 | }
29 |
30 | public NotificationStoreException(final String message) {
31 | super(message);
32 | }
33 |
34 | public NotificationStoreException(final String message, final Throwable cause) {
35 | super(message, cause);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/CreateNotificationMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
20 | import com.smoketurner.notification.api.Notification;
21 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
22 | import com.smoketurner.notification.application.store.NotificationStore;
23 | import graphql.schema.DataFetcher;
24 | import graphql.schema.DataFetchingEnvironment;
25 | import java.util.Collections;
26 | import java.util.Map;
27 | import java.util.Objects;
28 | import java.util.stream.Collectors;
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | public class CreateNotificationMutation implements DataFetcher {
33 |
34 | private static final Logger LOGGER = LoggerFactory.getLogger(CreateNotificationMutation.class);
35 | private final NotificationStore store;
36 |
37 | /**
38 | * Constructor
39 | *
40 | * @param store Notification data store
41 | */
42 | public CreateNotificationMutation(final NotificationStore store) {
43 | this.store = Objects.requireNonNull(store, "store == null");
44 | }
45 |
46 | @Override
47 | public Notification get(DataFetchingEnvironment environment) {
48 | final String username = environment.getArgument("username");
49 | if (Strings.isNullOrEmpty(username)) {
50 | throw new GraphQLValidationError("username cannot be empty");
51 | }
52 |
53 | final Map input = environment.getArgument("notification");
54 | if (input == null || input.isEmpty()) {
55 | throw new GraphQLValidationError("notification cannot be empty");
56 | }
57 |
58 | final String category = String.valueOf(input.get("category")).trim();
59 | if (Strings.isNullOrEmpty(category) || "null".equals(category)) {
60 | throw new GraphQLValidationError("category cannot be empty");
61 | }
62 |
63 | final int categoryLength = category.codePointCount(0, category.length());
64 |
65 | if (categoryLength < Notification.CATEGORY_MIN_LENGTH
66 | || categoryLength > Notification.CATEGORY_MAX_LENGTH) {
67 | throw new GraphQLValidationError(
68 | String.format(
69 | "category must be between %d and %d characters",
70 | Notification.CATEGORY_MIN_LENGTH, Notification.CATEGORY_MAX_LENGTH));
71 | }
72 |
73 | final String message = String.valueOf(input.get("message")).trim();
74 | if (Strings.isNullOrEmpty(message) || "null".equals(message)) {
75 | throw new GraphQLValidationError("message cannot be empty");
76 | }
77 |
78 | final int messageLength = message.codePointCount(0, message.length());
79 |
80 | if (messageLength < Notification.MESSAGE_MIN_LENGTH
81 | || messageLength > Notification.MESSAGE_MAX_LENGTH) {
82 | throw new GraphQLValidationError(
83 | String.format(
84 | "message must be between %d and %d characters",
85 | Notification.MESSAGE_MIN_LENGTH, Notification.MESSAGE_MAX_LENGTH));
86 | }
87 |
88 | final Notification.Builder builder = Notification.builder(category, message);
89 |
90 | final Object properties = input.get("properties");
91 | if (properties != null && properties instanceof Map) {
92 | builder.withProperties(convertToMap(properties));
93 | }
94 |
95 | final Notification notification = builder.build();
96 |
97 | try {
98 | return store.store(username, notification);
99 | } catch (NotificationStoreException e) {
100 | LOGGER.error(String.format("Unable to create notification for %s", username), e);
101 | throw new GraphQLValidationError("Unable to create notification");
102 | }
103 | }
104 |
105 | /**
106 | * Safely convert an Object into a Map of string objects.
107 | *
108 | * @param obj Object to convert
109 | * @return Map of properties
110 | */
111 | private static Map convertToMap(final Object obj) {
112 | if (obj == null) {
113 | return Collections.emptyMap();
114 | }
115 |
116 | @SuppressWarnings("unchecked")
117 | final Map map = (Map) obj;
118 | if (map.isEmpty()) {
119 | return Collections.emptyMap();
120 | }
121 |
122 | return map.entrySet().stream()
123 | .collect(Collectors.toMap(Map.Entry::getKey, e -> String.valueOf(e.getValue()).trim()));
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/CreateRuleMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
20 | import com.smoketurner.notification.api.Notification;
21 | import com.smoketurner.notification.api.Rule;
22 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
23 | import com.smoketurner.notification.application.store.RuleStore;
24 | import graphql.schema.DataFetcher;
25 | import graphql.schema.DataFetchingEnvironment;
26 | import io.dropwizard.util.Duration;
27 | import java.util.Map;
28 | import java.util.Objects;
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | public class CreateRuleMutation implements DataFetcher {
33 |
34 | private static final Logger LOGGER = LoggerFactory.getLogger(CreateRuleMutation.class);
35 | private final RuleStore store;
36 |
37 | /**
38 | * Constructor
39 | *
40 | * @param store Rule data store
41 | */
42 | public CreateRuleMutation(final RuleStore store) {
43 | this.store = Objects.requireNonNull(store, "store == null");
44 | }
45 |
46 | @Override
47 | public Boolean get(DataFetchingEnvironment environment) {
48 | final String category = environment.getArgument("category");
49 | if (Strings.isNullOrEmpty(category)) {
50 | throw new GraphQLValidationError("category cannot be empty");
51 | }
52 |
53 | final int categoryLength = category.codePointCount(0, category.length());
54 |
55 | if (categoryLength < Notification.CATEGORY_MIN_LENGTH
56 | || categoryLength > Notification.CATEGORY_MAX_LENGTH) {
57 | throw new GraphQLValidationError(
58 | String.format(
59 | "category must be between %d and %d characters",
60 | Notification.CATEGORY_MIN_LENGTH, Notification.CATEGORY_MAX_LENGTH));
61 | }
62 |
63 | final Map input = environment.getArgument("rule");
64 | if (input == null || input.isEmpty()) {
65 | throw new GraphQLValidationError("rule cannot be empty");
66 | }
67 |
68 | final Rule.Builder builder = Rule.builder();
69 | if (input.containsKey("maxSize")) {
70 | try {
71 | builder.withMaxSize(Integer.parseInt(String.valueOf(input.get("maxSize"))));
72 | } catch (NumberFormatException e) {
73 | throw new GraphQLValidationError("maxSize is not an integer");
74 | }
75 | }
76 | if (input.containsKey("maxDuration")) {
77 | try {
78 | builder.withMaxDuration(Duration.parse(String.valueOf(input.get("maxDuration"))));
79 | } catch (IllegalArgumentException e) {
80 | throw new GraphQLValidationError("maxDuration is an invalid duration");
81 | }
82 | }
83 | if (input.containsKey("matchOn")) {
84 | builder.withMatchOn(String.valueOf(input.get("matchOn")));
85 | }
86 |
87 | final Rule rule = builder.build();
88 |
89 | if (!rule.isValid()) {
90 | throw new GraphQLValidationError("rule cannot be empty");
91 | }
92 |
93 | try {
94 | store.store(category, rule);
95 | } catch (NotificationStoreException e) {
96 | LOGGER.error(String.format("Unable to create rule for %s", category), e);
97 | throw new GraphQLValidationError("Unable to create rule");
98 | }
99 |
100 | return true;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/NotificationDataFetcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.notification.api.Notification;
20 | import com.smoketurner.notification.application.core.UserNotifications;
21 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
22 | import com.smoketurner.notification.application.store.NotificationStore;
23 | import graphql.schema.DataFetcher;
24 | import graphql.schema.DataFetchingEnvironment;
25 | import java.util.Collections;
26 | import java.util.Objects;
27 | import java.util.Optional;
28 | import java.util.SortedSet;
29 | import javax.annotation.Nullable;
30 | import org.slf4j.Logger;
31 | import org.slf4j.LoggerFactory;
32 |
33 | public class NotificationDataFetcher implements DataFetcher> {
34 |
35 | private static final Logger LOGGER = LoggerFactory.getLogger(NotificationDataFetcher.class);
36 | private final NotificationStore store;
37 |
38 | /**
39 | * Constructor
40 | *
41 | * @param store Notification data store
42 | */
43 | public NotificationDataFetcher(final NotificationStore store) {
44 | this.store = Objects.requireNonNull(store, "store == null");
45 | }
46 |
47 | @Nullable
48 | @Override
49 | public SortedSet get(DataFetchingEnvironment environment) {
50 | final String username = environment.getArgument("username");
51 | if (Strings.isNullOrEmpty(username)) {
52 | return null;
53 | }
54 |
55 | final Optional notifications;
56 | try {
57 | notifications = store.fetch(username);
58 | } catch (NotificationStoreException e) {
59 | LOGGER.error("Unable to fetch notifications", e);
60 | return null;
61 | }
62 |
63 | if (!notifications.isPresent()) {
64 | return Collections.emptySortedSet();
65 | }
66 |
67 | return notifications.get().getNotifications();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/RemoveAllNotificationsMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
20 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
21 | import com.smoketurner.notification.application.store.NotificationStore;
22 | import graphql.schema.DataFetcher;
23 | import graphql.schema.DataFetchingEnvironment;
24 | import java.util.Objects;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 |
28 | public class RemoveAllNotificationsMutation implements DataFetcher {
29 |
30 | private static final Logger LOGGER =
31 | LoggerFactory.getLogger(RemoveAllNotificationsMutation.class);
32 | private final NotificationStore store;
33 |
34 | /**
35 | * Constructor
36 | *
37 | * @param store Notification data store
38 | */
39 | public RemoveAllNotificationsMutation(final NotificationStore store) {
40 | this.store = Objects.requireNonNull(store, "store == null");
41 | }
42 |
43 | @Override
44 | public Boolean get(DataFetchingEnvironment environment) {
45 | final String username = environment.getArgument("username");
46 | if (Strings.isNullOrEmpty(username)) {
47 | throw new GraphQLValidationError("username cannot be empty");
48 | }
49 |
50 | try {
51 | store.removeAll(username);
52 | } catch (NotificationStoreException e) {
53 | LOGGER.error(String.format("Unable to remove all notifications for %s", username), e);
54 | throw new GraphQLValidationError("Unable to remove all notifications");
55 | }
56 |
57 | return true;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/RemoveAllRulesMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
19 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
20 | import com.smoketurner.notification.application.store.RuleStore;
21 | import graphql.schema.DataFetcher;
22 | import graphql.schema.DataFetchingEnvironment;
23 | import java.util.Objects;
24 | import org.slf4j.Logger;
25 | import org.slf4j.LoggerFactory;
26 |
27 | public class RemoveAllRulesMutation implements DataFetcher {
28 |
29 | private static final Logger LOGGER = LoggerFactory.getLogger(RemoveAllRulesMutation.class);
30 | private final RuleStore store;
31 |
32 | /**
33 | * Constructor
34 | *
35 | * @param store Rule data store
36 | */
37 | public RemoveAllRulesMutation(final RuleStore store) {
38 | this.store = Objects.requireNonNull(store, "store == null");
39 | }
40 |
41 | @Override
42 | public Boolean get(DataFetchingEnvironment environment) {
43 | try {
44 | store.removeAll();
45 | } catch (NotificationStoreException e) {
46 | LOGGER.error("Unable to remove all rules", e);
47 | throw new GraphQLValidationError("Unable to remove all rules");
48 | }
49 |
50 | return true;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/RemoveNotificationMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
20 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
21 | import com.smoketurner.notification.application.store.NotificationStore;
22 | import graphql.schema.DataFetcher;
23 | import graphql.schema.DataFetchingEnvironment;
24 | import java.util.List;
25 | import java.util.Objects;
26 | import org.slf4j.Logger;
27 | import org.slf4j.LoggerFactory;
28 |
29 | public class RemoveNotificationMutation implements DataFetcher {
30 |
31 | private static final Logger LOGGER = LoggerFactory.getLogger(RemoveNotificationMutation.class);
32 | private final NotificationStore store;
33 |
34 | /**
35 | * Constructor
36 | *
37 | * @param store Notification data store
38 | */
39 | public RemoveNotificationMutation(final NotificationStore store) {
40 | this.store = Objects.requireNonNull(store, "store == null");
41 | }
42 |
43 | @Override
44 | public Boolean get(DataFetchingEnvironment environment) {
45 | final String username = environment.getArgument("username");
46 | if (Strings.isNullOrEmpty(username)) {
47 | throw new GraphQLValidationError("username cannot be empty");
48 | }
49 |
50 | final List ids = environment.getArgument("ids");
51 | if (ids == null || ids.isEmpty()) {
52 | return false;
53 | }
54 |
55 | try {
56 | store.remove(username, ids);
57 | } catch (NotificationStoreException e) {
58 | LOGGER.error(String.format("Unable to remove notifications for %s", username), e);
59 | throw new GraphQLValidationError("Unable to remove notifications");
60 | }
61 |
62 | return true;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/RemoveRuleMutation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.base.Strings;
19 | import com.smoketurner.dropwizard.graphql.GraphQLValidationError;
20 | import com.smoketurner.notification.application.exceptions.NotificationStoreException;
21 | import com.smoketurner.notification.application.store.RuleStore;
22 | import graphql.schema.DataFetcher;
23 | import graphql.schema.DataFetchingEnvironment;
24 | import java.util.Objects;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 |
28 | public class RemoveRuleMutation implements DataFetcher {
29 |
30 | private static final Logger LOGGER = LoggerFactory.getLogger(RemoveRuleMutation.class);
31 | private final RuleStore store;
32 |
33 | /**
34 | * Constructor
35 | *
36 | * @param store Rule data store
37 | */
38 | public RemoveRuleMutation(final RuleStore store) {
39 | this.store = Objects.requireNonNull(store, "store == null");
40 | }
41 |
42 | @Override
43 | public Boolean get(DataFetchingEnvironment environment) {
44 | final String category = environment.getArgument("category");
45 | if (Strings.isNullOrEmpty(category)) {
46 | throw new GraphQLValidationError("category cannot be empty");
47 | }
48 |
49 | try {
50 | store.remove(category);
51 | } catch (NotificationStoreException e) {
52 | LOGGER.error(String.format("Unable to remove rule for %s", category), e);
53 | throw new GraphQLValidationError("Unable to remove rule");
54 | }
55 |
56 | return true;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/notification-application/src/main/java/com/smoketurner/notification/application/graphql/RuleDataFetcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019 Smoke Turner, LLC (github@smoketurner.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.smoketurner.notification.application.graphql;
17 |
18 | import com.google.common.collect.ImmutableMap;
19 | import com.smoketurner.notification.api.Rule;
20 | import com.smoketurner.notification.application.store.RuleStore;
21 | import graphql.schema.DataFetcher;
22 | import graphql.schema.DataFetchingEnvironment;
23 | import java.util.List;
24 | import java.util.Map;
25 | import java.util.Objects;
26 | import java.util.stream.Collectors;
27 |
28 | public class RuleDataFetcher implements DataFetcher>> {
29 |
30 | private final RuleStore store;
31 |
32 | /**
33 | * Constructor
34 | *
35 | * @param store Rule data store
36 | */
37 | public RuleDataFetcher(final RuleStore store) {
38 | this.store = Objects.requireNonNull(store, "store == null");
39 | }
40 |
41 | @Override
42 | public List