12 Aralık 2021 Pazar

SpringMVC Model Arayüzü - Server Side Rendering İçindir

Giriş
Şu satırı dahil ederiz.
import org.springframework.ui.Model;
addAttribute metodu
İmzası şöyle. jsp sayfasında gösterilecek nesne belirtilir. 
public ModelAndView addObject(String attributeName, Object attributeValue);
addAttribute metodu
İmzası şöyle. jsp sayfasında gösterilecek nesne belirtilir. 
public Model addAttribute(Object attributeValue);

7 Aralık 2021 Salı

SpringSecurity AuthenticationException Sınıfı - Tüm Authentication Exceptionlarının Atası

Giriş
Şu satırı dahil ederiz
import org.springframework.security.core.AuthenticationException;
Açıklaması şöyle
Abstract superclass for all exceptions related to an Authentication object being invalid for whatever reason.
Bu sınıftan kalıtan bazı sınıflar şöyle
AccountStatusException, 
ActiveDirectoryAuthenticationException, 
AuthenticationCancelledException, 
AuthenticationCredentialsNotFoundException, 
AuthenticationServiceException, 
BadCredentialsException, 
InsufficientAuthenticationException, 
NonceExpiredException, 
OAuth2AuthenticationException, 
PreAuthenticatedCredentialsNotFoundException, 
ProviderNotFoundException, 
RememberMeAuthenticationException, 
Saml2AuthenticationException, 
SessionAuthenticationException, 
UsernameNotFoundException


SpringSecurity AuthenticationSuccessHandler Arayüzü

Giriş
Şu satırı dahil ederiz
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
Açıklaması şöyle
It has the direct responsibility of deciding what to do after a successful authentication (like controlling the navigation based on the authenticated user’s role to the subsequent destination using a redirect or a forward).

You can also set headers to response or save a token value to a cache or save an audit log as a successful login.

Ready to Use Handlers (within Spring Security):

AbstractAuthenticationTargetUrlRequestHandler,
SimpleUrlAuthenticationSuccessHandler
Şeklen şöyle




SpringSecurity AbstractAuthenticationProcessingFilter Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.security.web.authentication
  .AbstractAuthencticationProcessingFilter;
Açıklaması şöyle
This filter will intercept a (browser-based HTTP-based authentication) request and attempt to perform authentication from that request if the request url path matches.

You define a request matcher for matching urls (provided with a direct filter url string value or RequestMatcher object), success and fail handlers and an authentication manager inside which the authentication flow will take place, processing the provided authentication request token.

It has a “attemptAuthentication” method where you can build up your custom “AbstractAuthenticationToken” request class and pass to the authentication manager’s “authenticate” method.
Kalıtan bazı sınıflar şöyle
CasAuthenticationFilter, 
OpenIDAuthenticationFilter, 
UsernamePasswordAuthenticationFilter — default responds to the URL “/login” which used to be “/j_spring_security_check” before.


21 Kasım 2021 Pazar

SpringKafka Consumer KafkaListenerEndpointRegistry Sınıfı - Dinamik Olarak Listener Takılabilir

Giriş
Şu satırı dahil ederiz
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
Açıklaması şöyle
... we cannot delete the consumers we have registered, because it is not possible to do this using the KafkaListenerEndpointRegistry class. In order to make this possible, we need to create our own registry class. 
getListenerContainer
MessageListenerContainer nesnesi döner

getListenerContainerIds metodu
Örnek
Şöyle yaparız. Burada MessageListenerContainer elde edildikten sonra start(), pause(), resume(), getAssignedPartitions(), getListenerId(), getGroupId() bir sürü metodu kullanılabilir.
@Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;

@GetMapping
public List<KafkaConsumerResponse> getConsumerIds() {
  return kafkaListenerEndpointRegistry.getListenerContainerIds()
    .stream()
    .map(this::createKafkaConsumerResponse)
    .collect(Collectors.toList());
}

private KafkaConsumerResponse createKafkaConsumerResponse(String consumerId) {
  MessageListenerContainer listenerContainer = kafkaListenerEndpointRegistry .getListenerContainer(consumerId);
  ...
}
registerListenerContainer metodu
Açıklaması şöyle
When we create a kafka consumer using the @KafkaListener annotation, it will be read by the spring when the application is run. KafkaListenerAnnotationBeanPostProcessor is class that responsible to read those annotation. Furthermore, the kafka consumers that we have created will be registered in the KafkaListenerEndpointRegistry class by KafkaListenerEndpointRegistrar class. When the application is running, KafkaListenerEndpointRegistry will start the consumers that have been registered based on the autoStartup property.
Açıklaması şöyle
The method accepts 3 parameters, that is KafkaListenerEndpoint, KafkaListenerContainerFactory, and Boolean startImmediately. 
KafkaListenerEndpoint yazısına bakabilirsiniz

Örnek
Şöyle yaparız
@Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;

@Autowired
private KafkaListenerContainerFactory kafkaListenerContainerFactory;

MethodKafkaListenerEndpoint<String, String> kafkaListenerEndpoint = ...;
boolean startImmediately = ...;

kafkaListenerEndpointRegistry.registerListenerContainer(kafkaListenerEndpoint,
  kafkaListenerContainerFactory, 
  startImmediately);
Örnek
Şöyle yaparız
@Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;

@Autowired
private KafkaListenerContainerFactory kafkaListenerContainerFactory;

public void createAndRegisterListener(String topic) {
  KafkaListenerEndpoint listener = ...;
  kafkaListenerEndpointRegistry.registerListenerContainer(listener, 
    kafkaListenerContainerFactory, true);
}

15 Kasım 2021 Pazartesi

SpringWebFlux ServerResponse Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.web.reactive.function.server.ServerResponse;
body metodu
İçine bir Mono nesnesi konulabilir.
Örnek
Şöyle yaparız
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RouterFunctions.route; @Configuration public class SampleRouter { @Bean public RouterFunction<ServerResponse> sampleRoute(SampleHandler handler) { return route(GET("/simple-route"), handler::handleRequest); } } import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; @Component public class SampleHandler { public Mono<ServerResponse> handleRequest(ServerRequest request) { return ServerResponse.ok().body(Mono.just("Sample Route Successful!"), String.class); } }


SpringData Jdbc AbstractRoutingDataSource Sınıfı - Separate Database İle Multitenant Yapı İçindir

Giriş
Şu satırı dahil ederiz
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
Kısaca
1. AbstractRoutingDataSource sınıfından kalıtan bir bean kodlarız. Bu sınıfta determineCurrentLookupKey() metodunu olmalıdır
2. AbstractRoutingDataSource sınıfından kalıtan bir bean nesnemize setTargetDataSources() ile hedef DataSource nesnelerini atarız.
3. Bu sınıf bir ThreadLocal ile birlikte kullanılır. ThreadLocal.set() ile bir enum veya string verilir. Bu enum veya string'e denk gelen DataSource nesnesi AbstractRoutingDataSource içinde setTargetDataSources() ile atanmıştır.

Açıklaması şöyle. Yani aslında AbstractRoutingDataSource sınıfından kalıtsak bile yine bir DataSource yaratıyoruz.
Abstract DataSource implementation that routes getConnection() calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context.

determineCurrentLookupKey metodu
Açıklaması şöyle. Bu metod Spring tarafından çağrılır ve hangi DataSource'un kullanılacağını döner. Spring'de kendi içindeki Map'i arayarak ilgili DataSource nesnesini kullanır
A component that extends AbstractRoutingDataSource and is responsible to provide the list of datasources and also to provide the implementation of the determineCurrentLookupKey() method which will help in determining the current datasource.
Örnek
Şöyle yaparız
@Configuration
public class DataSourceConfig {

  @Bean
  public DataSource dataSource() {
    TenantRoutingDataSource customDataSource = new TenantRoutingDataSource();
    Map<Object, Object> targetDataSources = new HashMap<>();
    // Populate targetDataSources map with tenant's DataSource
    // ...
    customDataSource.setTargetDataSources(targetDataSources);
    return customDataSource;
  }
}
Açıklaması şöyle
In this example, TenantRoutingDataSource extends AbstractRoutingDataSource from Spring and overrides determineCurrentLookupKey() method to provide routing based on tenant context.
Örnek - Enum
Şöyle yaparız
public class DataSourceRouter extends AbstractRoutingDataSource {
  
  @Override
  protected Object determineCurrentLookupKey() {
    if (AsyncContextHolder.getAsyncContext() != null) {
      return AsyncContextHolder.getAsyncContext().get(READTYPE);
    }
    return null;
  }  
}
Elimizde şöyle bir enum olsun
public enum ClientDatabase {
  CLIENT_A, CLIENT_B
}
DataSourceRouter yaratmak için şöyle yaparız. Bu nesneye iki tane DataSource atanıyor
@Bean
public DataSource clientDatasource() {
  Map<Object, Object> targetDataSources = new HashMap<>();
  DataSource clientADatasource = clientADatasource();
  DataSource clientBDatasource = clientBDatasource();

  targetDataSources.put(ClientDatabase.CLIENT_A,clientADatasource);
  targetDataSources.put(ClientDatabase.CLIENT_B, clientBDatasource);

  DataSourceRouter datasourceRouter   = new ClientDataSourceRouter();
  datasourceRouter.setTargetDataSources(targetDataSources);
datasourceRouter.setDefaultTargetDataSource(clientADatasource);
return clientRoutingDatasource; }
Örnek - Enum
Elimizde şöyle bir kod olsun. Burada enum içeren ThreadLocal nesne tanımlanıyor
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class DataSourceContextHolder {
  private static ThreadLocal<DataSourceEnum> threadLocal; 

  public DataSourceContextHolder() {
    threadLocal = new ThreadLocal<>();
  }

  public void setDataSourceEnum(DataSourceEnum dataSourceEnum) {
    threadLocal.set(dataSourceEnum);
  }

  public DataSourceEnum getDataSourceEnum() {
    return threadLocal.get();
  }

  public static void clearDataSourceEnum() {
    threadLocal.remove();
  }
}
Şöyle yaparız. Burada constructor içinde setTargetDataSources ile her enum'a denk gelen DataSource atanıyor. Ayrıca varsayılan DataSource ta atanıyor.
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

@Component
public class DataSourceRouting extends AbstractRoutingDataSource {
  private DataSourceOneConfig dataSourceOneConfig;
  private DataSourceTwoConfig dataSourceTwoConfig;
  private DataSourceContextHolder dataSourceContextHolder;

  public DataSourceRouting(DataSourceContextHolder dataSourceContextHolder,
                           DataSourceOneConfig dataSourceOneConfig,
    		     	   DataSourceTwoConfig dataSourceTwoConfig) {
    this.dataSourceOneConfig = dataSourceOneConfig;
    this.dataSourceTwoConfig = dataSourceTwoConfig;
    this.dataSourceContextHolder = dataSourceContextHolder;

    Map<Object, Object> dataSourceMap = new HashMap<>();
    dataSourceMap.put(DataSourceEnum.DATASOURCE_ONE, dataSourceOneDataSource());
    dataSourceMap.put(DataSourceEnum.DATASOURCE_TWO, dataSourceTwoDataSource());
    this.setTargetDataSources(dataSourceMap);
    this.setDefaultTargetDataSource(dataSourceOneDataSource());
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return dataSourceContextHolder.getBranchContext(); //Thread local object
  }
}
Controller içinde şöyle yaparız. Burada ThreadLocal nesneye değer tanıyor
@RestController
@RequiredArgsConstructor
public class DetailsController {

  private final DataSourceContextHolder dataSourceContextHolder;

  @GetMapping(value="/getEmployeeDetails/{dataSourceType}")
  public List<Employee> getAllEmployees(@PathVariable("dataSourceType")
                                        String dataSourceType){
    if(DataSourceEnum.DATASOURCE_TWO.toString().equals(dataSourceType)){
      dataSourceContextHolder.setBranchContext(DataSourceEnum.DATASOURCE_TWO);
    } else {
      dataSourceContextHolder.setBranchContext(DataSourceEnum.DATASOURCE_ONE);
    }
    return employeeService.getAllEmployeeDetails(); 
  }
}
Örnek - AOP + Anotasyon
Şöyle yaparız. Burada isme sahip iki tane DataSource tanımlanıyor. Bir tanesi varsayılan DataSource
public class AbstractRoutingDataSourceImpl extends AbstractRoutingDataSource {

  private static final ThreadLocal<String> DATABASE_NAME = new ThreadLocal<>();

  public AbstractRoutingDataSourceImpl(DataSource defaultTargetDatasource, 
                                       Map<Object,Object> targetDatasources) {
    super.setDefaultTargetDataSource(defaultTargetDatasource);
    super.setTargetDataSources(targetDatasources);
    super.afterPropertiesSet();
  }
  public static void setDatabaseName(String key) {
    DATABASE_NAME.set(key);
  }

  public static String getDatabaseName() {
    return DATABASE_NAME.get();
  }

  public static void removeDatabaseName() {
    DATABASE_NAME.remove();
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return DATABASE_NAME.get();
  }
}
İki DataSource şöyle yaratılır
@Configuration
@EnableJpaRepositories(basePackages = "com.dynamicdatasource.demo",entityManagerFactoryRef = "entityManager")
public class DynamicDatabaseRouter {

    public static final String PROPERTY_PREFIX = "spring.datasource.";

    @Autowired
    private Environment environment;

    @Bean
    @Primary
    @Scope("prototype")
    public AbstractRoutingDataSourceImpl dataSource() {
        Map<Object, Object> targetDataSources = getTargetDataSources();
        return new AbstractRoutingDataSourceImpl((DataSource)targetDataSources.get("default"), targetDataSources);
    }

    @Bean(name = "entityManager")
    @Scope("prototype")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dataSource()).packages("com.dynamicdatasource").build();
    }

    private Map<Object,Object> getTargetDataSources() {
        
        //loading the database names to a list from application.properties file
        List<String> databaseNames = environment.getProperty("spring.database-names.list",List.class);
        Map<Object,Object> targetDataSourceMap = new HashMap<>();

        for (String dbName : databaseNames) {

                DriverManagerDataSource dataSource = new DriverManagerDataSource();
                dataSource.setDriverClassName(envioronment.getProperty(PROPERTY_PREFIX + dbName + ".driver"));
                dataSource.setUrl(environment.getProperty(PROPERTY_PREFIX + dbName + ".url"));
                dataSource.setUsername(environment.getProperty(PROPERTY_PREFIX + dbName + ".username"));
                dataSource.setPassword(environment.getProperty(PROPERTY_PREFIX + dbName + ".password"));
                targetDataSourceMap.put(dbName,dataSource);

        }
        targetDataSourceMap.put("default",targetDataSourceMap.get(databaseNames.get(0)));
        return targetDataSourceMap;
    }
}
Burada bir aspect kodlanıyor
@Aspect
@Component
@Order(-10)
public class DataSourceAspect {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    //defininining where the jointpoint need to be applied
    @Pointcut("@annotation(com.dynamicdatasource.demo.config.SwitchDataSource)")
    public void annotationPointCut() {
    }

    // setting the lookup key using the annotation passed value
    @Before("annotationPointCut()")
    public void before(JoinPoint joinPoint){
        MethodSignature sign =  (MethodSignature)joinPoint.getSignature();
        Method method = sign.getMethod();
        SwitchDataSource annotation = method.getAnnotation(SwitchDataSource.class);
        if(annotation != null){
            AbstractRoutingDataSourceImpl.setDatabaseName(annotation.value());
            logger.info("Switch DataSource to [{}] in Method [{}]",
                annotation.value(), joinPoint.getSignature());
        }
    }
    
    // restoring to default datasource after the execution of the method
    @After("annotationPointCut()")
    public void after(JoinPoint point){
        if(null != AbstractRoutingDataSourceImpl.getDatabaseName()) {
            AbstractRoutingDataSourceImpl.removeDatabaseName();
        }
    }
}
Aspect için anotasyon şöyle olsun
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchDataSource {

    String value() default "";

}
Kullanırken şöyle yaparız
@SwitchDataSource(value = "college")
public List<College> getAllColleges(){
  return collegeRepository.findAll();
}

@SwitchDataSource(value = "student")
public List<Student> getAllStudents(){
  return studentRepository.findAll();
}
Örnek - TransactionSynchronizationManager
Burada amaç @Transactional ve @Transactional(readOnly = true) olarak işaretli çağrıları farklı veri tabanlarına göndermek. Şeklen şöyle

AbstractRoutingDataSource şöyledir
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {

  @Override
  protected Object determineCurrentLookupKey() {
    return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ?
      DataSourceType.READ_ONLY :
      DataSourceType.READ_WRITE;
  }
}
Bu nesneyi yaratmak ve doldurmak için şöyle yaparız
public class TransactionRoutingConfiguration 
        extends AbstractJPAConfiguration {

   ...
  @Bean
  public DataSource readWriteDataSource() {
    ...
  }

  @Bean
  public DataSource readOnlyDataSource() {
    ...
  }

  @Bean
  public TransactionRoutingDataSource actualDataSource() {
    TransactionRoutingDataSource routingDataSource = 
      new TransactionRoutingDataSource();

    Map<Object, Object> dataSourceMap = new HashMap<>();
    dataSourceMap.put(
      DataSourceType.READ_WRITE, 
      readWriteDataSource()
    );
    dataSourceMap.put(
      DataSourceType.READ_ONLY, 
      readOnlyDataSource()
    );

    routingDataSource.setTargetDataSources(dataSourceMap);
    return routingDataSource;
  }
}