29 Ocak 2021 Cuma

Kafka Avro API

Maven
Şu satırı dahil ederiz
<dependency>
   <groupId>org.apache.avro</groupId>
   <artifactId>avro</artifactId>
   <version>1.8.2</version>
</dependency>
<dependency>
   <groupId>io.confluent</groupId>
   <artifactId>kafka-avro-serializer</artifactId>
   <version>3.3.1</version>
</dependency>
Örnek - Producer KafkaAvroSerializer Kullanır
Şu satırı dahil ederiz
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.generic.GenericRecordBuilder;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.dataformat.avro.AvroMapper;
import com.fasterxml.jackson.dataformat.avro.AvroSchema;
Şöyle yaparız
String TOPIC_NAME = "customer-topic";
String KAFKA_SERVER_ADDRESS = "localhost:9092";
String AVRO_SERIALIZER_CLASS = "io.confluent.kafka.serializers.KafkaAvroSerializer";
String SCHEMA_REGISTRY_SERVER_URL = "http://localhost:8081";
// Kafka Producer Configurations
Properties kafkaProps = new Properties();
kafkaProps.put("bootstrap.servers", KAFKA_SERVER_ADDRESS);
kafkaProps.put("key.serializer", AVRO_SERIALIZER_CLASS);
kafkaProps.put("value.serializer", AVRO_SERIALIZER_CLASS);
kafkaProps.put("schema.registry.url", SCHEMA_REGISTRY_SERVER_URL);
   
// Schema Generation For Our Customer Class
AvroMapper avroMapper = new AvroMapper();
AvroSchema schema = avroMapper.schemaFor(Customer.class);
try(final Producer<String, GenericRecord> producer = new KafkaProducer<>(kafkaProps);) {
   
  // Publishing The Messages
  Customer customer = CustomerGenerator.getNext();
  GenericRecordBuilder recordBuilder = new GenericRecordBuilder(schema.getAvroSchema());
  recordBuilder.set("name", customer.getName());
  recordBuilder.set("email", customer.getEmail());
  recordBuilder.set("contactNumber", customer.getContactNumber());
  GenericRecord genericRecord = recordBuilder.build();
    
  ProducerRecord<String, GenericRecord> producerRecord = new ProducerRecord<>(TOPIC_NAME, 
      "customer", genericRecord);
  producer.send(producerRecord);
  
}
Örnek - Consumer
Şöyle yaparız
String TOPIC_NAME = "customer-topic";
String KAFKA_SERVER_ADDRESS = "localhost:9092";
String SCHEMA_REGISTRY_SERVER_URL = "http://localhost:8081";
String CONSUMER_GROUP_ID = "customer-message-consumer-group";

// Kafka Consumer Configurations
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_SERVER_ADDRESS);
properties.put(ConsumerConfig.GROUP_ID_CONFIG, CONSUMER_GROUP_ID);
properties.put("schema.registry.url", SCHEMA_REGISTRY_SERVER_URL);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,KafkaAvroDeserializer.class);
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

try(final Consumer<String, Customer> consumer = new KafkaConsumer<>(properties)){
  consumer.subscribe(Collections.singleton(TOPIC_NAME));
  while (true) {
    ConsumerRecords<String, Customer> records = consumer.poll(Duration.ofMillis(100));
       
    for (final ConsumerRecord<String, Customer> record : records) {
      System.out.println(record.value());
    }
    consumer.commitAsync();
}

28 Ocak 2021 Perşembe

JPA EntityManager.createQuery metodu

Giriş
2 tip createQuery() metodu var. Birisi Query nesnesi dönüyor, diğeri ise TypedQuery dönüyor.
 
createQuery metodu - CriteriaQuery
JPQL kullanır. CriteriaQuery<T> nesnesi olduğu için TypedQuery döndürür.
Örnek
Şöyle yaparız.
CriteriaQuery<UserDTO> cq = ...;
...
TypedQuery<UserDTO> query = em.createQuery(cq);
Örnek
Şöyle yaparız.
CriteriaQuery<Long> sc = ...;
...
Long count = em.createQuery(sc).getSingleResult();
createQuery metodu - String
JPQL kullanır. Bir Query nesnesi döner. Dışarından alınacak parametreler ":" karakteri ile başlayan değişkenlerle belirlenir. Bu değişkenlere değerleri setParameter() metodu ile atanır.
Örnek
Şöyle yaparız.
Query q = em.createQuery("SELECT s FROM Status s WHERE s.statusName =:status");
Örnek
Şöyle yaparız.
em.createQuery("select gne from Entity gne where type(gne) = :subclass")
  .setParameter("subclass", GivenNameEntity.class)
  .getResultList();
Örnek
Şöyle yaparız.
String sql = "select r from Rent r JOIN FETCH r.rentables where r.id = :rid";
Query queryString =  em.createQuery(sql)
    .setParameter("rid", rent.getId());
List<Rent> resultList = queryString.getResultList();
createQuery metodu - String + Class
İkinci parametre T tipi olduğu için TypedQuery döndürür.TypedQuery yazısına taşıdım.

27 Ocak 2021 Çarşamba

Open Services Gateway initiative - OSGi

Giriş
OSGi ile tam denk olmayan ancak biraz benzer özellikler veren ServiceLoader yazısına bakabilirsiniz.

OSGi Nedir?
Açıklaması şöyle. OSGi bir container üzerinde çalışır.
The OSGi framework is a standardized module system and service platform for the Java programming language. The OSGi standards has both open-source and commercial implementations. Below are some popular open source implementations of OSGi.
- Apache Felix
- Eclipse Equinox

Any OSGi application runs on any of above OSGi container and application will be developed as a Module/Plugins.
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.osgi</groupId>
  <artifactId>org.osgi.core</artifactId>
  <version>${osgi.version}</version>
  <scope>provided</scope>
</dependency>
Container için org.apache.felix plugin dahil edilir.

BundleActivator Sınıfı
Plugin'leri register etmek içindir.

Örnek
Şöyle yaparız
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

  public static BundleContext bundleContext;

  @Override
  public void start(BundleContext context) throws Exception {
    bundleContext = context;
    registerServices();
  }

  @Override
  public void stop(BundleContext context) throws Exception {
    bundleContext = null;
  }

  private void registerServices() {
    CalculatorService service = new SubService();
    bundleContext.registerService(CalculatorService.class.getName(), service,
new Hashtable<String, Object>());
  }
}
BundleContext Sınıfı
Eğer SpringBoot ile kullanıyorsak ConfigurableApplicationContext ile aynı şeydir.
Örnek
Şöyle yaparız
import org.osgi.framework.BundleContext;

import com.finalhints.osgi.Activator;
import com.finalhints.osgi.api.CalculatorService;

@RestController
@RequestMapping("/calculator")
public class CalculatorController {

  @GetMapping
  public List<String> getAllOperations() {
    BundleContext bundleContext = ...;

    try {
      Collection<ServiceReference<CalculatorService>> references = bundleContext
        .getServiceReferences(CalculatorService.class, null);
      for (ServiceReference<CalculatorService> reference : references) {
        CalculatorService calcService = bundleContext.getService(reference);
      }
    } catch (Exception e) {
...
    }
    ...
  }
}

25 Ocak 2021 Pazartesi

SLF4J - Mapped Diagnostic Context (MDC)

Giriş
Açıklaması şöyle
The basic idea of Mapped Diagnostic Context is to provide a way to enrich log messages with pieces of information that could be not available in the scope where the logging actually occurs, but that can be indeed useful to better track the execution of the program.

In most of the modern client-server applications, servers are made to handle different client requests in separate worker threads. But traditional logging methods are not able to capture the thread/event specific information without doing a complete logging implementation manually.

MDC is introduced to serve this gap. It will help the developers to store thread/event specific information in its internal data structure (Map). And append the information to the log messages according to the configuration.

Simple Logging Facade for Java (SLF4J) APIs supports MDC feature and through Log4j and LOGBack implementations, developers can use the MDC feature.

... developer should clear the MDC context at the execution start point as it will contain garbage values if the threads are being reused. (eg: executer service with reusable threads)
Açıklaması şöyle
Both Logback and Log4j2 support a feature called “Mapped Diagnostic Context”, or MDC, where a dictionary/map of context is attached to each thread, which you can then read from and write to as needed. Data stored in a thread’s MDC can be logged using the %X variable. You can either log all of the MDC, or just specific pieces of information, e.g. if your log pattern included %X{orderNumber}, just the value of the "orderNumber" key from a thread's MDC would be logged.
Örnek
SLF kodunda şöyle yaparız
import org.slf4j.MDC;
...
try {
  MDC.put("field_name", "value");
  log.info("This message will have the field field_name set to value");
  …
  log.info("This one also");
  …
}
finally {
  MDC.remove("field_name");
}
Örnek
Şöyle yaparız. Burada AccountId=%X{accountId} şeklindeki kullanımda MDC içine "accountId" değişkeni yerleştiriliyor. Appender da bunu logluyor.
<configuration>
  <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>%d{YYYY-MM-DD'T'hh:mm:ss} [%thread] %5p %c{1}
- %m AccountId=%X{accountId}
CustomerId=%X{customerId}
RequestId=%X{requestId} %n</pattern>
    </encoder>
  </appender>

  <root level="TRACE">
    <appender-ref ref="stdout" />
  </root>

</configuration>
SLF4J kodu şöyle.
public void run() {
  MDC.put("threadId",String.valueOf(Thread.currentThread().getId()));
  MDC.put("accountId", transfer.getAccount());
  MDC.put("requestId", transfer.getRequest());
  ... 
}
Örnek
Log4J ile şöyle yaparız. Burada ThreadContext sınıfı kullanılıyor
import org.apache.logging.log4j.ThreadContext;

public List<OrderItem> getOrderItems(@PathVariable("orderNumber") @Required
  String orderNumber) {
  ThreadContext.put("orderNumber", orderNumber);
  
  List<OrderItem> orderItems = null;
  try {
    orderItems = d.getItemsForOrder(orderNumber);
  } catch (Exception ex) {
    logger.error("Unable to fetch items from database for order", ex);
  }
  logger.info("Got items, returning");
  ThreadContext.clearAll();
  return orderItems;
}
Burada ise CloseableThreadContext kullanılıyor. Bu sınıf ThreadContext'i otomatik olarak temizler. Şöyle yaparız
public List<OrderItem> getOrderItems(...) {
try (final CloseableThreadContext.Instance ignored = CloseableThreadContext .put("orderNumber", orderNumber) .put("retailer", retailer)) { ... } }
Log4J örneği ama mantık aynı. Şöyle yaparız
<Property name="LOG_PATTERN">
    %d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%15.15t] %-40.40c{1.} : %X : %m%n%ex
</Property>
Çıktı olarak şunu alırız
2021-06-08 12:25:10.774 INFO [nio-8080-exec-1] c.z.s.s.OrderController : {orderNumber=2313} : Getting items
2021-06-08 12:25:10.774 INFO [nio-8080-exec-1] c.z.s.s.OrderController : {orderNumber=2313} : Got items, returning
2021-06-08 12:25:26.732 INFO [nio-8080-exec-2] c.z.s.s.OrderController : {orderNumber=8291} : Getting items
2021-06-08 12:25:26.732 INFO [nio-8080-exec-2] c.z.s.s.OrderController : {orderNumber=8291} : Got items, returning
Örnek
Elimizde şöyle bir appender olsun. %X{key} ile MDC nesnesine "key" isimli bir alan ekleyeceğimizi belirtiriz.
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
  <layout>
    <Pattern>%X{key}  - %m%n</Pattern>
  </layout> 
</appender>
Şöyle yaparız.
public String doAnyAction(long key) {
  MDC.put("key", key);
  logger.info(key);
}

24 Ocak 2021 Pazar

Thread.setPriority metodu

Giriş
Açıklaması şöyle.
Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority.
Not 
Linux'da Thread önceliğini değiştirebilmek için JVM'i başlatırken iki tane parametre verilmes gerekiyor. Bunlar ThreadPriorityPolicy ve UseThreadPriorities. Şöyle yaparız
$ sudo java -XX:ThreadPriorityPolicy=1 -XX:+UseThreadPriorities ThreadPriorityTest &
Sabitler
Java 3 tane sabit veriyor. Açıklaması şöyle
The value can be anything between Thread.MAX_PRIORITY and Thread.MIN_PRIORITY (defined as 10 and 1 respectively). Default is Thread.NORM_PRIORITY with a value of 5.
Örnek
Şöyle yaparız
Thread t = new Thread();
t.setPriority(Thread.currentThread().getPriority() + 1);
t.start();
Örnek
Şöyle yaparız
Thread t1 = ...;
t1.setPriority(Thread.NORM_PRIORITY + 3);
      
Thread t2 = ...;
t2.setPriority(Thread.NORM_PRIORITY - 3);

22 Ocak 2021 Cuma

Awt SystemTray Sınıfı

Giriş
Şu satırı dahil ederiz
import java.awt.SystemTray;
displayMessage metodu
Şöyle yaparız
String toolTip = "...";

if (SystemTray.isSupported()) {
  SystemTray tray = SystemTray.getSystemTray();
  TrayIcon[] trayIcons = tray.getTrayIcons();

  for (TrayIcon trayIcon: trayIcons ) {
    if (trayIcon.getToolTip().equals(toolTip)) {
      tray.remove(trayIcon);
    }
  }

  //If the icon is a file : e.g src/main/resources/2_1.jpg
  Image image = Toolkit.getDefaultToolkit().createImage("2_1.jpg");
  //Alternative (if the icon is on the classpath):
  //Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon.png"));

  TrayIcon trayIcon = new TrayIcon(image, toolTip);
  //Let the system resize the image if needed
  trayIcon.setImageAutoSize(true);
  //Set tooltip text for the tray icon
  tray.add(trayIcon);

  trayIcon.displayMessage("...", TrayIcon.MessageType.INFO);
}

21 Ocak 2021 Perşembe

MapStruct Kullanımı

Giriş
Açıklaması şöyle
.. MapStruct generates bean mappings at compile-time which ensures a high performance, …

MapStruct is an annotation processor which is plugged into the Java compiler and can be used in command-line builds (Maven, Gradle etc.) as well as from within your preferred IDE.
MapStruct kütüphanesinin rakibi ModelMapper kütüphanesi.

Maven
Örnek - dependency
Şöyle yaparız. Bu kullanımda mapstruct-processor dependency tree'de görünür. Hatta alt projelerde de dependency olarak eklemek gerekir.
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.4.2.Final</version>
</dependency>

<dependency>  <!-- this one here -->
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.4.2.Final</version>
  <scope>provided</scope>
</dependency>
Örnek - maven-compiler-plugin ile kullanım
Şu satırı dahil ederiz
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.3.1.Final</version>
</dependency>
Örnek - plugin
Plugin için şu satırı dahil ederiz.  Burada Lombok eklentisi, MapStruct'tan önce tanımlanmalı
Sırayla ilgili bir uyarı burada. Bir başka uyarı da burada
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <source>1.8</source> <!-- depending on your project -->
    <target>1.8</target> <!-- depending on your project -->
    <annotationProcessorPaths>
      <path>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
      </path>
      <path>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.3.1.Final</version>
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>
Örnek - plugin
Şöyle yaparız
<annotationProcessorPaths>
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${org.projectlombok.version}</version>
  </path>
  <!-- This is needed when using Lombok 1.18.16 and above -->
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
  </path>
  <!-- Mapstruct should follow the lombok path(s) -->
  <path>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
  </path>
</annotationProcessorPaths>
Aslında annotationProcessorPaths için açıklama şöyle.  Bu kullanımda mapstruct-processor dependency tree'de görünmez. Ancak diğer annotation processorlar da varsa onları da artık otomatik bulunmadıkları için eklemek gerekir.
If specified, the compiler will detect annotation processors only in those classpath elements. If omitted, the default classpath is used to detect annotation processors. The detection itself depends on the configuration of annotationProcessors.

Kullanım
- Bir tane POJO kodlanır
- Bir tane DTO sınıfı kodlanır
- Bir tane @Mapper anotasyonuna sahip interface kodlanır. Interface'in toDto isimli POJO alan ve DTO dönen bir metodu olur. Daha sonra kullanmak için şöyle yaparız
StudentMapper studentMapper = ...;

Student student = ...;
StudentDto studentDto = studentMapper.toDto(student);
@InheritInverseConfiguration Anotasyonu
@Mapper ile işaretli sınıf içine yerleştirilir. Ters yöne yani DTO'dan sınıfa çevrim kodunun da üretilmesini sağlar.

@Mapper Anotasyonu
@Mapper anotasyonu yazısına taşıdım

@Mapping Anotasyonu 
@Mapping anotasyonu yazısına taşıdım.

@ObjectFactory Anotasyonu
@ObjectFactory anotasyonu yazısına taşıdım

@BeforeMapping Anotasyonu
@BeforeMapping anotasyonu yazısına taşıdım