27 Ağustos 2021 Cuma

SpringWebFlux FilePart Arayüzü

Giriş
Şu satırı dahil ederiz. WebFlux ile MultiPartFile kullanılamıyor
import org.springframework.http.codec.multipart.FilePart;
transferTo metodu
Örnek
Şöyle yaparız
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;

@RestController
@RequestMapping("upload")
public class UploadController {

  private final Path basePath = Paths.get("./src/main/resources/upload/");

  @PostMapping("file/single")
  public Mono<Void> upload(@RequestPart("user-name") String name,
                           @RequestPart("fileToUpload") Mono<FilePart> filePartMono){
    System.out.println("user : " + name);
    return filePartMono
      .doOnNext(fp -> System.out.println("Received File : " + fp.filename()))
      .flatMap(fp -> fp.transferTo(basePath.resolve(fp.filename())))
      .then();
  }

  @PostMapping("file/multi1")
  public Mono<Void> upload(@RequestPart("files") Flux<FilePart> partFlux){
    return partFlux
      .doOnNext(fp -> System.out.println(fp.filename()))
      .flatMap(fp -> fp.transferTo(basePath.resolve(fp.filename())))
      .then();
  }
}

26 Ağustos 2021 Perşembe

SpringBoot Test Sliced Test Anotasyonları

Test Slices Nedir?
Açıklaması şöyle. @SpringBootTest kullanmaktan çok daha iyi
Whenever your tests go beyond trivial unit testing and you include Spring Test features, you'll most likely start a customized Spring Context (e.g. @SpringBootTest, @WebMvcTest, @DataJpaTest).
Açıklaması şöyle.
Test Slices are a Spring Boot feature introduced in the 1.4. The idea is fairly simple, Spring will create a reduced application context for a specific slice of your app.

Also, the framework will take care of configuring the very minimum.

There are a sensible number of slices available out of the box in Spring Boot and we can create our own too:

- @JsonTest: Registers JSON relevant components
- @DataJpaTest: Registers JPA beans, including the ORM available
- @JdbcTest: Useful for raw JDBC tests, takes care of the data source and in memory DBs without ORM frills
- @DataMongoTest: Tries to provide an in-memory mongo testing setup
- @WebMvcTest: A mock MVC testing slice without the rest of the app

This particular feature if used wisely can help us build narrow tests without such a big penalty in terms of performance particularly for small/medium sized apps.
Bazı anotasyonlar şöyle
@DataLdapTest
@DataMongoTest
@DataNeo4jTest
@DataRedisTest

@JsonTest
@JooqTest


@RestClientTest
@WebFluxTest

SpringData Flyway Sınıfı

Giriş
Şu satırı dahil ederizz
import org.flywaydb.core.Flyway;
Eğer bu sınıfı bir bean olarak kullanacaksak şöyle yaparız. Flyway her zaman JPA 1EntityManagerFactory sınıfından önce çalışmalıdır
@Bean
@DependsOn("flyway")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    ...
}
callbacks metodu
Örnek
Şöyle yaparız
Flyway flyway = Flyway.configure()
  .dataSource(dataSource)
  .locations("db/migration", "db/callbacks")
  .callbacks(new ExampleFlywayCallback())
  .load();
flyway.migrate();
locations metodu
Örnek
Şöyle yaparız. Burada scriptler "src/main/resources/flyway/scripts/postgresql/migration" gibi bir dizindedir
@Bean
public Flyway flyway() {
    Flyway flyway = Flyway.configure()
        .dataSource(dataSource())
        .baselineOnMigrate(true)
        .locations(
            String.format(
                "classpath:/flyway/scripts/postgresql/migration",
                databaseType.name().toLowerCase()
            )
    ).load();
    flyway.migrate();
    return flyway;
}
migrate metodu
Örnek
Şöyle yaparız
public class TestClass {
  @Autowired Flyway flyway;
  @BeforeEach
  public void beforeEach() {
    flyway.clean();
    flyway.migrate();
  }
  @Test
  public void example1() {
    // test example
  }
}

25 Ağustos 2021 Çarşamba

SpringSecurity AbstractAuthenticationToken Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.security.authentication.AbstractAuthencticationToken;
Tüm AuthenticationToken sınıflarının atasıdır. Authentication arayüzünden kalıtır. Açıklaması şöyle
It is a base class for Authentication objects and creates a token with the supplied array of authorities (GrantedAuthority).

It implements Authentication interface and it has a collection of “authorities” you can set for your authenticated account and a “details” object where you can set other info you may use later (like an auth token to set to response header or save in a cache (Redis, etc.) in your custom success handler class).
gibi sınıflara bakılabilir

getCredential metodu
Bir örnek burada

getPrincipal metodu
Bir örnek burada

24 Ağustos 2021 Salı

SpringQuartz Kullanımı Trigger Yaratma

Giriş
Açıklaması şöyle. Yani Trigger'a bir isim ve grup veriliyor
Trigger — A component that determines the schedule upon which a given Job will be performed. Every Trigger is identified by the TriggerKey that has a name and group and the name must be unique within a group. Triggers can also send data to the Job
TriggerBuilder: A builder pattern implementation to create a Trigger instance
Trigger Çeşitleri
Açıklaması şöyle.
There are 2 types of triggers,
Simple Trigger — Used if you need to execute a job exactly once at a specific moment in time, or at a specific moment in time followed by repeats at a specific interval.
Cron Trigger — For scheduling jobs having complex times like if you need to execute a job that recurs based on calendar-like notions
org.quartz.Trigger yaratmak için kendi kodumuzda şu yöntemlerden birisini kullanırız

1. Quartz projesine ait TriggerBuilder kullanılır. 
2. Spring projesine ait SimpleTriggerFactoryBean kullanılır
3. Spring projesine ait CronTriggerFactoryBean kullanılır

TriggerBuilder 
forJob metodu
Örnek
org.quartz.Trigger yaratmak için kendi kodumuzda Quartz projesine ait TriggerBuilder kullanılır. Şöyle yaparız
private Trigger buildJobTrigger(JobDetail jobDetail, ZonedDateTime startAt) {
  return TriggerBuilder.newTrigger()
    .forJob(jobDetail)
    .withIdentity(jobDetail.getKey().getName(), "email-triggers")
    .withDescription("Send Email Trigger")
    .startAt(Date.from(startAt.toInstant()))
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow())
    .build();
}
Spring SimpleTriggerFactoryBean
İşi belirtilen zamanda koşan basit bir SimpleTrigger nesnesi yaratır
Örnek
Şöyle yaparız
Trigger buildTrigger() {
  SimpleTriggerFactoryBean triggerFactory = new SimpleTriggerFactoryBean();

  triggerFactory.setName("SimpleTriggerName");
  triggerFactory.setGroup("Group");
  triggerFactory.setStartTime(localTimeToDate(LocalTime.now().plusHours(2)));
  triggerFactory.setRepeatCount(0);
  triggerFactory.setRepeatInterval(0);

  triggerFactory.afterPropertiesSet();
  return triggerFactory.getObject();
}
Şöyle yaparız
/**
 * Create simple trigger.
 *
 * @param triggerName        Trigger name.
 * @param startTime          Trigger start time.
 * @param repeatTime         Job repeat period mills
 * @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
 * @return {@link SimpleTrigger}
*/
public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime,
Long repeatTime, int misFireInstruction) {
  SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
  factoryBean.setName(triggerName);
  factoryBean.setStartTime(startTime);
  factoryBean.setRepeatInterval(repeatTime);
  factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
  factoryBean.setMisfireInstruction(misFireInstruction);
  factoryBean.afterPropertiesSet();
  return factoryBean.getObject();
}
Spring CronTriggerFactoryBean Sınıfı
Cron gibi çalışan daha karmaşık iş zamanlama yeteneği sağlar. 
Örnek
Şöyle yaparız
Trigger buildCronTrigger() {
  CronTriggerFactoryBean crontriggerFactory = new CronTriggerFactoryBean();
  crontriggerFactory.setName("CronName");
  crontriggerFactory.setGroup("Group");
  crontriggerFactory.setCronExpression("2 * * * * *");
  crontriggerFactory.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);

  try {
    crontriggerFactory.afterPropertiesSet();
   } catch (ParseException e) {
     ...
  }
  return crontriggerFactory.getObject();
}

Örnek
Şöyle yaparız
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SimpleTrigger;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
   
/**
 * Create cron trigger.
 *
 * @param triggerName        Trigger name.
 * @param startTime          Trigger start time.
 * @param cronExpression     Cron expression.
 * @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
 * @return {@link CronTrigger}
*/
public CronTrigger createCronTrigger(String triggerName, Date startTime,
String cronExpression, int misFireInstruction) {
  CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
  factoryBean.setName(triggerName);
  factoryBean.setStartTime(startTime);
  factoryBean.setCronExpression(cronExpression);
  factoryBean.setMisfireInstruction(misFireInstruction);
  try {
    factoryBean.afterPropertiesSet();
  } catch (ParseException e) {
    log.error(e.getMessage(), e);
  }
  return factoryBean.getObject();
}

SpringQuartz Kullanımı JobDetail Yaratma

JobBuilder Sınıfı
org.quartz.JobDetail  yaratmak için kendi kodumuzda Quartz projesine ait JobBuilder kullanılır.

newJob metodu
Burada Job sınıfının sınıf tipini vermek gerekir.
Job iki şekilde kodlanabilir
1. Quartz projesinin Job arayüzünden kalıtılır
2. Spring' ait QuartzJobBean sınıfından  kalıtılır

Örnek
Elimizde şöyle bir Job olsun
public class FileDeletionJob implements Job {
  ...
}
Şöyle yaparız. Burada JobBuilder ile bir JobDetail yaratılıyor. Daha sonra Trigger yaratılıyor. Scheduler.schedule() çağrısına JobDetail ve Trigger geçiliyor.
public static void configureJob(String path, String regex) throws SchedulerException {
  JobDetail job = JobBuilder.newJob(FileDeletionJob.class)
    .usingJobData("regex", regex)
    .usingJobData("path", path)
    .build();

  Trigger trigger = newTrigger().withSchedule(simpleSchedule()
                                .withIntervalInMinutes(1)
     // .withIntervalInHours(1)
     .repeatForever())
     .build();

  Scheduler scheduler = new StdSchedulerFactory().getScheduler();
  scheduler.start();
  scheduler.scheduleJob(job, trigger);
}
usingJobData metodu
Örnek
Şöyle yaparız
JobDetail buildJobDetail(ScheduleReportRequest scheduleReportRequest) {
  JobDataMap jobDataMap = new JobDataMap();

  jobDataMap.put("reportname", scheduleEmailRequest.getEmail());
  jobDataMap.put("type", scheduleEmailRequest.getSubject());
       

  return JobBuilder.newJob(ReportJob.class)
    .withIdentity(UUID.randomUUID().toString(), "report-jobs")
    .withDescription("Send Reports")
    .usingJobData(jobDataMap)
    .storeDurably()
    .build();
}

storeDurably metodu
Örnek
Şöyle yaparız
private JobDetail buildJobDetail(ScheduleEmailRequest scheduleEmailRequest) {
  JobDataMap jobDataMap = new JobDataMap();
  jobDataMap.put("email", scheduleEmailRequest.getEmail());
  jobDataMap.put("subject", scheduleEmailRequest.getSubject());
  jobDataMap.put("body", scheduleEmailRequest.getBody());
  return JobBuilder.newJob(EmailJob.class)
    .withIdentity(UUID.randomUUID().toString(), "email-jobs")
    .withDescription("Send Email Job")
    .usingJobData(jobDataMap)
    .storeDurably()
    .build();
}
Spring JobDetailFactoryBean 
org.quartz.JobDetail  yaratmak için kendi kodumuzda Spring projesine ait JobDetailFactoryBean kullanırız

Örnek
Şöyle yaparız
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * Create Quartz Job.
 *
 * @param jobClass  Class whose executeInternal() method needs to be called.
 * @param isDurable Job needs to be persisted even after completion. if true, job will be
persisted, not otherwise.
 * @param context   Spring application context.
 * @param jobName   Job name.
 * @param jobGroup  Job group.
 * @return JobDetail object
*/
public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,
                           ApplicationContext context, String jobName, String jobGroup) {
  JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
  factoryBean.setJobClass(jobClass);
  factoryBean.setDurability(isDurable);
  factoryBean.setApplicationContext(context);
  factoryBean.setName(jobName);
  factoryBean.setGroup(jobGroup);

  // set job data map
  JobDataMap jobDataMap = new JobDataMap();
  jobDataMap.put(jobName + jobGroup, jobClass.getName());
  factoryBean.setJobDataMap(jobDataMap);

  factoryBean.afterPropertiesSet();

  return factoryBean.getObject();
}

23 Ağustos 2021 Pazartesi

SpringCloud Config Server Kullanımı

Giriş
Açıklaması şöyle
With the Spring Cloud Configuration server, we can place the configuration files for all our microservices in a central configuration repository that will make it much easier to handle them. Our microservices will be updated to retrieve their configuration from the configuration server at startup.
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>
JDBC için şu satırı dahil ederiz
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
Config Repository Olarak Ne Kullanılabilir
Açıklaması şöyle. Yani repository olarak bir sürü seçenek var
The config server supports the storing of configuration files in a number of different backends, for example:  

Git repository  
Local filesystem  
HashiCorp Vault  
A JDBC database  

Other Spring projects have added extra backends for storing configuration, for example, the Spring Cloud AWS project, which has support for using either AWS Parameter Store or AWS Secrets Manager as backends. 
Vault için bir örnek burada

Deciding on the Initial Client Connection  
Açıklaması şöyle
By default, a client connects first to the config server to retrieve its configuration....
It is also possible to do this the other way around, that is, the client first connecting to the discovery server to find a config server instance and then connecting to the config server to get its configuration. There are pros and cons to both approaches.  

Note: One concern with connecting to the config server first is that the config server can become a single point of failure. If the clients connect first to a discovery server, such as Netflix Eureka, there can be multiple config server instances registered so that a single point of failure can be avoided.
Config Server API
Açıklaması şöyle
The config server exposes a REST API that can be used by its clients to retrieve their configuration. In this chapter, we will use the following endpoints in the API:  

1. /actuator: The standard actuator endpoint is exposed by all microservices. 
As always, these should be used with care. They are very useful during development but must be locked down before being used in production.  

2. /encrypt and /decrypt: Endpoints for encrypting and decrypting sensitive information. These must also be locked down before being used in production.  

3. /{microservice}/{profile}: Returns the configuration for the specified microservice and the specified Spring profile.
actuator/refresh microservice'leri yeni konfigürasyondan haberdar etmek için kullanılır. Şeklen şöyle


/encrypt ve /decrypt
Örnek - symmetric
Şöyle yaparız
server.port = 8888

# Set path to Local Git Repository
spring.cloud.config.server.git.uri = /home/...

# Setting the key for Symmetric Encryption and Decryption
encrypt.key = secret
Böylece artık 
localhost:888/encrypt
adresine POST yaparsak gönderdiğimiz veriyi şifreli olarak gelir alırız. Eğer şifreli veriyi açmak istersek
localhost:888/encrypt
adresine yine POST yapmak gerekir.
Örnek - asymmetric
keytool komutu ile bir public/private key JKS oluşturulur. Şöyle yaparız
keytool -genkeypair \
  -keyalg RSA \
  -dname "cn=Config-Serv, ou=Java, o=Spring, c=US" \
  -alias configserv \
  -keypass nopass \
  -keystore simple.jks \
  -storepass nopass
Şöyle yaparız
# Path of Key-Pairs to be used. (Recommended: Use Classpath)
encrypt.keyStore.location = claspath:/simple.jks

# Represents the password to read jks file (keypass)
encrypt.keyStore.password = nopass

# Represents the password to read jks file (storepass)
# From Java-11 keystore password and secret should be same.
# Optional to specify. When not given keystore password is used as secret by default.
encrypt.keyStore.secret = nopass

# Value of Alias used in jks generation
encrypt.keyStore.alias = configserv

# Format of keystore (Default: jks)
encrypt.keyStore.type = jks
Kullanım
1. @EnableConfigServer anotasyonu tanımlanır

2. Sunucu için SpringCloud Config Server application.properties ayarları tanımlanır

3. İstemci için ayarlar yapılır. Ayarlarda application + profile + label bilgilerinin belirtilmesi gerekiyor. Yani şöyledir
application-name
|--- application-name-dev.properties
|--- application-name-qa.properties