├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .codecov.yml
├── src
├── test
│ ├── resources
│ │ ├── application-aws-configured.yml
│ │ └── application-cors-configured.yml
│ └── java
│ │ └── com
│ │ └── jpomykala
│ │ └── springhoc
│ │ ├── cors
│ │ ├── TestCorsApplication.java
│ │ ├── SpringHocCORSAutoConfigurationTest.java
│ │ ├── CorsDefaultConfiguredIT.java
│ │ └── CorsConfiguredIT.java
│ │ ├── s3
│ │ ├── TestByteUtility.java
│ │ ├── UploadServiceAutoConfigurationTest.java
│ │ ├── model
│ │ │ └── UploadRequestTest.java
│ │ └── UploadServiceTest.java
│ │ ├── wrapper
│ │ ├── ResponseWrappingAutoConfigurationTests.java
│ │ ├── TestWrapperApplication.java
│ │ ├── ResponseWrappingIT.java
│ │ └── ResponseWrappingTests.java
│ │ ├── logging
│ │ ├── LoggingFilterAutoConfigurationTest.java
│ │ └── LoggingFilterFactoryTest.java
│ │ ├── mail
│ │ ├── MailServiceTest.java
│ │ └── AmazonSesAutoConfigurationTest.java
│ │ └── utils
│ │ └── RequestUtilsTest.java
└── main
│ └── java
│ └── com
│ └── jpomykala
│ └── springhoc
│ ├── recaptcha
│ ├── ReCaptchaService.java
│ └── google
│ │ ├── GoogleReCaptchaConfigurationProperties.java
│ │ ├── GoogleReCaptchaAutoConfiguration.java
│ │ ├── GoogleReCaptchaResponse.java
│ │ └── GoogleReCaptchaService.java
│ ├── logging
│ ├── PrincipalProvider.java
│ ├── RequestIdProvider.java
│ ├── EnableRequestLogging.java
│ ├── RequestLoggingAutoConfiguration.java
│ ├── LoggingFilter.java
│ └── LoggingFilterFactory.java
│ ├── wrapper
│ ├── DisableWrapping.java
│ ├── EnableResponseWrapping.java
│ ├── PageDetails.java
│ ├── ResponseWrapper.java
│ └── RestResponse.java
│ ├── cors
│ ├── EnableCORS.java
│ ├── CorsConfigurationProperties.java
│ └── CorsAutoConfiguration.java
│ ├── mail
│ ├── EnableEmailSending.java
│ ├── MailServiceAutoConfiguration.java
│ ├── MailService.java
│ ├── AmazonSesAutoConfiguration.java
│ └── EmailRequest.java
│ ├── s3
│ ├── EnableFileUploading.java
│ ├── AmazonS3Properties.java
│ ├── UploadServiceAutoConfiguration.java
│ ├── AmazonS3AutoConfiguration.java
│ ├── UploadRequest.java
│ └── UploadService.java
│ ├── utils
│ └── RequestUtils.java
│ └── AmazonProperties.java
├── .gitignore
├── .maven.xml
├── .travis.yml
├── .github
└── workflows
│ └── maven-publish.yml
├── mvnw.cmd
├── mvnw
├── pom.xml
└── README.md
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpomykala/spring-higher-order-components/HEAD/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
2 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch: off
4 | project:
5 | default:
6 | # basic
7 | threshold: 0.05
8 | comment: off
9 |
--------------------------------------------------------------------------------
/src/test/resources/application-aws-configured.yml:
--------------------------------------------------------------------------------
1 | spring-hoc:
2 | aws:
3 | access-key: xxxxxxxx
4 | secret-key: xxxxxxxx
5 | region: eu-west-1
6 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/recaptcha/ReCaptchaService.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.recaptcha;
2 |
3 | public interface ReCaptchaService {
4 |
5 | boolean isValid(String reCaptchaToken);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/PrincipalProvider.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 |
5 | public interface PrincipalProvider {
6 |
7 | String getPrincipal(HttpServletRequest request);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/RequestIdProvider.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 |
5 | public interface RequestIdProvider {
6 |
7 | String getRequestId(HttpServletRequest request);
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | *.pgp
3 | ### STS ###
4 | .apt_generated
5 | .classpath
6 | .factorypath
7 | .project
8 | .settings
9 | .springBeans
10 | .sts4-cache
11 |
12 |
13 |
14 |
15 | ### IntelliJ IDEA ###
16 | .idea
17 | *.iws
18 | *.iml
19 | *.ipr
20 |
21 | ### NetBeans ###
22 | /nbproject/private/
23 | /build/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/wrapper/DisableWrapping.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * @author Jakub Pomykala on 2/26/18.
7 | */
8 | @Target({ElementType.METHOD})
9 | @Retention(RetentionPolicy.RUNTIME)
10 | @Documented
11 | @Inherited
12 | public @interface DisableWrapping
13 | {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/cors/EnableCORS.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.springframework.context.annotation.Import;
4 |
5 | import java.lang.annotation.*;
6 |
7 | @Target({ElementType.TYPE})
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Documented
10 | @Inherited
11 | @Import({CorsAutoConfiguration.class})
12 | public @interface EnableCORS {
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/resources/application-cors-configured.yml:
--------------------------------------------------------------------------------
1 | spring-hoc:
2 | cors:
3 | allow-credentials: true
4 | allowed-origins:
5 | - "http://google.com"
6 | - "https://jpomykala.com"
7 | allowed-methods:
8 | - GET
9 | - POST
10 | - PATCH
11 | - DELETE
12 | allowed-headers:
13 | - Content-Type
14 | exposed-headers:
15 | - Content-Type
16 | max-age: 20
17 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/EnableRequestLogging.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import org.springframework.context.annotation.Import;
4 |
5 | import java.lang.annotation.*;
6 |
7 | @Target({ElementType.TYPE})
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Documented
10 | @Inherited
11 | @Import({RequestLoggingAutoConfiguration.class})
12 | public @interface EnableRequestLogging {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/mail/EnableEmailSending.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import org.springframework.context.annotation.Import;
4 |
5 | import java.lang.annotation.*;
6 |
7 | @Target({ElementType.TYPE})
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Documented
10 | @Inherited
11 | @Import({AmazonSesAutoConfiguration.class, MailServiceAutoConfiguration.class})
12 | public @interface EnableEmailSending {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/EnableFileUploading.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import org.springframework.context.annotation.Import;
4 |
5 | import java.lang.annotation.*;
6 |
7 | @Target({ElementType.TYPE})
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Documented
10 | @Inherited
11 | @Import({AmazonS3AutoConfiguration.class, UploadServiceAutoConfiguration.class})
12 | public @interface EnableFileUploading {
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/RequestLoggingAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 |
6 | @Configuration
7 | public class RequestLoggingAutoConfiguration {
8 |
9 | @Bean
10 | public LoggingFilterFactory loggingFilterFactory() {
11 | return new LoggingFilterFactory();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/wrapper/EnableResponseWrapping.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.springframework.context.annotation.Import;
4 |
5 | import java.lang.annotation.*;
6 |
7 | @Documented
8 | @Inherited
9 | @Retention(RetentionPolicy.RUNTIME)
10 | @Target({ElementType.TYPE})
11 | @Import({ResponseWrapper.class})
12 | public @interface EnableResponseWrapping {
13 |
14 | Class>[] exclude() default {};
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/AmazonS3Properties.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties(prefix = "spring-hoc.s3")
6 | public class AmazonS3Properties {
7 |
8 | private String bucketName;
9 |
10 | public String getBucketName() {
11 | return bucketName;
12 | }
13 |
14 | public void setBucketName(String bucketName) {
15 | this.bucketName = bucketName;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/recaptcha/google/GoogleReCaptchaConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.recaptcha.google;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties(prefix = "spring-hoc.recaptcha.google")
6 | public class GoogleReCaptchaConfigurationProperties {
7 | private String secret;
8 |
9 | public String getSecret() {
10 | return secret;
11 | }
12 |
13 | public void setSecret(String secret) {
14 | this.secret = secret;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.maven.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.jacoco
4 |
5 |
6 |
7 | ossrh
8 | ${env.OSSRH_USERNAME}
9 | ${env.OSSRH_PASSWORD}
10 |
11 |
12 |
13 |
14 |
15 |
16 | gpg
17 |
18 | true
19 |
20 |
21 | ${env.GPG_EXECUTABLE}
22 | ${env.GPG_PASS_PHRASE}
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/cors/TestCorsApplication.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | @SpringBootApplication(scanBasePackages = { "com.jpomykala.springhoc.cors" })
10 | @RestController
11 | public class TestCorsApplication extends SpringBootServletInitializer
12 | {
13 | public static void main(String[] args)
14 | {
15 | SpringApplication.run(TestCorsApplication.class, args);
16 | }
17 |
18 | @GetMapping("/echo")
19 | public String echo()
20 | {
21 | return "hello";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/s3/TestByteUtility.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.util.concurrent.ThreadLocalRandom;
5 |
6 | public final class TestByteUtility {
7 | private TestByteUtility() {
8 | //hidden
9 | }
10 |
11 | public static byte[] generateRandomByteStream(int size) {
12 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
13 | for (int i = 0; i < size; i++) {
14 | int randomByte = ThreadLocalRandom.current().nextInt();
15 | byteArrayOutputStream.write(randomByte);
16 | }
17 |
18 | return byteArrayOutputStream.toByteArray();
19 | }
20 |
21 | public static byte[] generateRandomByteStream() {
22 | int randomSize = ThreadLocalRandom.current().nextInt(10_000, 100_000);
23 | return generateRandomByteStream(randomSize);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/wrapper/ResponseWrappingAutoConfigurationTests.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.junit.Test;
4 | import org.springframework.boot.autoconfigure.AutoConfigurations;
5 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
6 |
7 | import static org.assertj.core.api.Java6Assertions.assertThat;
8 |
9 | public class ResponseWrappingAutoConfigurationTests {
10 |
11 | private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
12 | .withConfiguration(AutoConfigurations.of(ResponseWrapper.class));
13 |
14 |
15 | @Test
16 | public void testDefaultCorsConfiguration() {
17 | this.contextRunner.run(context -> {
18 | ResponseWrapper responseWrapper = context.getBean(ResponseWrapper.class);
19 | assertThat(responseWrapper).isNotNull();
20 | });
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | install: mvn --settings .maven.xml clean install -Dgpg.skip -Dmaven.javadoc.skip=true -Ptest -B -V
5 | after_success:
6 | - mvn clean test -Ptest && bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports"
7 |
8 | cache:
9 | directories:
10 | - "$HOME/.m2"
11 | addons:
12 | apt:
13 | packages:
14 | - oracle-java8-installer
15 | before_install:
16 | - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import
17 | - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust
18 |
19 |
20 | deploy:
21 | provider: script
22 | script: mvn versions:set -DremoveSnapshot && mvn --settings .maven.xml clean deploy -DskipTests=true -B -U -Prelease
23 | skip_cleanup: true
24 | on:
25 | repo: jpomykala/spring-higher-order-components
26 | tags: true
27 | jdk: oraclejdk8
28 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/logging/LoggingFilterAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import org.junit.Test;
4 | import org.springframework.boot.autoconfigure.AutoConfigurations;
5 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
6 |
7 | import static org.assertj.core.api.Java6Assertions.assertThat;
8 |
9 | public class LoggingFilterAutoConfigurationTest {
10 |
11 | private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
12 | .withConfiguration(AutoConfigurations.of(RequestLoggingAutoConfiguration.class));
13 |
14 |
15 | @Test
16 | public void testDefaultCorsConfiguration() {
17 | this.contextRunner.run(context -> {
18 | RequestLoggingAutoConfiguration responseWrapper = context.getBean(RequestLoggingAutoConfiguration.class);
19 | assertThat(responseWrapper).isNotNull();
20 | });
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/mail/MailServiceAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
6 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | @Configuration
11 | @AutoConfigureAfter(AmazonSimpleEmailService.class)
12 | public class MailServiceAutoConfiguration {
13 |
14 | private final AmazonSimpleEmailService amazonSimpleEmailService;
15 |
16 | @Autowired
17 | public MailServiceAutoConfiguration(AmazonSimpleEmailService amazonSimpleEmailService) {
18 | this.amazonSimpleEmailService = amazonSimpleEmailService;
19 | }
20 |
21 | @Bean
22 | public MailService mailSendingService() {
23 | return new MailService(amazonSimpleEmailService);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/cors/SpringHocCORSAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.junit.Test;
4 | import org.springframework.boot.autoconfigure.AutoConfigurations;
5 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
6 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
7 | import org.springframework.core.Ordered;
8 |
9 | import static org.assertj.core.api.Java6Assertions.assertThat;
10 |
11 | public class SpringHocCORSAutoConfigurationTest {
12 |
13 |
14 | private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
15 | .withConfiguration(AutoConfigurations.of(CorsAutoConfiguration.class));
16 |
17 |
18 | @Test
19 | public void testDefaultCorsConfiguration() {
20 | this.contextRunner.run(context -> {
21 | FilterRegistrationBean filterRegistrationBean = context.getBean("corsFilter", FilterRegistrationBean.class);
22 | assertThat(filterRegistrationBean).isNotNull();
23 | assertThat(filterRegistrationBean.getOrder()).isEqualTo(Ordered.HIGHEST_PRECEDENCE);
24 | });
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/UploadServiceAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import com.amazonaws.services.s3.AmazonS3;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
6 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | @Configuration
11 | @AutoConfigureAfter(AmazonS3AutoConfiguration.class)
12 | @EnableConfigurationProperties(AmazonS3Properties.class)
13 | public class UploadServiceAutoConfiguration {
14 |
15 | private final AmazonS3Properties properties;
16 | private final AmazonS3 amazonS3;
17 |
18 | @Autowired
19 | public UploadServiceAutoConfiguration(AmazonS3Properties properties,
20 | AmazonS3 amazonS3) {
21 | this.properties = properties;
22 | this.amazonS3 = amazonS3;
23 | }
24 |
25 | @Bean
26 | public UploadService uploadService() {
27 | return new UploadService(properties, amazonS3);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/recaptcha/google/GoogleReCaptchaAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.recaptcha.google;
2 |
3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.web.client.RestTemplate;
7 |
8 | @EnableConfigurationProperties(GoogleReCaptchaConfigurationProperties.class)
9 | public class GoogleReCaptchaAutoConfiguration {
10 |
11 | private final GoogleReCaptchaConfigurationProperties properties;
12 |
13 |
14 | private final RestTemplate restTemplate;
15 |
16 | public GoogleReCaptchaAutoConfiguration(GoogleReCaptchaConfigurationProperties properties, RestTemplate restTemplate) {
17 | this.restTemplate = restTemplate;
18 | this.properties = properties;
19 | }
20 |
21 | @Bean
22 | @ConditionalOnProperty(prefix = "spring-hoc.recaptcha.google", name = "secret")
23 | public GoogleReCaptchaService reCaptchaService() {
24 | return new GoogleReCaptchaService(restTemplate, properties);
25 | }
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/mail/MailService.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
4 | import com.amazonaws.services.simpleemail.model.SendEmailRequest;
5 | import org.springframework.context.event.EventListener;
6 | import org.springframework.lang.NonNull;
7 |
8 | public class MailService {
9 |
10 | private final AmazonSimpleEmailService simpleEmailService;
11 |
12 | public MailService(AmazonSimpleEmailService simpleEmailService) {
13 | this.simpleEmailService = simpleEmailService;
14 | }
15 |
16 | @EventListener(EmailRequest.class)
17 | public void onEmailMessageRequest(@NonNull EmailRequest emailRequest) {
18 | sendEmail(emailRequest);
19 | }
20 |
21 | private void sendEmail(@NonNull EmailRequest emailRequest) {
22 | SendEmailRequest sendEmailRequest = new SendEmailRequest()
23 | .withMessage(emailRequest.getMessage())
24 | .withSource(emailRequest.getSource())
25 | .withReplyToAddresses(emailRequest.getReplyTo())
26 | .withDestination(emailRequest.getDestination());
27 | simpleEmailService.sendEmail(sendEmailRequest);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/mail/AmazonSesAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
4 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClientBuilder;
5 | import com.jpomykala.springhoc.AmazonProperties;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | @Configuration
13 | @EnableConfigurationProperties(AmazonProperties.class)
14 | public class AmazonSesAutoConfiguration {
15 |
16 | private final AmazonProperties properties;
17 |
18 | @Autowired
19 | public AmazonSesAutoConfiguration(AmazonProperties properties) {
20 | this.properties = properties;
21 | }
22 |
23 | @Bean
24 | @ConditionalOnMissingBean
25 | public AmazonSimpleEmailService amazonSimpleEmailService() {
26 | return AmazonSimpleEmailServiceClientBuilder.standard()
27 | .withCredentials(properties.getAWSCredentials())
28 | .withRegion(properties.getRegions())
29 | .build();
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/utils/RequestUtils.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.utils;
2 |
3 |
4 | import org.springframework.util.StringUtils;
5 |
6 | import jakarta.servlet.http.HttpServletRequest;
7 | import java.util.Optional;
8 |
9 | public final class RequestUtils {
10 |
11 | private RequestUtils() {
12 | //hidden constructor
13 | throw new AssertionError("Util class not available for instantiation");
14 | }
15 |
16 | public static String getClientIP(HttpServletRequest request) {
17 | String defaultOutput = "127.0.0.1";
18 | if (request == null) {
19 | return defaultOutput;
20 | }
21 |
22 | String xfHeader = request.getHeader("X-Forwarded-For");
23 | if (StringUtils.hasText(xfHeader)) {
24 | String[] split = xfHeader.split(",");
25 | if (split.length > 0) {
26 | return split[0];
27 | }
28 | }
29 |
30 | String remoteAddr = request.getRemoteAddr();
31 | if (StringUtils.hasText(remoteAddr)) {
32 | return remoteAddr;
33 | }
34 | return defaultOutput;
35 | }
36 |
37 | public static String getPath(HttpServletRequest request) {
38 | Optional optionalRequest = Optional.ofNullable(request);
39 | return optionalRequest
40 | .map(HttpServletRequest::getRequestURL)
41 | .map(StringBuffer::toString)
42 | .orElse("");
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/AmazonS3AutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import com.amazonaws.services.s3.AmazonS3;
4 | import com.amazonaws.services.s3.AmazonS3ClientBuilder;
5 | import com.jpomykala.springhoc.AmazonProperties;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.autoconfigure.AutoConfigureAfter;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | @Configuration
13 | @AutoConfigureAfter(AmazonProperties.class)
14 | @EnableConfigurationProperties(AmazonProperties.class)
15 | public class AmazonS3AutoConfiguration {
16 |
17 | private final AmazonProperties properties;
18 |
19 | @Autowired
20 | public AmazonS3AutoConfiguration(AmazonProperties properties) {
21 | this.properties = properties;
22 | }
23 |
24 | @Bean
25 | public AmazonS3 amazonS3() {
26 |
27 | if (properties == null) {
28 | throw new IllegalArgumentException("properties are null");
29 | }
30 |
31 | if (properties.getAccessKey() == null) {
32 | throw new IllegalArgumentException("access key is null");
33 | }
34 |
35 | return AmazonS3ClientBuilder.standard()
36 | .withCredentials(properties.getAWSCredentials())
37 | .withRegion(properties.getRegions())
38 | .build();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/recaptcha/google/GoogleReCaptchaResponse.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.recaptcha.google;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | import java.util.List;
6 |
7 | public class GoogleReCaptchaResponse
8 | {
9 | public GoogleReCaptchaResponse(boolean success, String challengeTs, String hostname, List errorCodes) {
10 | this.success = success;
11 | this.challengeTs = challengeTs;
12 | this.hostname = hostname;
13 | this.errorCodes = errorCodes;
14 | }
15 |
16 | private boolean success;
17 |
18 | @JsonProperty("challenge_ts")
19 | private String challengeTs;
20 |
21 | private String hostname;
22 |
23 | @JsonProperty("error-codes")
24 | private List errorCodes;
25 |
26 |
27 | public boolean isSuccess() {
28 | return success;
29 | }
30 |
31 | public void setSuccess(boolean success) {
32 | this.success = success;
33 | }
34 |
35 | public String getChallengeTs() {
36 | return challengeTs;
37 | }
38 |
39 | public void setChallengeTs(String challengeTs) {
40 | this.challengeTs = challengeTs;
41 | }
42 |
43 | public String getHostname() {
44 | return hostname;
45 | }
46 |
47 | public void setHostname(String hostname) {
48 | this.hostname = hostname;
49 | }
50 |
51 | public List getErrorCodes() {
52 | return errorCodes;
53 | }
54 |
55 | public void setErrorCodes(List errorCodes) {
56 | this.errorCodes = errorCodes;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/mail/MailServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.mockito.InjectMocks;
8 | import org.mockito.Mock;
9 | import org.mockito.Mockito;
10 | import org.mockito.junit.MockitoJUnitRunner;
11 |
12 | @RunWith(MockitoJUnitRunner.class)
13 | public class MailServiceTest {
14 |
15 | @InjectMocks
16 | private MailService mailService;
17 |
18 | @Mock
19 | private AmazonSimpleEmailService amazonSimpleEmailService;
20 |
21 |
22 | @Before
23 | public void setUp() throws Exception {
24 | }
25 |
26 | @Test
27 | public void shouldOnEmailMessageRequest() throws Exception {
28 | //given
29 | EmailRequest emailRequest = EmailRequest.builder()
30 | .to("jakub.pomykala@gmail.com")
31 | .subject("Hello")
32 | .body("Message")
33 | .build();
34 |
35 | //when
36 | mailService.onEmailMessageRequest(emailRequest);
37 |
38 | //then
39 | Mockito.verify(amazonSimpleEmailService).sendEmail(Mockito.any());
40 | }
41 |
42 | @Test(expected = NullPointerException.class)
43 | public void shouldThrowOnNullRequest() throws Exception {
44 | //given
45 | EmailRequest emailRequest = null;
46 |
47 | //when
48 | mailService.onEmailMessageRequest(emailRequest);
49 |
50 | //then
51 | Mockito.verifyNoInteractions(amazonSimpleEmailService);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/recaptcha/google/GoogleReCaptchaService.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.recaptcha.google;
2 |
3 | import com.jpomykala.springhoc.recaptcha.ReCaptchaService;
4 | import org.springframework.http.ResponseEntity;
5 | import org.springframework.web.client.RestTemplate;
6 |
7 | import java.util.Optional;
8 |
9 | public class GoogleReCaptchaService implements ReCaptchaService {
10 | private static final String SITE_VERIFY = "https://www.google.com/recaptcha/api/siteverify";
11 |
12 | private final RestTemplate restTemplate;
13 |
14 | private final GoogleReCaptchaConfigurationProperties properties;
15 |
16 | public GoogleReCaptchaService(RestTemplate restTemplate, GoogleReCaptchaConfigurationProperties properties) {
17 | this.restTemplate = restTemplate;
18 | this.properties = properties;
19 | }
20 |
21 | public ResponseEntity sendReCaptchaRequest(String reCaptchaToken) {
22 | String reCaptchaSecret = properties.getSecret();
23 | return restTemplate.postForEntity(SITE_VERIFY + "?secret=" + reCaptchaSecret + "&response=" + reCaptchaToken, null, GoogleReCaptchaResponse.class);
24 |
25 | }
26 |
27 | public boolean isValid(String reCaptchaToken) {
28 | ResponseEntity reCaptchaResponse = sendReCaptchaRequest(reCaptchaToken);
29 | GoogleReCaptchaResponse body = reCaptchaResponse.getBody();
30 | return Optional.ofNullable(body).map(GoogleReCaptchaResponse::isSuccess).orElse(false);
31 |
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/AmazonProperties.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc;
2 |
3 | import com.amazonaws.auth.AWSStaticCredentialsProvider;
4 | import com.amazonaws.auth.BasicAWSCredentials;
5 | import com.amazonaws.regions.Regions;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 |
8 | import jakarta.validation.constraints.NotNull;
9 |
10 | @ConfigurationProperties(prefix = "spring-hoc.aws")
11 | public class AmazonProperties {
12 |
13 | @NotNull
14 | private String accessKey;
15 |
16 | @NotNull
17 | private String secretKey;
18 |
19 | @NotNull
20 | private String region;
21 |
22 | public AmazonProperties setAccessKey(String accessKey) {
23 | this.accessKey = accessKey;
24 | return this;
25 | }
26 |
27 | public AmazonProperties setSecretKey(String secretKey) {
28 | this.secretKey = secretKey;
29 | return this;
30 | }
31 |
32 | public AmazonProperties setRegion(String region) {
33 | this.region = region;
34 | return this;
35 | }
36 |
37 | public String getAccessKey() {
38 | return accessKey;
39 | }
40 |
41 | public String getSecretKey() {
42 | return secretKey;
43 | }
44 |
45 | public String getRegion() {
46 | return region;
47 | }
48 |
49 | public Regions getRegions() {
50 | return Regions.fromName(region);
51 | }
52 |
53 | public AWSStaticCredentialsProvider getAWSCredentials() {
54 | final BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
55 | return new AWSStaticCredentialsProvider(credentials);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/UploadRequest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import com.amazonaws.services.s3.model.ObjectMetadata;
4 |
5 | import java.util.Arrays;
6 | import java.util.Objects;
7 |
8 | public class UploadRequest {
9 |
10 | private byte[] bytes;
11 | private String filePath;
12 | private ObjectMetadata metadata;
13 |
14 | public UploadRequest(byte[] bytes, String filePath, ObjectMetadata metadata) {
15 | this.bytes = bytes;
16 | this.filePath = filePath;
17 | this.metadata = metadata;
18 | }
19 |
20 | public byte[] getBytes() {
21 | return bytes;
22 | }
23 |
24 | public void setBytes(byte[] bytes) {
25 | this.bytes = bytes;
26 | }
27 |
28 | public String getFilePath() {
29 | return filePath;
30 | }
31 |
32 | public void setFilePath(String filePath) {
33 | this.filePath = filePath;
34 | }
35 |
36 | public ObjectMetadata getMetadata() {
37 | return metadata;
38 | }
39 |
40 | public void setMetadata(ObjectMetadata metadata) {
41 | this.metadata = metadata;
42 | }
43 |
44 | @Override
45 | public boolean equals(Object o) {
46 | if (this == o) return true;
47 | if (!(o instanceof UploadRequest)) return false;
48 | UploadRequest that = (UploadRequest) o;
49 | return Arrays.equals(bytes, that.bytes) &&
50 | Objects.equals(filePath, that.filePath) &&
51 | Objects.equals(metadata, that.metadata);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | int result = Objects.hash(filePath, metadata);
57 | result = 31 * result + Arrays.hashCode(bytes);
58 | return result;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/LoggingFilter.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import org.slf4j.MDC;
4 | import org.springframework.web.filter.OncePerRequestFilter;
5 |
6 | import jakarta.servlet.FilterChain;
7 | import jakarta.servlet.ServletException;
8 | import jakarta.servlet.http.HttpServletRequest;
9 | import jakarta.servlet.http.HttpServletResponse;
10 | import java.io.IOException;
11 |
12 | public class LoggingFilter extends OncePerRequestFilter {
13 |
14 | private final String mdcKey;
15 | private final String mdcLogFormat;
16 | private PrincipalProvider principalProvider;
17 | private RequestIdProvider requestIdProvider;
18 |
19 | public LoggingFilter(String mdcKey, String mdcLogFormat) {
20 | this.mdcKey = mdcKey;
21 | this.mdcLogFormat = mdcLogFormat;
22 | }
23 |
24 | void setPrincipalProvider(PrincipalProvider principalProvider) {
25 | this.principalProvider = principalProvider;
26 | }
27 |
28 | void setRequestIdProvider(RequestIdProvider requestIdProvider) {
29 | this.requestIdProvider = requestIdProvider;
30 | }
31 |
32 | @Override
33 | protected void doFilterInternal(
34 | HttpServletRequest request,
35 | HttpServletResponse response,
36 | FilterChain filterChain) throws ServletException, IOException {
37 | try {
38 | String principal = principalProvider.getPrincipal(request);
39 | String requestId = requestIdProvider.getRequestId(request);
40 | String mdcData = String.format(mdcLogFormat, principal, requestId);
41 | MDC.put(mdcKey, mdcData);
42 | filterChain.doFilter(request, response);
43 | } finally {
44 | MDC.clear();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/maven-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created
2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path
3 |
4 | name: Maven Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 | permissions:
15 | contents: read
16 | packages: write
17 |
18 | steps:
19 | - uses: actions/checkout@v3
20 | - name: Set up JDK 17
21 | uses: actions/setup-java@v3
22 | with:
23 | java-version: '17'
24 | distribution: 'temurin'
25 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
26 | settings-path: ${{ github.workspace }} # location for the settings.xml file
27 | # - name: Publish to GitHub Packages Apache Maven
28 | # run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml
29 | # env:
30 | # GITHUB_TOKEN: ${{ github.token }}
31 | - name: Build with Maven
32 | run: mvn package --file pom.xml
33 | - name: Nexus Repo Publish
34 | uses: sonatype-nexus-community/nexus-repo-github-action@master
35 | with:
36 | serverUrl: https://oss.sonatype.org/
37 | username: ${{ secrets.OSSRH_USERNAME }}
38 | password: ${{ secrets.OSSRH_PASSWORD }}
39 | format: maven2
40 | repository: maven-releases
41 | coordinates: groupId=com.jpomykala artifactId=spring-higher-order-components version=2.0.0-SNAPSHOT
42 | assets: extension=jar
43 | filename: ./target/spring-higher-order-components-2.0.0-SNAPSHOT.jar
44 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/logging/LoggingFilterFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import org.assertj.core.api.Assertions;
4 | import org.junit.Test;
5 | import org.springframework.mock.web.MockFilterChain;
6 | import org.springframework.mock.web.MockHttpServletRequest;
7 | import org.springframework.mock.web.MockHttpServletResponse;
8 |
9 | public class LoggingFilterFactoryTest {
10 |
11 | @Test
12 | public void shouldCreateFilterFromFactoryWithCustomProviders() throws Exception {
13 | //given
14 | LoggingFilterFactory loggingFilterFactory = new LoggingFilterFactory();
15 |
16 | //when
17 | LoggingFilter filter = loggingFilterFactory
18 | .withPrincipalProvider(request -> "")
19 | .withRequestIdProvider(request -> "")
20 | .withCustomMdc("user", "[u:%s][rid:%s]")
21 | .createFilter();
22 |
23 |
24 | //then
25 | Assertions.assertThat(filter).isNotNull();
26 | }
27 |
28 | @Test
29 | public void shouldCreateFilterFromFactoryWithDefaultProviders() throws Exception {
30 | //given
31 | LoggingFilterFactory loggingFilterFactory = new LoggingFilterFactory();
32 |
33 | //when
34 | LoggingFilter filter = loggingFilterFactory.createFilter();
35 |
36 |
37 | //then
38 | Assertions.assertThat(filter).isNotNull();
39 | }
40 |
41 | @Test
42 | public void shouldDoFilterWhenDefaultProviders() throws Exception {
43 | //given
44 | LoggingFilterFactory loggingFilterFactory = new LoggingFilterFactory();
45 | LoggingFilter filter = loggingFilterFactory.createFilter();
46 |
47 | //when
48 | filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain());
49 |
50 | //then
51 | }
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/mail/AmazonSesAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
4 | import com.jpomykala.springhoc.AmazonProperties;
5 | import org.junit.Test;
6 | import org.springframework.boot.autoconfigure.AutoConfigurations;
7 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
8 |
9 | import static org.assertj.core.api.Java6Assertions.assertThat;
10 |
11 | public class AmazonSesAutoConfigurationTest {
12 |
13 | private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
14 | .withPropertyValues(
15 | "spring-hoc.aws.access-key=xxx",
16 | "spring-hoc.aws.secret-key=xxx",
17 | "spring-hoc.aws.region=eu-west-1"
18 | )
19 | .withConfiguration(AutoConfigurations.of(AmazonSesAutoConfigurationTest.ExampleAnnotationConfiguration.class));
20 |
21 |
22 | @Test
23 | public void shouldConfigureServiceAndAmazonServices() {
24 | this.contextRunner.run(context -> {
25 | AmazonSimpleEmailService responseWrapper = context.getBean(AmazonSimpleEmailService.class);
26 | assertThat(responseWrapper).isNotNull();
27 |
28 | AmazonProperties amazonProperties = context.getBean(AmazonProperties.class);
29 | assertThat(amazonProperties).isNotNull();
30 | assertThat(amazonProperties.getAccessKey()).isNotEmpty();
31 | assertThat(amazonProperties.getSecretKey()).isNotEmpty();
32 | assertThat(amazonProperties.getRegion()).isNotEmpty();
33 | assertThat(amazonProperties.getAWSCredentials()).isNotNull();
34 |
35 | });
36 | }
37 |
38 | @EnableEmailSending
39 | public static class ExampleAnnotationConfiguration {
40 |
41 | }
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/wrapper/PageDetails.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.springframework.data.domain.Page;
4 |
5 | public final class PageDetails {
6 |
7 | private long totalElements;
8 | private int totalPages;
9 | private int currentPage;
10 | private boolean hasNext;
11 | private boolean isFirst;
12 | private boolean isLast;
13 |
14 | public PageDetails() {
15 | this.totalElements = 0L;
16 | this.totalPages = 0;
17 | this.currentPage = 0;
18 | this.hasNext = false;
19 | this.isFirst = true;
20 | this.isLast = true;
21 | }
22 |
23 | private PageDetails(long totalElements, int totalPages, int currentPage, boolean hasNext, boolean isFirst, boolean isLast) {
24 | this.totalElements = totalElements;
25 | this.totalPages = totalPages;
26 | this.currentPage = currentPage;
27 | this.hasNext = hasNext;
28 | this.isFirst = isFirst;
29 | this.isLast = isLast;
30 | }
31 |
32 | public static PageDetails of(Page page) {
33 | return new PageDetails(
34 | page.getTotalElements(),
35 | page.getTotalPages(),
36 | page.getNumber(),
37 | page.hasNext(),
38 | page.isFirst(),
39 | page.isLast());
40 | }
41 |
42 | public static PageDetails empty() {
43 | return new PageDetails(0L, 0, 0, false, true, true);
44 | }
45 |
46 | public long getTotalElements() {
47 | return totalElements;
48 | }
49 |
50 | public int getTotalPages() {
51 | return totalPages;
52 | }
53 |
54 | public int getCurrentPage() {
55 | return currentPage;
56 | }
57 |
58 | public boolean getHasNext() {
59 | return hasNext;
60 | }
61 |
62 | public boolean getFirst() {
63 | return isFirst;
64 | }
65 |
66 | public boolean getLast() {
67 | return isLast;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/logging/LoggingFilterFactory.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.logging;
2 |
3 | import com.jpomykala.springhoc.utils.RequestUtils;
4 | import org.springframework.lang.NonNull;
5 |
6 | import jakarta.servlet.http.HttpServletRequest;
7 | import jakarta.validation.constraints.NotNull;
8 | import java.util.UUID;
9 |
10 | public class LoggingFilterFactory {
11 |
12 | private RequestIdProvider requestIdProvider = new DefaultRequestIdProvider();
13 | private PrincipalProvider principalProvider = new DefaultPrincipalProvider();
14 |
15 | private String mdcKey = "user";
16 | private String logFormat = "[u:%s][rid:%s]";
17 |
18 | @NotNull
19 | public LoggingFilterFactory withCustomMdc(@NotNull String mdcKey, @NotNull String logFormat) {
20 | this.mdcKey = mdcKey;
21 | this.logFormat = logFormat;
22 | return this;
23 | }
24 |
25 | @NotNull
26 | public LoggingFilterFactory withRequestIdProvider(@NonNull RequestIdProvider requestIdProvider) {
27 | this.requestIdProvider = requestIdProvider;
28 | return this;
29 | }
30 |
31 | @NotNull
32 | public LoggingFilterFactory withPrincipalProvider(@NotNull PrincipalProvider principalProvider) {
33 | this.principalProvider = principalProvider;
34 | return this;
35 | }
36 |
37 | @NotNull
38 | public LoggingFilter createFilter() {
39 | LoggingFilter loggingFilter = new LoggingFilter(mdcKey, logFormat);
40 | loggingFilter.setPrincipalProvider(principalProvider);
41 | loggingFilter.setRequestIdProvider(requestIdProvider);
42 | return loggingFilter;
43 | }
44 |
45 |
46 | private static class DefaultRequestIdProvider implements RequestIdProvider {
47 | @Override
48 | public String getRequestId(HttpServletRequest request) {
49 | return UUID.randomUUID().toString().toUpperCase().replace("-", "");
50 | }
51 | }
52 |
53 | private static class DefaultPrincipalProvider implements PrincipalProvider {
54 | @Override
55 | public String getPrincipal(HttpServletRequest request) {
56 | return RequestUtils.getClientIP(request);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/s3/UploadServiceAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import com.amazonaws.services.s3.AmazonS3;
4 | import com.jpomykala.springhoc.AmazonProperties;
5 | import org.junit.Test;
6 | import org.springframework.boot.autoconfigure.AutoConfigurations;
7 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
8 |
9 | import static org.assertj.core.api.Java6Assertions.assertThat;
10 |
11 | public class UploadServiceAutoConfigurationTest {
12 |
13 | private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
14 | .withPropertyValues(
15 | "spring-hoc.aws.access-key=xxx",
16 | "spring-hoc.aws.secret-key=xxx",
17 | "spring-hoc.aws.region=eu-west-1",
18 | "spring-hoc.s3.bucket-name=my-bucket"
19 | )
20 | .withConfiguration(AutoConfigurations.of(ExampleAnnotationConfiguration.class));
21 |
22 |
23 | @Test
24 | public void shouldConfigureServiceAndAmazonServices() {
25 | this.contextRunner.run(context -> {
26 | UploadServiceAutoConfiguration responseWrapper = context.getBean(UploadServiceAutoConfiguration.class);
27 | assertThat(responseWrapper).isNotNull();
28 |
29 | AmazonS3Properties amazonS3Properties = context.getBean(AmazonS3Properties.class);
30 | assertThat(amazonS3Properties).isNotNull();
31 | assertThat(amazonS3Properties.getBucketName()).isNotBlank();
32 |
33 | AmazonS3 amazonS3 = context.getBean(AmazonS3.class);
34 | assertThat(amazonS3).isNotNull();
35 |
36 | AmazonProperties amazonProperties = context.getBean(AmazonProperties.class);
37 | assertThat(amazonProperties).isNotNull();
38 | assertThat(amazonProperties.getAccessKey()).isNotEmpty();
39 | assertThat(amazonProperties.getSecretKey()).isNotEmpty();
40 | assertThat(amazonProperties.getRegion()).isNotEmpty();
41 | assertThat(amazonProperties.getAWSCredentials()).isNotNull();
42 |
43 | });
44 | }
45 |
46 | @EnableFileUploading
47 | public static class ExampleAnnotationConfiguration {
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/wrapper/ResponseWrapper.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.springframework.core.MethodParameter;
4 | import org.springframework.data.domain.Page;
5 | import org.springframework.http.MediaType;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.http.server.ServerHttpRequest;
8 | import org.springframework.http.server.ServerHttpResponse;
9 | import org.springframework.web.bind.annotation.ControllerAdvice;
10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
11 |
12 | import java.lang.annotation.Annotation;
13 | import java.lang.reflect.Type;
14 | import java.util.List;
15 |
16 | @ControllerAdvice
17 | public class ResponseWrapper implements ResponseBodyAdvice {
18 |
19 | @Override
20 | public boolean supports(MethodParameter methodParameter, Class converterType) {
21 | Annotation[] methodAnnotations = methodParameter.getMethodAnnotations();
22 |
23 | Type genericParameterType = methodParameter.getGenericParameterType();
24 | String typeName = genericParameterType.getTypeName();
25 |
26 | if (typeName.contains(ResponseEntity.class.getTypeName())) {
27 | return false;
28 | }
29 |
30 | if (typeName.contains(RestResponse.class.getTypeName())) {
31 | return false;
32 | }
33 |
34 | for (Annotation methodAnnotation : methodAnnotations) {
35 | if (methodAnnotation.annotationType().equals(DisableWrapping.class)) {
36 | return false;
37 | }
38 | }
39 | return true;
40 | }
41 |
42 | @Override
43 | public Object beforeBodyWrite(
44 | Object body,
45 | MethodParameter returnType,
46 | MediaType selectedContentType,
47 | Class selectedConverterType,
48 | ServerHttpRequest request,
49 | ServerHttpResponse response) {
50 |
51 | if (body instanceof Page) {
52 | Page pageBody = (Page) body;
53 | List data = pageBody.getContent();
54 | PageDetails pageDetails = PageDetails.of(pageBody);
55 | return RestResponse.ok(data, pageDetails);
56 | } else {
57 | return RestResponse.ok(body);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/cors/CorsDefaultConfiguredIT.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.apache.http.HttpResponse;
4 | import org.apache.http.client.HttpClient;
5 | import org.apache.http.client.methods.HttpOptions;
6 | import org.apache.http.impl.client.HttpClientBuilder;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.boot.test.web.server.LocalServerPort;
12 | import org.springframework.test.context.junit4.SpringRunner;
13 |
14 | @RunWith(SpringRunner.class)
15 | @SpringBootTest(classes = { TestCorsApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
16 | public class CorsDefaultConfiguredIT
17 | {
18 | @LocalServerPort
19 | private int serverPort;
20 |
21 | private final HttpClient client = HttpClientBuilder.create().build();
22 |
23 | @Test
24 | public void shouldSuccessPreflightWhenDefault() throws Exception
25 | {
26 | //given
27 | String url = "http://localhost:" + serverPort + "/echo";
28 | HttpOptions request = new HttpOptions(url);
29 | request.addHeader("Access-Control-Request-Method", "GET");
30 | request.addHeader("Origin", "http://google.com");
31 |
32 | //when
33 | HttpResponse response = client.execute(request);
34 |
35 | //then
36 | int statusCode = response.getStatusLine().getStatusCode();
37 | Assertions.assertThat(statusCode).isEqualTo(200);
38 | }
39 |
40 | @Test
41 | public void shouldSuccessPreflightWhenAnyOrigin() throws Exception
42 | {
43 | //given
44 | String url = "http://localhost:" + serverPort + "/echo";
45 | HttpOptions request = new HttpOptions(url);
46 | request.addHeader("Access-Control-Request-Method", "GET");
47 | request.addHeader("Origin", "http://google.com");
48 |
49 | //when
50 | HttpResponse response = client.execute(request);
51 |
52 | //then
53 | int statusCode = response.getStatusLine().getStatusCode();
54 | Assertions.assertThat(statusCode).isEqualTo(200);
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/s3/model/UploadRequestTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3.model;
2 |
3 | import com.amazonaws.services.s3.model.ObjectMetadata;
4 | import com.jpomykala.springhoc.s3.TestByteUtility;
5 | import com.jpomykala.springhoc.s3.UploadRequest;
6 | import org.assertj.core.api.Assertions;
7 | import org.junit.Test;
8 |
9 | public class UploadRequestTest {
10 |
11 | @Test
12 | public void shouldEqualsWhenSameArguments() throws Exception {
13 | //given
14 | byte[] bytes = TestByteUtility.generateRandomByteStream();
15 | ObjectMetadata metadata = new ObjectMetadata();
16 | metadata.setContentType("pdf");
17 |
18 | UploadRequest uploadRequest1 = new UploadRequest(bytes, "path", metadata);
19 | UploadRequest uploadRequest2 = new UploadRequest(bytes, "path", metadata);
20 |
21 | //when
22 | boolean result = uploadRequest1.equals(uploadRequest2);
23 |
24 | //then
25 | Assertions.assertThat(result).isTrue();
26 | }
27 |
28 | @Test
29 | public void shouldNotEqualsWhenDifferentBytes() throws Exception {
30 | //given
31 | byte[] bytes1 = TestByteUtility.generateRandomByteStream(500);
32 | byte[] bytes2 = TestByteUtility.generateRandomByteStream(600);
33 | ObjectMetadata metadata = new ObjectMetadata();
34 | metadata.setContentType("pdf");
35 |
36 | UploadRequest uploadRequest1 = new UploadRequest(bytes1, "path", metadata);
37 | UploadRequest uploadRequest2 = new UploadRequest(bytes2, "path", metadata);
38 |
39 | //when
40 | boolean result = uploadRequest1.equals(uploadRequest2);
41 |
42 | //then
43 | Assertions.assertThat(result).isFalse();
44 | }
45 |
46 | @Test
47 | public void shouldHashCode() throws Exception {
48 | //given
49 | byte[] bytes = TestByteUtility.generateRandomByteStream();
50 | ObjectMetadata metadata = new ObjectMetadata();
51 | metadata.setContentType("pdf");
52 |
53 | UploadRequest uploadRequest1 = new UploadRequest(bytes, "path", metadata);
54 | UploadRequest uploadRequest2 = new UploadRequest(bytes, "path", metadata);
55 |
56 | //when
57 | int hashCode1 = uploadRequest1.hashCode();
58 | int hashCode2 = uploadRequest2.hashCode();
59 |
60 | //then
61 | Assertions.assertThat(hashCode1).isEqualTo(hashCode2);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/wrapper/RestResponse.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.springframework.http.HttpStatus;
4 |
5 | public final class RestResponse {
6 |
7 | private final String msg;
8 | private final int status;
9 | private final ConcreteResponseData data;
10 | private final PageDetails pageDetails;
11 |
12 | public String getMsg() {
13 | return msg;
14 | }
15 |
16 | public int getStatus() {
17 | return status;
18 | }
19 |
20 | public ConcreteResponseData getData() {
21 | return data;
22 | }
23 |
24 | public PageDetails getPageDetails() {
25 | return pageDetails;
26 | }
27 |
28 |
29 | public RestResponse() {
30 | this.msg = "Internal server error";
31 | this.status = 500;
32 | this.data = null;
33 | this.pageDetails = null;
34 | }
35 |
36 | public RestResponse(String msg, int status, ConcreteResponseData data, PageDetails pageDetails) {
37 | this.msg = msg;
38 | this.status = status;
39 | this.data = data;
40 | this.pageDetails = pageDetails;
41 | }
42 |
43 | private RestResponse(String message, HttpStatus status, ConcreteResponseData data, PageDetails pageDetails) {
44 | this.msg = message;
45 | this.status = status.value();
46 | this.data = data;
47 | this.pageDetails = pageDetails;
48 | }
49 |
50 | @SuppressWarnings(value = {"unchecked", "unused"})
51 | public static RestResponse ok(Object body) {
52 | return new RestResponse("OK", HttpStatus.OK, body, null);
53 | }
54 |
55 | @SuppressWarnings(value = {"unchecked", "unused"})
56 | public static RestResponse ok(Object body, PageDetails pageDetails) {
57 | return new RestResponse("OK", HttpStatus.OK, body, pageDetails);
58 | }
59 |
60 | @SuppressWarnings(value = {"unchecked", "unused"})
61 | public static RestResponse empty(String message, HttpStatus status) {
62 | return new RestResponse(message, status, null, null);
63 | }
64 |
65 | @SuppressWarnings(value = {"unchecked", "unused"})
66 | public static RestResponse of(String message, HttpStatus status, T data) {
67 | return new RestResponse(message, status, data, null);
68 | }
69 |
70 | @SuppressWarnings(value = {"unchecked", "unused"})
71 | public static RestResponse of(String message, HttpStatus status, T data, PageDetails pageDetails) {
72 | return new RestResponse(message, status, data, pageDetails);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/wrapper/TestWrapperApplication.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.assertj.core.util.Lists;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
7 | import org.springframework.data.domain.Page;
8 | import org.springframework.data.domain.PageImpl;
9 | import org.springframework.data.domain.Pageable;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.web.bind.annotation.GetMapping;
12 | import org.springframework.web.bind.annotation.RestController;
13 |
14 | import java.util.List;
15 |
16 | @SpringBootApplication(scanBasePackages = {"com.jpomykala.springhoc.wrapper"})
17 | @RestController
18 | public class TestWrapperApplication extends SpringBootServletInitializer {
19 | public static void main(String[] args) {
20 | SpringApplication.run(TestWrapperApplication.class, args);
21 | }
22 |
23 | @GetMapping("/echo")
24 | public MyPojo echo() {
25 | return new MyPojo()
26 | .setFirstName("Jakub")
27 | .setLastName("Pomykała");
28 | }
29 |
30 | @GetMapping("/paged-echo")
31 | public Page pageEcho() {
32 | MyPojo jakub = new MyPojo()
33 | .setFirstName("Jakub")
34 | .setLastName("Pomykała");
35 |
36 | MyPojo nieMaJajek = new MyPojo()
37 | .setFirstName("Michał")
38 | .setLastName("Białek");
39 |
40 | List pojoList = Lists.newArrayList(jakub, nieMaJajek);
41 | int totalElements = pojoList.size();
42 | return new PageImpl<>(pojoList, Pageable.unpaged(), totalElements);
43 | }
44 |
45 | @GetMapping("/response-entity-echo")
46 | public ResponseEntity responseEntityEcho() {
47 | MyPojo jakub = new MyPojo()
48 | .setFirstName("Jakub")
49 | .setLastName("Pomykała");
50 | return ResponseEntity.ok(jakub);
51 | }
52 |
53 |
54 | public class MyPojo {
55 | private String firstName;
56 | private String lastName;
57 |
58 | public String getFirstName() {
59 | return firstName;
60 | }
61 |
62 | public MyPojo setFirstName(String firstName) {
63 | this.firstName = firstName;
64 | return this;
65 | }
66 |
67 | public String getLastName() {
68 | return lastName;
69 | }
70 |
71 | public MyPojo setLastName(String lastName) {
72 | this.lastName = lastName;
73 | return this;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/cors/CorsConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | @ConfigurationProperties(prefix = "spring-hoc.cors")
9 | public class CorsConfigurationProperties
10 | {
11 |
12 | /**
13 | * Comma-separated list of origins to allow. '*' allows all origins. When not set,
14 | * CORS support is disabled.
15 | */
16 | private List allowedOrigins = new ArrayList();
17 |
18 | /**
19 | * Comma-separated list of methods to allow. '*' allows all methods. When not set,
20 | * defaults to GET.
21 | */
22 | private List allowedMethods = new ArrayList();
23 |
24 | /**
25 | * Comma-separated list of headers to allow in a request. '*' allows all headers.
26 | */
27 | private List allowedHeaders = new ArrayList();
28 |
29 | /**
30 | * Comma-separated list of headers to include in a response.
31 | */
32 | private List exposedHeaders = new ArrayList();
33 |
34 | /**
35 | * Set whether credentials are supported. When not set, credentials are not supported.
36 | */
37 | private Boolean allowCredentials;
38 |
39 | /**
40 | * How long, in seconds, the response from a pre-flight request can be cached by
41 | * clients.
42 | */
43 | private Long maxAge = 1800L;
44 |
45 | public List getAllowedOrigins() {
46 | return this.allowedOrigins;
47 | }
48 |
49 | public void setAllowedOrigins(List allowedOrigins) {
50 | this.allowedOrigins = allowedOrigins;
51 | }
52 |
53 | public List getAllowedMethods() {
54 | return this.allowedMethods;
55 | }
56 |
57 | public void setAllowedMethods(List allowedMethods) {
58 | this.allowedMethods = allowedMethods;
59 | }
60 |
61 | public List getAllowedHeaders() {
62 | return this.allowedHeaders;
63 | }
64 |
65 | public void setAllowedHeaders(List allowedHeaders) {
66 | this.allowedHeaders = allowedHeaders;
67 | }
68 |
69 | public List getExposedHeaders() {
70 | return this.exposedHeaders;
71 | }
72 |
73 | public void setExposedHeaders(List exposedHeaders) {
74 | this.exposedHeaders = exposedHeaders;
75 | }
76 |
77 | public Boolean getAllowCredentials() {
78 | return this.allowCredentials;
79 | }
80 |
81 | public void setAllowCredentials(Boolean allowCredentials) {
82 | this.allowCredentials = allowCredentials;
83 | }
84 |
85 | public Long getMaxAge() {
86 | return this.maxAge;
87 | }
88 |
89 | public void setMaxAge(Long maxAge) {
90 | this.maxAge = maxAge;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/wrapper/ResponseWrappingIT.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import com.amazonaws.util.IOUtils;
4 | import com.jayway.jsonpath.JsonPath;
5 | import org.apache.http.HttpResponse;
6 | import org.apache.http.client.HttpClient;
7 | import org.apache.http.client.methods.HttpGet;
8 | import org.apache.http.impl.client.HttpClientBuilder;
9 | import org.assertj.core.api.Assertions;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.springframework.boot.test.context.SpringBootTest;
13 | import org.springframework.boot.test.web.server.LocalServerPort;
14 | import org.springframework.test.context.junit4.SpringRunner;
15 |
16 | @RunWith(SpringRunner.class)
17 | @SpringBootTest(classes = {TestWrapperApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
18 | public class ResponseWrappingIT {
19 | @LocalServerPort
20 | private int serverPort;
21 |
22 | private HttpClient client = HttpClientBuilder.create().build();
23 |
24 | @Test
25 | public void shouldWrapEntityResponseWhenConfigured() throws Exception {
26 | //given
27 | String url = "http://localhost:" + serverPort + "/echo";
28 | HttpGet request = new HttpGet(url);
29 |
30 | //when
31 | HttpResponse response = client.execute(request);
32 |
33 | //then
34 | String json = IOUtils.toString(response.getEntity().getContent());
35 | String firstName = JsonPath.read(json, "$.data.firstName");
36 | Assertions.assertThat(firstName).isEqualTo("Jakub");
37 |
38 | String lastName = JsonPath.read(json, "$.data.lastName");
39 | Assertions.assertThat(lastName).isEqualTo("Pomykała");
40 |
41 | String message = JsonPath.read(json, "$.msg");
42 | Assertions.assertThat(message).isEqualTo("OK");
43 |
44 | int status = JsonPath.read(json, "$.status");
45 | Assertions.assertThat(status).isEqualTo(200);
46 | }
47 |
48 | @Test
49 | public void shouldWrapPageResponseWhenConfigured() throws Exception {
50 | //given
51 | String url = "http://localhost:" + serverPort + "/paged-echo";
52 | HttpGet request = new HttpGet(url);
53 |
54 | //when
55 | HttpResponse response = client.execute(request);
56 |
57 | //then
58 | String json = IOUtils.toString(response.getEntity().getContent());
59 | String firstName = JsonPath.read(json, "$.data[0].firstName");
60 | Assertions.assertThat(firstName).isEqualTo("Jakub");
61 |
62 | String lastName = JsonPath.read(json, "$.data[0].lastName");
63 | Assertions.assertThat(lastName).isEqualTo("Pomykała");
64 |
65 | String message = JsonPath.read(json, "$.msg");
66 | Assertions.assertThat(message).isEqualTo("OK");
67 |
68 | int status = JsonPath.read(json, "$.status");
69 | Assertions.assertThat(status).isEqualTo(200);
70 |
71 | int totalElements = JsonPath.read(json, "$.pageDetails.totalElements");
72 | Assertions.assertThat(totalElements).isEqualTo(2);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/s3/UploadService.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 |
4 | import com.amazonaws.services.s3.AmazonS3;
5 | import com.amazonaws.services.s3.model.ObjectMetadata;
6 | import jakarta.validation.constraints.NotNull;
7 | import org.springframework.web.multipart.MultipartFile;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.util.Optional;
13 | import java.util.UUID;
14 |
15 | public class UploadService {
16 |
17 | private final AmazonS3Properties properties;
18 | private final AmazonS3 amazonS3;
19 |
20 | public UploadService(
21 | AmazonS3Properties properties,
22 | AmazonS3 amazonS3) {
23 | this.properties = properties;
24 | this.amazonS3 = amazonS3;
25 | }
26 |
27 | public String upload(@NotNull UploadRequest uploadRequest) {
28 | byte[] requestBytes = uploadRequest.getBytes();
29 | String filePath = uploadRequest.getFilePath();
30 | ObjectMetadata metadata = uploadRequest.getMetadata();
31 | return upload(requestBytes, filePath, metadata);
32 | }
33 |
34 | public String upload(@NotNull MultipartFile file) throws IOException {
35 | return upload(file, "");
36 | }
37 |
38 | public String upload(@NotNull MultipartFile file, @NotNull String path) throws IOException {
39 | Optional multipartFile = Optional.of(file);
40 | String originalFilename = multipartFile
41 | .map(MultipartFile::getOriginalFilename)
42 | .orElse("unknown_name");
43 |
44 |
45 | String contentType = multipartFile
46 | .map(MultipartFile::getContentType)
47 | .orElse("");
48 |
49 | ObjectMetadata objectMetadata = new ObjectMetadata();
50 | objectMetadata.setContentType(contentType);
51 |
52 | byte[] bytes = file.getBytes();
53 | return upload(bytes, path + "/" + originalFilename, objectMetadata);
54 | }
55 |
56 | public String upload(byte[] bytes) {
57 | String generatedFilePath = UUID.randomUUID().toString().replace("-", "").toLowerCase();
58 | upload(bytes, generatedFilePath);
59 | return getDownloadUrl(generatedFilePath);
60 | }
61 |
62 | public String upload(byte[] bytes, String fileKey) {
63 | return upload(bytes, fileKey, new ObjectMetadata());
64 | }
65 |
66 | public String upload(byte[] bytes, String fileKey, ObjectMetadata metadata) {
67 | int length = bytes.length;
68 | if (length == 0) {
69 | throw new IllegalArgumentException("File has 0 bytes");
70 | }
71 | InputStream inputStream = new ByteArrayInputStream(bytes);
72 | metadata.setContentLength(length);
73 | String bucketName = properties.getBucketName();
74 | amazonS3.putObject(bucketName, fileKey, inputStream, metadata);
75 | return getDownloadUrl(fileKey);
76 | }
77 |
78 | public String getDownloadUrl(String fileKey) {
79 | return "https://" + properties.getBucketName() + "/" + fileKey;
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/mail/EmailRequest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.mail;
2 |
3 | import com.amazonaws.services.simpleemail.model.Body;
4 | import com.amazonaws.services.simpleemail.model.Content;
5 | import com.amazonaws.services.simpleemail.model.Destination;
6 | import com.amazonaws.services.simpleemail.model.Message;
7 |
8 | import java.util.*;
9 |
10 | public class EmailRequest {
11 |
12 | private final Destination destination;
13 | private final Set replyTo;
14 | private final Message message;
15 | private final String source;
16 |
17 |
18 | public Destination getDestination() {
19 | return destination;
20 | }
21 |
22 | public Set getReplyTo() {
23 | return replyTo;
24 | }
25 |
26 | public Message getMessage() {
27 | return message;
28 | }
29 |
30 | public String getSource() {
31 | return source;
32 | }
33 |
34 | protected EmailRequest(Builder builder) {
35 | this.destination = builder.destination;
36 | this.message = builder.message;
37 | this.source = builder.source;
38 | this.replyTo = new HashSet<>(builder.replyTo);
39 | }
40 |
41 |
42 | public static Builder builder() {
43 | return new Builder();
44 | }
45 |
46 | public static class Builder {
47 |
48 | private final Message message;
49 | private Destination destination;
50 | private String source;
51 | private final Collection replyTo;
52 |
53 | public Builder() {
54 | message = new Message();
55 | replyTo = new HashSet<>();
56 | }
57 |
58 |
59 | public Builder to(String emailAddress) {
60 | return to(Collections.singletonList(emailAddress));
61 | }
62 |
63 | public Builder to(Collection emailAddresses) {
64 | ArrayList toAddresses = new ArrayList<>(emailAddresses);
65 | this.destination = new Destination(toAddresses);
66 | return this;
67 | }
68 |
69 | public Builder from(String from) {
70 | this.source = from;
71 | return this;
72 | }
73 |
74 | public Builder body(String bodyText) {
75 | Content content = new Content().withData(bodyText).withCharset("UTF-8");
76 | Body body = new Body().withHtml(content);
77 | message.setBody(body);
78 | return this;
79 | }
80 |
81 | public Builder body(Body body) {
82 | message.setBody(body);
83 | return this;
84 | }
85 |
86 | public Builder replyTo(String replyTo) {
87 | this.replyTo.add(replyTo);
88 | return this;
89 | }
90 |
91 | public Builder replyTo(Collection replyTo) {
92 | this.replyTo.addAll(replyTo);
93 | return this;
94 | }
95 |
96 | public Builder subject(String subject) {
97 | Content content = new Content(subject);
98 | message.setSubject(content);
99 | return this;
100 | }
101 |
102 | public EmailRequest build() {
103 | return new EmailRequest(this);
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/com/jpomykala/springhoc/cors/CorsAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
5 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.core.Ordered;
9 | import org.springframework.http.HttpHeaders;
10 | import org.springframework.http.HttpMethod;
11 | import org.springframework.web.cors.CorsConfiguration;
12 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
13 | import org.springframework.web.filter.CorsFilter;
14 |
15 | import jakarta.servlet.Filter;
16 | import java.util.Arrays;
17 | import java.util.Collections;
18 | import java.util.List;
19 | import java.util.stream.Collectors;
20 |
21 | @Configuration
22 | @EnableConfigurationProperties(CorsConfigurationProperties.class)
23 | public class CorsAutoConfiguration {
24 |
25 | private final CorsConfigurationProperties properties;
26 |
27 | @Autowired
28 | public CorsAutoConfiguration(CorsConfigurationProperties properties) {
29 | this.properties = properties;
30 | }
31 |
32 | @Bean
33 | public FilterRegistrationBean extends Filter> corsFilter() {
34 | CorsConfiguration configuration = new CorsConfiguration();
35 |
36 | List allowedOrigins = getAllowedOrigins();
37 | configuration.setAllowedOrigins(allowedOrigins);
38 |
39 | List allowedMethods = getAllowedMethods();
40 | configuration.setAllowCredentials(properties.getAllowCredentials());
41 | configuration.setAllowedMethods(allowedMethods);
42 |
43 | List allowedHeaders = getAllowedHeaders();
44 | configuration.setAllowedHeaders(allowedHeaders);
45 |
46 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
47 | source.registerCorsConfiguration("/**", configuration);
48 |
49 | FilterRegistrationBean> bean = new FilterRegistrationBean<>(new CorsFilter(source));
50 | bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
51 | return bean;
52 | }
53 |
54 | private List getAllowedOrigins() {
55 | List allowedOrigins = properties.getAllowedOrigins();
56 | if (!allowedOrigins.isEmpty()) {
57 | return allowedOrigins;
58 | }
59 | return Collections.singletonList("*");
60 | }
61 |
62 | private List getAllowedHeaders() {
63 | List allowedHeaders = properties.getAllowedHeaders();
64 | if (!allowedHeaders.isEmpty()) {
65 | return allowedHeaders;
66 | }
67 | return Arrays.asList(
68 | HttpHeaders.ORIGIN,
69 | HttpHeaders.REFERER,
70 | HttpHeaders.USER_AGENT,
71 | HttpHeaders.CACHE_CONTROL,
72 | HttpHeaders.CONTENT_TYPE,
73 | HttpHeaders.ACCEPT,
74 | HttpHeaders.AUTHORIZATION,
75 | "X-Requested-With",
76 | "X-Forwarded-For",
77 | "x-ijt");
78 | }
79 |
80 | private List getAllowedMethods() {
81 | List allowedMethods = properties.getAllowedMethods();
82 | if (!allowedMethods.isEmpty()) {
83 | return allowedMethods;
84 | }
85 | return Arrays.stream(HttpMethod.values())
86 | .map(HttpMethod::name)
87 | .collect(Collectors.toList());
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/s3/UploadServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.s3;
2 |
3 | import com.amazonaws.services.s3.AmazonS3;
4 | import com.amazonaws.services.s3.model.ObjectMetadata;
5 | import com.amazonaws.services.s3.model.PutObjectResult;
6 | import org.assertj.core.api.Assertions;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.Mockito;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 | import org.springframework.mock.web.MockMultipartFile;
14 |
15 | @RunWith(MockitoJUnitRunner.class)
16 | public class UploadServiceTest {
17 |
18 | @InjectMocks
19 | private UploadService uploadService;
20 |
21 | @Mock
22 | private AmazonS3Properties amazonS3Properties;
23 |
24 | @Mock
25 | private AmazonS3 amazonS3;
26 |
27 | @Test
28 | public void shouldUploadWhenUploadRequest() throws Exception {
29 | //given
30 | byte[] bytes = TestByteUtility.generateRandomByteStream();
31 | ObjectMetadata objectMetadata = new ObjectMetadata();
32 | UploadRequest uploadRequest = new UploadRequest(bytes, "file/doc.txt", objectMetadata);
33 |
34 | Mockito.when(amazonS3.putObject(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(ObjectMetadata.class))).thenReturn(new PutObjectResult());
35 | Mockito.when(amazonS3Properties.getBucketName()).thenReturn("my-test-bucket");
36 |
37 | //when
38 | String uploadUrl = uploadService.upload(uploadRequest);
39 |
40 | //then
41 | Assertions.assertThat(uploadUrl)
42 | .isNotEmpty()
43 | .containsIgnoringCase("my-test-bucket")
44 | .startsWith("https://");
45 | }
46 |
47 | @Test
48 | public void shouldUploadWhenMultipartFile() throws Exception {
49 | //given
50 | byte[] bytes = TestByteUtility.generateRandomByteStream();
51 | MockMultipartFile mockMultipartFile = new MockMultipartFile("file.txt", bytes);
52 |
53 | Mockito.when(amazonS3.putObject(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(ObjectMetadata.class))).thenReturn(new PutObjectResult());
54 | Mockito.when(amazonS3Properties.getBucketName()).thenReturn("my-test-bucket");
55 |
56 | //when
57 | String uploadUrl = uploadService.upload(mockMultipartFile);
58 |
59 | //then
60 | Assertions.assertThat(uploadUrl)
61 | .isNotEmpty()
62 | .containsIgnoringCase("my-test-bucket")
63 | .startsWith("https://");
64 | }
65 |
66 | @Test
67 | public void shouldUploadWhenBytes() throws Exception {
68 | //given
69 | byte[] bytes = TestByteUtility.generateRandomByteStream();
70 |
71 | Mockito.when(amazonS3.putObject(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(ObjectMetadata.class))).thenReturn(new PutObjectResult());
72 | Mockito.when(amazonS3Properties.getBucketName()).thenReturn("my-test-bucket");
73 |
74 | //when
75 | String uploadUrl = uploadService.upload(bytes);
76 |
77 | //then
78 | Assertions.assertThat(uploadUrl)
79 | .isNotEmpty()
80 | .containsIgnoringCase("my-test-bucket")
81 | .startsWith("https://");
82 | }
83 |
84 | @Test(expected = IllegalArgumentException.class)
85 | public void shouldTrowWhen0Bytes() throws Exception {
86 | //given
87 | byte[] bytes = TestByteUtility.generateRandomByteStream(0);
88 |
89 | //when
90 | uploadService.upload(bytes);
91 |
92 | //then
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/utils/RequestUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.utils;
2 |
3 | import org.junit.Test;
4 | import org.springframework.mock.web.MockHttpServletRequest;
5 |
6 | import static org.assertj.core.api.Java6Assertions.assertThat;
7 |
8 |
9 | public class RequestUtilsTest {
10 |
11 | @Test
12 | public void shouldGetClientRemoteAddressWhenExists() {
13 | //given
14 | MockHttpServletRequest mockServerHttpRequest = new MockHttpServletRequest();
15 | mockServerHttpRequest.setServerName("https://example.com");
16 | mockServerHttpRequest.setRequestURI("/example");
17 | mockServerHttpRequest.setLocalAddr("1.1.1.1");
18 | mockServerHttpRequest.setRemoteAddr("2.2.2.2");
19 |
20 | //when
21 | String clientIP = RequestUtils.getClientIP(mockServerHttpRequest);
22 |
23 | //then
24 | assertThat(clientIP).isEqualTo("2.2.2.2");
25 | }
26 |
27 | @Test
28 | public void shouldGetClientXForwardedForWhenExists() {
29 | //given
30 | MockHttpServletRequest mockServerHttpRequest = new MockHttpServletRequest();
31 | mockServerHttpRequest.setServerName("https://example.com");
32 | mockServerHttpRequest.setRequestURI("/example");
33 | mockServerHttpRequest.setLocalAddr("1.1.1.1");
34 | mockServerHttpRequest.setRemoteAddr("2.2.2.2");
35 | mockServerHttpRequest.addHeader("X-Forwarded-For", "3.3.3.3");
36 |
37 | //when
38 | String clientIP = RequestUtils.getClientIP(mockServerHttpRequest);
39 |
40 | //then
41 | assertThat(clientIP).isEqualTo("3.3.3.3");
42 | }
43 |
44 | @Test
45 | public void shouldGetPath() {
46 | //given
47 | MockHttpServletRequest mockServerHttpRequest = new MockHttpServletRequest();
48 | mockServerHttpRequest.setServerName("example.com");
49 | mockServerHttpRequest.setRequestURI("/example");
50 | mockServerHttpRequest.setQueryString("param1=a¶m2=b");
51 |
52 | //when
53 | String path = RequestUtils.getPath(mockServerHttpRequest);
54 |
55 | //then
56 | assertThat(path).isEqualTo("http://example.com/example");
57 | }
58 |
59 | @Test
60 | public void shouldReturnDefaultWhenNoIpFound() {
61 | //given
62 | MockHttpServletRequest mockServerHttpRequest = new MockHttpServletRequest();
63 | mockServerHttpRequest.setServerName("https://example.com");
64 | mockServerHttpRequest.setRequestURI("/example");
65 | mockServerHttpRequest.setLocalAddr("");
66 | mockServerHttpRequest.setRemoteAddr("");
67 | mockServerHttpRequest.addHeader("X-Forwarded-For", "");
68 |
69 | //when
70 | String clientIP = RequestUtils.getClientIP(mockServerHttpRequest);
71 |
72 | //then
73 | assertThat(clientIP).isEqualTo("127.0.0.1");
74 | }
75 |
76 | @Test
77 | public void shouldReturnLocalhostWhenNoIpFound() {
78 | //given
79 | MockHttpServletRequest mockServerHttpRequest = new MockHttpServletRequest();
80 | mockServerHttpRequest.setServerName("https://example.com");
81 | mockServerHttpRequest.setRequestURI("/example");
82 |
83 | //when
84 | String clientIP = RequestUtils.getClientIP(mockServerHttpRequest);
85 |
86 | //then
87 | assertThat(clientIP).isEqualTo("127.0.0.1");
88 | }
89 |
90 | @Test
91 | public void shouldReturnLocalhostWhenNullRequest() {
92 | //given
93 | //when
94 | String clientIP = RequestUtils.getClientIP(null);
95 |
96 | //then
97 | assertThat(clientIP).isEqualTo("127.0.0.1");
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/cors/CorsConfiguredIT.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.cors;
2 |
3 | import org.apache.http.HttpResponse;
4 | import org.apache.http.client.HttpClient;
5 | import org.apache.http.client.methods.HttpOptions;
6 | import org.apache.http.impl.client.HttpClientBuilder;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.boot.test.web.server.LocalServerPort;
12 | import org.springframework.test.context.ActiveProfiles;
13 | import org.springframework.test.context.junit4.SpringRunner;
14 |
15 | @RunWith(SpringRunner.class)
16 | @SpringBootTest(classes = { TestCorsApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
17 | @ActiveProfiles("cors-configured")
18 | public class CorsConfiguredIT
19 | {
20 | @LocalServerPort
21 | private int serverPort;
22 |
23 | private HttpClient client = HttpClientBuilder.create().build();
24 |
25 | @Test
26 | public void shouldSuccessPreflightWhenConfigured() throws Exception
27 | {
28 | //given
29 | String url = "http://localhost:" + serverPort + "/echo";
30 | HttpOptions request = new HttpOptions(url);
31 | request.addHeader("Access-Control-Request-Method", "GET");
32 | request.addHeader("Origin", "http://google.com");
33 |
34 | //when
35 | HttpResponse response = client.execute(request);
36 |
37 | //then
38 | int statusCode = response.getStatusLine().getStatusCode();
39 | Assertions.assertThat(statusCode).isEqualTo(200);
40 | }
41 |
42 | @Test
43 | public void shouldFailPreflightWhenWrongOrigin() throws Exception
44 | {
45 | //given
46 | String url = "http://localhost:" + serverPort + "/echo";
47 | HttpOptions request = new HttpOptions(url);
48 | request.addHeader("Access-Control-Request-Method", "GET");
49 | request.addHeader("Origin", "http://not-allowed-domain.com");
50 |
51 | //when
52 | HttpResponse response = client.execute(request);
53 |
54 | //then
55 | int statusCode = response.getStatusLine().getStatusCode();
56 | Assertions.assertThat(statusCode).isEqualTo(403);
57 | }
58 |
59 | @Test
60 | public void shouldFailPreflightWhenWrongMethod() throws Exception
61 | {
62 | //given
63 | String url = "http://localhost:" + serverPort + "/echo";
64 | HttpOptions request = new HttpOptions(url);
65 | request.addHeader("Access-Control-Request-Method", "PUT");
66 | request.addHeader("Origin", "http://google.com");
67 |
68 | //when
69 | HttpResponse response = client.execute(request);
70 |
71 | //then
72 | int statusCode = response.getStatusLine().getStatusCode();
73 | Assertions.assertThat(statusCode).isEqualTo(403);
74 | }
75 |
76 | @Test
77 | public void shouldSuccessPreflightWhenAllowedHeader() throws Exception
78 | {
79 | //given
80 | String url = "http://localhost:" + serverPort + "/echo";
81 | HttpOptions request = new HttpOptions(url);
82 | request.addHeader("Access-Control-Request-Headers", "Content-Type");
83 |
84 | //when
85 | HttpResponse response = client.execute(request);
86 |
87 | //then
88 | int statusCode = response.getStatusLine().getStatusCode();
89 | Assertions.assertThat(statusCode).isEqualTo(200);
90 | }
91 |
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/src/test/java/com/jpomykala/springhoc/wrapper/ResponseWrappingTests.java:
--------------------------------------------------------------------------------
1 | package com.jpomykala.springhoc.wrapper;
2 |
3 | import org.assertj.core.util.Lists;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
7 | import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
8 | import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
9 | import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
10 | import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
11 | import org.springframework.context.annotation.Configuration;
12 | import org.springframework.context.annotation.Import;
13 | import org.springframework.data.domain.Page;
14 | import org.springframework.data.domain.PageImpl;
15 | import org.springframework.data.domain.PageRequest;
16 | import org.springframework.data.domain.Sort;
17 | import org.springframework.http.MediaType;
18 | import org.springframework.http.ResponseEntity;
19 | import org.springframework.test.web.servlet.MockMvc;
20 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
21 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
22 | import org.springframework.web.bind.annotation.GetMapping;
23 | import org.springframework.web.bind.annotation.RestController;
24 |
25 | import java.lang.annotation.*;
26 | import java.util.List;
27 |
28 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
29 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
30 |
31 | public class ResponseWrappingTests {
32 |
33 | private MockMvc mockMvc;
34 |
35 | @Before
36 | public void setup() {
37 | this.mockMvc = MockMvcBuilders
38 | .standaloneSetup(ExampleController.class)
39 | .setControllerAdvice(ResponseWrapper.class)
40 | .build();
41 | }
42 |
43 | @Test
44 | public void testWrapperWithSamplePojo() throws Exception {
45 | this.mockMvc
46 | .perform(get("/examples/1"))
47 | .andExpect(status().isOk())
48 | .andExpect(content().contentType(MediaType.APPLICATION_JSON))
49 | .andExpect(jsonPath("$.msg").value("OK"))
50 | .andExpect(jsonPath("$.status").value(200))
51 | .andExpect(jsonPath("$.data.name").value("Example"))
52 | .andExpect(jsonPath("$.data.age").value(100))
53 | .andExpect(jsonPath("$.data.pageDetails").doesNotExist())
54 | .andDo(MockMvcResultHandlers.print());
55 | }
56 |
57 | @Test
58 | public void testWrapperWithPageOfSamplePojos() throws Exception {
59 | this.mockMvc
60 | .perform(get("/examples"))
61 | .andExpect(status().isOk())
62 | .andExpect(content().contentType(MediaType.APPLICATION_JSON))
63 | .andExpect(jsonPath("$.msg").value("OK"))
64 | .andExpect(jsonPath("$.status").value(200))
65 | .andExpect(jsonPath("$.data[0].name").value("Example"))
66 | .andExpect(jsonPath("$.data[0].age").value(100))
67 | .andExpect(jsonPath("$.pageDetails.totalElements").value(1))
68 | .andExpect(jsonPath("$.pageDetails.currentPage").value(0))
69 | .andExpect(jsonPath("$.pageDetails.totalPages").value(1))
70 | .andDo(MockMvcResultHandlers.print());
71 | }
72 |
73 | @Test
74 | public void testWrapperWithNotWrappedResponseEntity() throws Exception {
75 | this.mockMvc
76 | .perform(get("/not-wrapped"))
77 | .andExpect(status().isOk())
78 | .andExpect(content().contentType(MediaType.APPLICATION_JSON))
79 | .andExpect(jsonPath("$.name").value("Jakub"))
80 | .andExpect(jsonPath("$.age").value(24))
81 | .andDo(MockMvcResultHandlers.print());
82 | }
83 |
84 |
85 | @Target(ElementType.TYPE)
86 | @Retention(RetentionPolicy.RUNTIME)
87 | @Documented
88 | @Import({ServletWebServerFactoryAutoConfiguration.class,
89 | DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
90 | HttpMessageConvertersAutoConfiguration.class,
91 | PropertyPlaceholderAutoConfiguration.class})
92 | protected @interface MinimalWebConfiguration {
93 | }
94 |
95 | @Configuration
96 | @MinimalWebConfiguration
97 | protected static class TestWebApplicationConfiguration {
98 |
99 | }
100 |
101 | @RestController
102 | protected static class ExampleController {
103 |
104 | private final SamplePojo example = new SamplePojo("Example", 100);
105 | private final List singleElementList = Lists.newArrayList(example);
106 |
107 | @GetMapping("/examples/1")
108 | public SamplePojo examples() {
109 | return singleElementList.get(0);
110 | }
111 |
112 | @GetMapping("/examples")
113 | public Page examplePojo() {
114 | PageRequest pageRequest = PageRequest.of(0, 1, Sort.Direction.ASC, "name");
115 | return new PageImpl(singleElementList, pageRequest, 1L);
116 | }
117 |
118 |
119 | @GetMapping("/not-wrapped")
120 | public ResponseEntity responseEntityEcho() {
121 | SamplePojo jakub = new SamplePojo();
122 | jakub.setName("Jakub");
123 | jakub.setAge(24);
124 | return ResponseEntity.ok(jakub);
125 | }
126 | }
127 |
128 | protected static class SamplePojo {
129 | private String name;
130 | private int age;
131 |
132 | public SamplePojo() {
133 | }
134 |
135 | public SamplePojo(String name, int age) {
136 | this.name = name;
137 | this.age = age;
138 | }
139 |
140 | public String getName() {
141 | return name;
142 | }
143 |
144 | public void setName(String name) {
145 | this.name = name;
146 | }
147 |
148 | public int getAge() {
149 | return age;
150 | }
151 |
152 | public void setAge(int age) {
153 | this.age = age;
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Migwn, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.jpomykala
7 | spring-higher-order-components
8 | 2.0.0-SNAPSHOT
9 | jar
10 |
11 |
12 |
13 | GNU General Public License v3.0
14 | https://www.gnu.org/licenses/gpl-3.0.en.html
15 | repo
16 |
17 |
18 |
19 |
20 | scm:git:git://github.com/jpomykala/spring-higher-order-components.git
21 | scm:git:ssh://github.com:jpomykala/spring-higher-order-components.git
22 | https://github.com/jpomykala/spring-higher-order-components.git/tree/master
23 |
24 |
25 | spring-higher-order-components
26 | High Order Components for Spring Boot
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-parent
31 | 3.0.2
32 |
33 |
34 |
35 |
36 |
37 | jpomykala
38 | Jakub Pomykała
39 | jakub.pomykala@gmail.com
40 | https://jpomykala.com
41 |
42 | Project Lead
43 |
44 | Europe/Warsaw
45 |
46 |
47 |
48 |
49 |
50 | UTF-8
51 | UTF-8
52 | 17
53 |
54 |
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-starter
59 | provided
60 |
61 |
62 |
63 | org.springframework.boot
64 | spring-boot-starter-web
65 | provided
66 |
67 |
68 | org.springframework.data
69 | spring-data-commons
70 | provided
71 |
72 |
73 | org.springframework.boot
74 | spring-boot-configuration-processor
75 | true
76 | provided
77 |
78 |
79 |
80 | jakarta.validation
81 | jakarta.validation-api
82 | 3.0.2
83 | provided
84 |
85 |
86 |
87 | com.amazonaws
88 | aws-java-sdk
89 | provided
90 | 1.12.410
91 |
92 |
93 | org.springframework.boot
94 | spring-boot-starter-test
95 | test
96 |
97 |
98 |
99 | org.jacoco
100 | jacoco-maven-plugin
101 | 0.8.3
102 | test
103 |
104 |
105 |
106 | org.apache.httpcomponents
107 | httpclient
108 | 4.5.7
109 | test
110 |
111 |
112 |
113 |
114 |
115 | ossrh
116 | https://oss.sonatype.org/content/repositories/snapshots
117 |
118 |
119 | ossrh
120 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
121 |
122 |
123 |
124 |
125 |
126 |
127 | true
128 |
129 | test
130 |
131 |
132 |
133 | org.jacoco
134 | jacoco-maven-plugin
135 | 0.8.3
136 |
137 |
138 |
139 | prepare-agent
140 |
141 |
142 |
143 |
144 | report
145 | test
146 |
147 | report
148 |
149 |
150 |
151 |
152 | post-unit-test
153 | test
154 |
155 | report
156 |
157 |
158 |
159 |
160 | target/jacoco.exec
161 |
162 | target/jacoco-ut
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | release
174 |
175 |
176 | release
177 |
178 |
179 |
180 |
181 |
182 | org.sonatype.plugins
183 | nexus-staging-maven-plugin
184 | 1.6.7
185 | true
186 |
187 | ossrh
188 | https://oss.sonatype.org/
189 | true
190 |
191 |
192 |
193 | org.apache.maven.plugins
194 | maven-source-plugin
195 | 3.0.1
196 |
197 |
198 | attach-sources
199 |
200 | jar-no-fork
201 |
202 |
203 |
204 |
205 |
206 | org.apache.maven.plugins
207 | maven-javadoc-plugin
208 | 3.0.1
209 |
210 |
211 | attach-javadocs
212 |
213 | jar
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/jpomykala/spring-higher-order-components)
4 | [](https://maven-badges.herokuapp.com/maven-central/com.jpomykala/spring-higher-order-components)
5 | [](https://codecov.io/gh/jpomykala/spring-higher-order-components)
6 | [](https://codeclimate.com/github/jpomykala/spring-higher-order-components/maintainability)
7 |
8 | Boilerplate components for Spring Boot.
9 | - [x] [E-mail sending with step builder](#enableemailsending-annotation)
10 | - [x] [Request logging](#enablerequestlogging-annotation)
11 | - [x] [Files uploading to Amazon S3](#enablefileuploading-annotation)
12 | - [x] [Response wrapping](#enableresponsewrapping-annotation) (works with Swagger / SpringFox!)
13 | - [x] [Easy to use CORS filter](#enablecors-annotation)
14 |
15 | ## Installation
16 | ```xml
17 |
18 | com.jpomykala
19 | spring-higher-order-components
20 | 1.0.11
21 |
22 | ```
23 |
24 | [Check version in maven repository](https://mvnrepository.com/artifact/com.jpomykala/spring-higher-order-components)
25 |
26 | ## Motivation
27 |
28 | - Write inline code
29 | - Duplicate code a few times in different spots
30 | - Extract duplicate code into methods
31 | - Use your abstractions for a while
32 | - See how that code interacts with other code
33 | - Extract common functionality into internal library
34 | - Use internal library for extended periods of time
35 | - Really understand how all of the pieces come together
36 | - Create external open source library (we are here now)
37 |
38 | source: [https://nickjanetakis.com](https://nickjanetakis.com/blog/microservices-are-something-you-grow-into-not-begin-with)
39 |
40 | ***
41 |
42 | ## `@EnableEmailSending` annotation
43 |
44 | This component gives you simple API to send emails using Amazon SES service. Spring HOC will automatically create for you Amazon SES component if bean doesn't exist.
45 |
46 | ### Configuration
47 |
48 | - Provide **verified** sender email address ``spring-hoc.mail.sender-email-address``
49 | - Provide AWS credentials ``spring-hoc.aws.access-key``, ``spring-hoc.aws.secret-key``, ``spring-hoc.aws.region``
50 |
51 | #### Example `application.yml` configuration for e-mail sending
52 |
53 | ```yml
54 | spring-hoc:
55 | aws:
56 | access-key: xxxxxxxx
57 | secret-key: xxxxxxxx
58 | region: eu-west-1
59 | mail:
60 | sender-email-address: "no-reply@mydomain.com"
61 | ```
62 | This properties are **required**.
63 |
64 | #### Tip
65 | You can put `My Company Name ` in `sender-email-address` property to show "My Company Name" in e-mail apps instead plain e-mail.
66 | Reference: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-concepts-email-format.html
67 |
68 | ### How to send e-mail?
69 |
70 | Use ``EmailRequest`` step builder to create request.
71 |
72 | ```java
73 | EmailRequest.builder()
74 | .to("jpomykala@example.com")
75 | .subject("Hey, I just met you and this is crazy")
76 | .body("But here's my number, so call me maybe")
77 | .build();
78 | ```
79 |
80 | Now it's time to send email. You have 2 options here.
81 | - ``@Autowire MailService`` and invoke ``sendEmail(EmailRequest)``.
82 | - Publish ``EmailRequest`` using ``ApplicationEventPublisher``
83 |
84 | That's all!
85 |
86 | ### Example application with sending email (SES)
87 |
88 | ```java
89 | @SpringBootApplication
90 | @EnableEmailSending
91 | public class MySpringBootApplication {
92 |
93 | public static void main(String[] args) {
94 | SpringApplication.run(MySpringBootApplication.class, args);
95 | }
96 |
97 | // Send e-mail by event publishing, Spring HOC will listen to EmailRequest objects
98 | @Autowired
99 | private ApplicationEventPublisher eventPublisher;
100 |
101 | @GetMapping("/send-email-by-event-publishing")
102 | public void sendEmailByEventPublishing(){
103 | EmailRequest emailRequest = EmailRequest.builder()
104 | .to("jakub.pomykala@gmail.com")
105 | .subject("Hey, I just met you and this is crazy [event publishing]")
106 | .body("But here's my number, so call me maybe")
107 | .build();
108 |
109 | eventPublisher.publishEvent(emailRequest);
110 | }
111 |
112 | // Send e-mail by mail service provided by Spring HOC and @EnableEmailSending annotation
113 | @Autowired
114 | private MailService mailService;
115 |
116 | @GetMapping("/send-email-by-mail-service")
117 | public void sendEmailByMailService(){
118 | EmailRequest emailRequest = EmailRequest.builder()
119 | .to("jakub.pomykala@gmail.com")
120 | .subject("Hey, I just met you and this is crazy [mail service]")
121 | .body("But here's my number, so call me maybe")
122 | .build();
123 |
124 | mailService.send(emailRequest);
125 | }
126 | }
127 | ```
128 |
129 | ***
130 |
131 | ## `@EnableRequestLogging` annotation
132 |
133 | Adds logging requests, populate MDC with:
134 | - user (IP address by default)
135 | - requestId (UUID by default).
136 |
137 | ### Example application with request logging
138 |
139 | ```java
140 | @SpringBootApplication
141 | @EnableRequestLogging
142 | public class MySpringBootApplication {
143 |
144 | public static void main(String[] args) {
145 | SpringApplication.run(MySpringBootApplication.class, args);
146 | }
147 |
148 | @Autowired
149 | private MyUserService userService;
150 |
151 | // [OPTIONAL] customize configuration
152 | @Bean
153 | public LoggingFilter loggingFilter(LoggingFilterFactory loggingFilterFactory) {
154 | return loggingFilterFactory
155 | .withPrincipalProvider(new PrincipalProvider() {
156 | @Override
157 | public String getPrincipal(HttpServletRequest request) {
158 | return userService.findUserName(request);
159 | }
160 | })
161 | .createFilter();
162 | }
163 | }
164 |
165 | ```
166 |
167 |
168 | ### Customization of request logging
169 | ```java
170 | @Bean
171 | public LoggingFilter loggingFilter(LoggingFilterFactory loggingFilterFactory){
172 | return loggingFilterFactory
173 | .withPrincipalProvider() // [optional] PrincipalProvider implementation
174 | .withRequestIdProvider() // [optional] RequestIdProvider implementation
175 | .withCustomMdc("user", "[u:%s][rid:%s]") // [optional] MDC key, String.format()
176 | .createFilter();
177 | }
178 | ```
179 |
180 | ***
181 |
182 | ## `@EnableFileUploading` annotation
183 |
184 | This annotation autoconfigures Amazon S3 component if bean doesn't exit.
185 |
186 | ``@Autowire UploadService`` gives you ability to upload files using overloaded methods:
187 | - ``void upload(@NotNull UploadRequest uploadRequest)``
188 | - ``void upload(@NotNull MultipartFile file)``
189 | - ``void upload(@NotNull MultipartFile file, @NotNull String path)``
190 | - ``void upload(byte[] bytes, String fileKey)``
191 | - ``void upload(byte[] bytes, String fileKey, ObjectMetadata metadata)``
192 | - ``String upload(byte[] bytes)`` // path is autogenerated (sha256 hash)
193 |
194 | ### Example ``application.yml`` configuration for file uploading
195 |
196 | ```
197 | spring-hoc:
198 | aws:
199 | access-key: xxxxxxxx
200 | secret-key: xxxxxxxx
201 | region: eu-west-1
202 | s3:
203 | bucket-name: my-bucket
204 | ```
205 | This properties are **required.***
206 |
207 | ### Example application with files uploading
208 |
209 | ```java
210 | @SpringBootApplication
211 | @EnableFileUploading
212 | public class MySpringBootApplication {
213 |
214 | public static void main(String[] args) {
215 | SpringApplication.run(MySpringBootApplication.class, args);
216 | }
217 |
218 | @Autowired
219 | private UploadService uploadService;
220 |
221 | @GetMapping("/upload-file")
222 | public String uploadFile(@RequestBody MultipartFile multipartFile) throws IOException {
223 | String s3DownloadUrl = uploadService.upload(multipartFile);
224 | return s3DownloadUrl;
225 | }
226 | }
227 | ```
228 |
229 | ***
230 |
231 | ## `@EnableResponseWrapping` annotation
232 |
233 | Every `@RestController` output will be wrapped into `RestResponse` object for JSON it will look like as follows:
234 |
235 | ```
236 | {
237 | msg: "OK"
238 | status: 200
239 | data:
240 | pageDetails:
241 | }
242 | ```
243 |
244 | `RestResponse` static contructors:
245 |
246 | - `RestResponse ok(Object body)`
247 | - `RestResponse ok(Object body, PageDetails pageDetails)`
248 | - `RestResponse empty(String message, HttpStatus status)`
249 | - `RestResponse of(String message, HttpStatus status, Object data)`
250 | - `RestResponse of(String message, HttpStatus status, Object data, PageDetails pageDetails)`
251 |
252 | Every output will be wrapped into `RestResponse` [see this issue](https://github.com/jpomykala/spring-higher-order-components/issues/4)
253 |
254 | Response wrapping can be disabled for specific endpoinds by using `@DisableWrapping` annotation on method.
255 |
256 | ### Example application with response wrapping
257 |
258 | ```java
259 | @SpringBootApplication
260 | @EnableResponseWrapping
261 | public class MySpringBootApplication {
262 |
263 | public static void main(String[] args) {
264 | SpringApplication.run(MySpringBootApplication.class, args);
265 | }
266 |
267 | @GetMapping("/wrap-pojo")
268 | public MyPojo wrapResponse() {
269 | MySpringBootApplication.MyPojo myPojo = new MyPojo("Jakub", "Pomykala");
270 | return myPojo;
271 | }
272 |
273 | @Autowired
274 | private MyPojoRepository myPojoRepository;
275 |
276 | @GetMapping("/wrap-pojo-page")
277 | public Page wrapPageResponse() {
278 | Page myPojos = myPojoRepository.findAll();
279 | return myPojos;
280 | }
281 |
282 | public class MyPojo {
283 | private String firstName;
284 | private String lastName;
285 | // getters and setters
286 | }
287 | }
288 | ```
289 |
290 | ***
291 |
292 | ## `@EnableCORS` annotation
293 |
294 | This annotation adds filter which handles CORS requests.
295 |
296 | ### Example `application.yml` configuration for CORS
297 |
298 | ```yml
299 | spring-hoc:
300 | cors:
301 | allow-credentials: true
302 | allowed-origins:
303 | - "https://my-frontend-application.com"
304 | - "https://jpomykala.com"
305 | allowed-methods:
306 | - GET
307 | - POST
308 | - PATCH
309 | - DELETE
310 | ```
311 | This properties are **optional.**
312 |
313 | By default CORS will accept all origins, all HTTP methods and all popular headers.
314 |
315 | ### Example application with CORS filter
316 |
317 | ```java
318 | @SpringBootApplication
319 | @EnableCORS
320 | public class MySpringBootApplication {
321 |
322 | public static void main(String[] args) {
323 | SpringApplication.run(MySpringBootApplication.class, args);
324 | }
325 |
326 | }
327 | ```
328 |
329 |
330 | # Contribution
331 |
332 | Would you like to add something or improve source? Create new issue, let's discuss it
333 |
334 | - **If in doubt, please discuss your ideas first before providing a pull request. This often helps avoid a lot of unnecessary work. In particular, we might prefer not to prioritise a particular feature for another while.**
335 | - Fork the repository.
336 | - The commit message should reference the issue number.
337 | - Check out and work on your own fork.
338 | - Try to make your commits as atomic as possible. Related changes to three files should be committed in one commit.
339 | - Try not to modify anything unrelated.
340 |
341 | Source: https://github.com/jOOQ/jOOQ
342 |
343 | # More
344 |
345 | - Check out my [website](https://jpomykala.com)
346 |
347 | # License
348 | GNU General Public License v3.0
349 |
--------------------------------------------------------------------------------