Skip to content

20. RxJava ile asenkron programlama

Okunması gereken belge: [Introduction à RxJava. Application aux environnements Swing et Android.]

Bu bölümde, aşağıdaki mimariye sahip bir istemci/sunucu uygulaması oluşturduğumuz 17.6. bölüme geri dönüyoruz:

[1]'teki Swing arayüzünde kullanıcının gerçekleştirdiği bazı eylemler, HTTP ve [2] ağları üzerinden [3]'teki veritabanında eylemleri tetiklemektedir. Bu nedenle, kullanıcının eylemine verilen yanıtın gelmesi biraz zaman alabilir. Kullanıcı arayüzüne bir bekleme göstergesi eklenmesi ve işlemin çok uzun sürmesi durumunda başlatılan işlemi iptal etme seçeneği sunulması iyi olurdu. 17.6. bölümde, sunucuyla bilgi alışverişi gerektiren her kullanıcı eylemi senkronize olarak gerçekleştirilir. Kod tarafından yürütülen olay işleyicisi, yanıt alınana kadar tamamlanmaz. Bu süre boyunca grafik kullanıcı arayüzü donar: kullanıcının yeni eylemlerine yanıt vermez. Bu eylemler, o anda çalışmakta olan olay işleyicisi tamamlandığında işlenmek üzere basitçe kuyruğa alınır. Dolayısıyla, bir iptal düğmesi görüntülenirse, kullanıcı bu düğmeye tıklayabilir ancak devam eden işlem tamamlanana kadar hiçbir şey olmaz. Bu durumda iptal düğmesinin hiçbir anlamı kalmaz.

İptal düğmesine tıklamanın bir sonuç doğurması için, devam eden işlemin tamamlanmış olması gerekir. Bunun için, potansiyel olarak uzun sürecek işlemi asenkron olarak başlatmalıdır:

  • olay işleyicisi uzun süren işlemi başlatır ancak sonucunu beklemez ve kontrolü, grafik kullanıcı arayüzündeki olayları yöneten UI iş parçacığına geri verir. Uzun süren işlem, UI iş parçacığından farklı bir iş parçacığı üzerinde başlatılır; bu sayede UI iş parçacığı engellenmez;
  • kullanıcı, uzun süren işlemin tamamlanmasından önce iptal düğmesine tıklarsa, boş durumda olan UI iş parçacığı bu olayı işleyebilir. Böylece, uzun süren işlemin sonucunu göz ardı ederek işlemden vazgeçilebilir;
  • uzun işlem iptal edilmediyse, yanıtın gelmesi UI iş parçacığında bir olay tetikleyecektir. Bu iş parçacığı, boş durumda ise, yanıtı işleyecek olan bu olayla ilişkili kodu çalıştıracaktır;

Kullanıcı arayüzü daha önce olduğu gibi çalışacaktır. Sunucunun yanıt süreleri hızlıysa, kullanıcı farkı hissetmeyecektir. Yanıt süreleri fark edilebilir düzeydeyse, kullanıcı bir iptal düğmesinin belirdiğini görecek ve devam eden işlemi durdurma imkânına sahip olacaktır.

[Rx] kütüphanesi, asenkron programlama yapılmasına olanak tanır. En büyük avantajı, birçok ortama (Java, .NET, JS, ...) ve bir ortamda edinilen bilgi birikiminin başka bir ortama kolayca aktarılabilmesidir. Burada [Introduction à RxJava. Application aux environnements Swing et Android] belgesinin 2. bölümünden yararlanacağız. Okuyucunun bu bölümü okumasını öneririz. Aşağıda, bu bölümdeki örneklerden alınan kodları kullanacağız.

Uygulamanın mimarisini şu şekilde geliştireceğiz:

  • [1]'e; [swing] katmanı ile [métier] katmanı arasına bir [RxJava] katmanı ekleyeceğiz. Bu katmandaki yöntemler artık asenkron olarak çağrılacaktır;

İşlemi birkaç adımda gerçekleştireceğiz:

  • 1. adım: [metier, DAO] katmanı şu anda [ui] katmanına senkron bir arayüz sunuyor. Bunu asenkron bir [RxJava, metier, DAO] katmanına dönüştüreceğiz;
  • 2. adım: Senkron konsol uygulamasını, yine senkron ancak [RxJava, metier, DAO] asenkron arayüzünü kullanan bir uygulamaya dönüştüreceğiz;
  • 3. adım: Senkron Swing uygulamasını asenkron bir Swing uygulamasına dönüştüreceğiz;

20.1. 1. adım

Mevcut senkron katmanı [metier, DAO]'i asenkron katman [RxJava, metier, DAO]'e dönüştürüyoruz.

20.1.1. Oluşturma

17.4. bölümdeki Maven projesinden yola çıkıyoruz ve bunu NetBeans ile açıyoruz:

Bu [1] projesini (kopyala/yapıştır) yeni bir proje olan [elections-rxjava-metier-dao-security-webjson] [2]'e kopyalıyoruz.

20.1.2. Maven Yapılandırması

Yeni projenin [pom.xml] dosyasını güncelleyerek [RxJava] kütüphanesine bağımlılık ekliyoruz:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>istia.st.elections</groupId>
  <artifactId>elections-metier-dao-security-rxjava-webjson</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <description>Client jUnit du serveur web / jSON</description>
  <name>elections-metier-dao-security-rxjava-webjson</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.7.RELEASE</version>
  </parent>

  <dependencies>
    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
    </dependency>
    <!-- Spring tarafından kullanılan jSON kütüphanesi -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
    <!-- Spring tarafından kullanılan bileşen RestTemplate -->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    <!-- Google Guava -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>16.0.1</version>
      <scope>test</scope>
    </dependency>
    <!-- günlük kütüphanesi -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <!-- Spring Boot Testi -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- Spring Boot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.reactivex/rxjava -->
    <dependency>
      <groupId>io.reactivex</groupId>
      <artifactId>rxjava</artifactId>
      <version>1.2.0</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
      </plugin>
    </plugins>
  </build>
</project>
  • 65-70. satırlar: RxJava kütüphanesine bağımlılık ekledik;

20.1.3. [métier] katmanının asenkron uygulaması

[RxJava, métier] katmanını uygulamak için, projeye [IRxElectionsMetier] [1] asenkron arayüzünü ve bunun [RxElectionsMetier] [2] uygulamasını ekliyoruz:

  

[IRxElectionsMetier] arayüzü, [RxJava, métier] katmanının asenkron arayüzüdür. Kod şöyledir:


package elections.security.client.metier;

import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import rx.Observable;

public interface IRxElectionsMetier {

  // kimlik doğrulama
  Observable<Void> authenticate(User user);

  // yarışmada yer alan listeleri almak
  Observable<ListeElectorale[]> getListesElectorales(User user);

  // doldurulacak koltuk sayısı
  Observable<Integer> getNbSiegesAPourvoir(User user);

  // seçim barajı
  Observable<Double> getSeuilElectoral(User user);

  // sonuçların kaydı
  Observable<Void> recordResultats(User user, ListeElectorale[] listesElectorales);

  // koltuk hesaplaması
  Observable<ListeElectorale[]> calculerSieges(User user, ListeElectorale[] listesElectorales);

}

[IRxElectionsMetier] arayüzü, [IElectionsMetier] arayüzünün yöntemlerini devralır; ancak [IElectionsMetier] arayüzündeki bir M yöntemi T türünde bir sonuç döndürürken, [IRxElectionsMetier] arayüzündeki M yöntemi, Observable<T> türünde bir sonuç döndürür. [Observable] türü, RxJava kütüphanesi tarafından sağlanır. Bir Observable<T> türü, T türünü asenkron olarak alacak olan [subscribe] yöntemini sağlar. Bu yöntemle ilişkili üç olay vardır:

  • onSuccess(T result), T türünde bir sonucun hazır olduğunu bildirir. Asenkron işlem birden fazla sonuç verebilir;
  • onError(Throwable th): Asenkron işlemin bir hatayla karşılaştığını bildirir;
  • onCompleted(): Asenkron işlemin tamamlandığını bildirir;

[Observable.subscribe] yöntemi çağrılmadıkça, gözlemlenebilir ile ilişkili asenkron işlem başlatılmaz. [IRxElectionsMetier] arayüzünün M yöntemini çağıran kod, beklenen T sonucunu almaz, ancak daha sonra [Observable.subscribe] yöntemini çağırarak T sonucunu almasını sağlayacak bir Observable<T> türü alır.

[IRxElectionsMetier] arayüzünün [RxElectionsMetier] uygulaması şu şekildedir:


package elections.security.client.metier;

import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rx.Observable;

@Component
public class RxElectionsMetier implements IRxElectionsMetier {

  @Autowired
  private IElectionsMetier metier;

  @Override
  public Observable<Void> authenticate(User user) {
    ...
  }

  @Override
  public Observable<ListeElectorale[]> getListesElectorales(User user) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı ve ardından aboneye yanıt
        subscriber.onNext(metier.getListesElectorales(user));
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletilir
        subscriber.onError(e);
      }
    });
  }

  @Override
  public Observable<Integer> getNbSiegesAPourvoir(User user) {
    ...
  }

  @Override
  public Observable<Double> getSeuilElectoral(User user) {
    ...
  }

  @Override
  public Observable<Void> recordResultats(User user, ListeElectorale[] listesElectorales) {
    ...
  }

  @Override
  public Observable<ListeElectorale[]> calculerSieges(User user, ListeElectorale[] listesElectorales) {
    ...
  }
}
  • 12-13. satırlar: senkron iş katmanının Spring enjeksiyonu;
  • 20-34. satırlar: [ListeElectorale[]] türü yerine [Observable<ListeElectorale[]>] türünü döndüren [getListesElectorales] yöntemini yorumlayacağız;
  • satır 22-32: statik [Observable.create] yöntemi, bir [Subscriber] türünden bir Observable oluşturulmasına olanak tanır. [Subscriber] türü, gözlemlenen süreç (Observable) tarafından üretilen sonuç akışlarına abone olan bir nesneyi temsil eder. Bu tür üç yöntem sunar:
    • [Subscriber.onNext] (satır 25), gözlemlenen süreçten bir sonuç almak için;
    • [Subscriber.onError] (satır 30), gözlemlenen süreçten bir istisna almak için kullanılır. Bir istisna sonrasında, [Observable] türü artık sonuç yaymaz;
    • [Subscriber.onCompleted] (satır 27), gözlemlenen sürecin yayın sonu sinyalini almak için kullanılır. Burada, gözlemlenen süreç yalnızca bir eleman yayınlar. Bir istisna meydana gelirse bu sinyalin yayınlanmayacağına dikkat edilmelidir. Bu, Gözlemlenebilirlerin varsayılan davranışıdır: bir istisnanın yayınlanması, aynı zamanda yayınların sonunu da işaret eder. Aboneler bunu bilir;
  • 22-34. satırlar: [Observable.create] yöntemi, parametre olarak [Observable.OnSubscribe] türünü kabul eder. Bu tür, işlevsel bir arayüzdür. Bu kavram Java 8 ile getirilmiştir ve tek bir yönteme sahip bir arayüzü ifade eder. Burada, [Observable.OnSubscribe] arayüzünün tek yöntemi şöyledir:
T call(Subscriber<T> subscriber)

Tek yöntemli m(param1, param2, ..., paramn) işlevsel bir arayüzü uygulamak için aşağıdaki basitleştirilmiş sözdizimi kullanılabilir:

(param1, param2, ..., paramn) -> { code de la méthode m}

22-34. satırlarda yapılan da budur:

  • [subscriber], [Observable.OnSubscribe.call] yönteminin parametresidir;
  • 23-32. satırlar: [call] yöntemine verilmek istenen kod;
  • 25. satır: 13. satırda enjekte edilen [métier] katmanından seçim listeleri senkronize bir şekilde talep edilir. Dolayısıyla sonuç beklenecektir. Sonuç alındığında, abonenin [onNext] yöntemine aktarılır;
  • 28. satır: Hata durumunda, istisna abonenin [onError] yöntemine aktarılır;
  • satır 31: Tek bir sonuç beklenir. Sonuç elde edildiğinde (seçmen listeleri veya bir istisna), aboneye gözlemlenen sürecin sonuç vermeyi tamamladığı bildirilir;

[RxElectionsMetier] yönteminin, ListeElectorale[] türünü değil, Observable<ListeElectorale[]> türünü döndürdüğü unutulmamalıdır. 23-33. satırlardaki kodun çalıştırılması ve 25. satır aracılığıyla seçmen listelerinin döndürülmesi için, çağıran kodun Observable<ListeElectorale[]>.subscribe yöntemini çağırması gerekir.

Diğer yöntemlerin kodları da benzerdir:


package elections.security.client.metier;

import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rx.Observable;

@Component
public class RxElectionsMetier implements IRxElectionsMetier {

  @Autowired
  private IElectionsMetier metier;

  @Override
  public Observable<Void> authenticate(User user) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı
        metier.authenticate(user);
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletiliyor
        subscriber.onError(e);
      }
    });

  }

  @Override
  public Observable<ListeElectorale[]> getListesElectorales(User user) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı ve ardından aboneye yanıt
        subscriber.onNext(metier.getListesElectorales(user));
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletilir
        subscriber.onError(e);
      }
    });
  }

  @Override
  public Observable<Integer> getNbSiegesAPourvoir(User user) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı ve ardından aboneye yanıt
        subscriber.onNext(metier.getNbSiegesAPourvoir(user));
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletilir
        subscriber.onError(e);
      }
    });
  }

  @Override
  public Observable<Double> getSeuilElectoral(User user) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı ve ardından aboneye yanıt
        subscriber.onNext(metier.getSeuilElectoral(user));
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletilir
        subscriber.onError(e);
      }
    });
  }

  @Override
  public Observable<Void> recordResultats(User user, ListeElectorale[] listesElectorales) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı
        metier.recordResultats(user, listesElectorales);
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletiliyor
        subscriber.onError(e);
      }
    });
  }

  @Override
  public Observable<ListeElectorale[]> calculerSieges(User user, ListeElectorale[] listesElectorales) {
    return Observable.create(subscriber -> {
      try {
        // senkron yöntem çağrısı ve ardından aboneye yanıt
        subscriber.onNext(metier.calculerSieges(user, listesElectorales));
        // gözlemlenebilirin sonu bildirilir
        subscriber.onCompleted();
      } catch (Exception e) {
        // istisna iletilir
        subscriber.onError(e);
      }
    });
  }
}
  • 20. ve 81. satırlar: abone, sonuç beklemediği için [onNext] yöntemi çağrılmaz;

20.1.4. [métier] katmanındaki JUnit testleri

  

20.1.4.1. Test01

17.4.4. paragrafında incelenen [Test01] birim testine geri dönüyoruz. Bu test, [IElectionsMetier] arayüzüne senkron çağrılar yapmak üzere tasarlanmıştır. Bu testi, yeni [IRxElectionsMetier] arayüzüne senkron çağrılar yapacak şekilde değiştiriyoruz. Aslında, asenkron bir arayüze (RxJava) senkron çağrılar yapmak mümkündür. Kod şu şekilde olur:


package elections.security.client.metier.junit;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.security.client.config.MetierConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IRxElectionsMetier;
import rx.observables.BlockingObservable;

@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

  // katman [electionsMetier]
  @Autowired
  private IRxElectionsMetier electionsMetier;

  // jSON eşleştiricisi
  private final ObjectMapper mapper = new ObjectMapper();

  // kullanıcılar
  static private User admin;
  static private User user;
  static private User unknown;

  @BeforeClass
  public static void initTest() {
    admin = new User("admin", "admin");
    user = new User("user", "user");
    unknown = new User("x", "y");
  }

  @Test()
  public void checkUserUser() {
    ElectionsException se = null;
    try {
      BlockingObservable.from(electionsMetier.authenticate(user)).firstOrDefault(null);
    } catch (ElectionsException e) {
      se = e;
    }
    Assert.assertNotNull(se);
    Assert.assertEquals("403 Forbidden", se.getErreurs().get(0));
  }

  @Test()
  public void checkUserUnknown() {
    ElectionsException se = null;
    try {
      BlockingObservable.from(electionsMetier.authenticate(unknown)).firstOrDefault(null);
    } catch (ElectionsException e) {
      se = e;
    }
    Assert.assertNotNull(se);
    Assert.assertEquals("401 Unauthorized", se.getErreurs().get(0));
  }

  @Test()
  public void checkUserAdmin() {
    ElectionsException se = null;
    try {
      BlockingObservable.from(electionsMetier.authenticate(admin)).firstOrDefault(null);
    } catch (ElectionsException e) {
      se = e;
    }
    Assert.assertNull(se);
  }

  /**
   * vérification 1 : méthode de calcul des sièges on fixe en dur les listes
   */
  @Test
  public void calculSieges1() {
    // 7 aday listesinden oluşan tablo oluşturulur
    ListeElectorale[] listes = new ListeElectorale[7];
    listes[0] = new ListeElectorale("A", 32000, 0, false);
    listes[1] = new ListeElectorale("B", 25000, 0, false);
    listes[2] = new ListeElectorale("C", 16000, 0, false);
    listes[3] = new ListeElectorale("D", 12000, 0, false);
    listes[4] = new ListeElectorale("E", 8000, 0, false);
    listes[5] = new ListeElectorale("F", 4500, 0, false);
    listes[6] = new ListeElectorale("G", 2500, 0, false);
    // her listenin koltuk sayısı hesaplanır
    listes = BlockingObservable.from(electionsMetier.calculerSieges(admin, listes)).first();
    // sonuçlar kontrol ediliyor
    Assert.assertEquals(2, listes[0].getSieges());
    Assert.assertFalse(listes[0].isElimine());
    Assert.assertEquals(2, listes[1].getSieges());
    Assert.assertFalse(listes[1].isElimine());
    Assert.assertEquals(1, listes[2].getSieges());
    Assert.assertFalse(listes[2].isElimine());
    Assert.assertEquals(1, listes[3].getSieges());
    Assert.assertFalse(listes[3].isElimine());
    Assert.assertEquals(0, listes[4].getSieges());
    Assert.assertFalse(listes[4].isElimine());
    Assert.assertEquals(0, listes[5].getSieges());
    Assert.assertTrue(listes[5].isElimine());
    Assert.assertEquals(0, listes[6].getSieges());
    Assert.assertTrue(listes[6].isElimine());
  }

  /**
   * vérification 2 : méthode de calcul des sièges on demande les listes à la couche [metier] puis on fixe en dur les
   * voix
   */
  @Test
  public void calculSieges2() {
    // 7 aday listesinin tablosu oluşturulur
    ListeElectorale[] listes = BlockingObservable.from(electionsMetier.getListesElectorales(admin)).first();
    // oylar sabitlenir
    listes[0].setVoix(32000);
    listes[1].setVoix(25000);
    listes[2].setVoix(16000);
    listes[3].setVoix(12000);
    listes[4].setVoix(8000);
    listes[5].setVoix(4500);
    listes[6].setVoix(2500);
    // her bir listenin kazandığı sandalye sayısı hesaplanır
    listes = BlockingObservable.from(electionsMetier.calculerSieges(admin, listes)).first();
    // sonuçlar kontrol ediliyor
    Assert.assertEquals(2, listes[0].getSieges());
    Assert.assertFalse(listes[0].isElimine());
    Assert.assertEquals(2, listes[1].getSieges());
    Assert.assertFalse(listes[1].isElimine());
    Assert.assertEquals(1, listes[2].getSieges());
    Assert.assertFalse(listes[2].isElimine());
    Assert.assertEquals(1, listes[3].getSieges());
    Assert.assertFalse(listes[3].isElimine());
    Assert.assertEquals(0, listes[4].getSieges());
    Assert.assertFalse(listes[4].isElimine());
    Assert.assertEquals(0, listes[5].getSieges());
    Assert.assertTrue(listes[5].isElimine());
    Assert.assertEquals(0, listes[6].getSieges());
    Assert.assertTrue(listes[6].isElimine());
  }

  /**
   * vérification 3 méthode de calcul des sièges on provoque une exception
   */
  @Test(expected = ElectionsException.class)
  public void calculSieges3() {
    // her biri 1 oy alan 24 aday listesinden oluşan bir tablo oluşturulur
    ListeElectorale[] listes = new ListeElectorale[25];
    // 25 listenin hepsi aynı sayıda oy alacak (%4)
    for (int i = 0; i < listes.length; i++) {
      listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
    }
    // Milletvekili sandalye dağılımı hesaplaması - normalde şu sonuç çıkmalıdır: ElectionsException
    // %5 seçim barajı ile
    BlockingObservable.from(electionsMetier.calculerSieges(admin, listes)).first();
  }

  /**
   * enregistrement des résultats de l'élection
   *
   * @throws JsonProcessingException
   */
  @Test
  public void ecritureResultatsElections() throws JsonProcessingException {
    // 7 aday listenin tablosu oluşturulur
    ListeElectorale[] listes = BlockingObservable.from(electionsMetier.getListesElectorales(admin)).first();
    // oylar sabitlenir
    listes[0].setVoix(32000);
    listes[1].setVoix(25000);
    listes[2].setVoix(16000);
    listes[3].setVoix(12000);
    listes[4].setVoix(8000);
    listes[5].setVoix(4500);
    listes[6].setVoix(2500);
    // her listenin kazandığı sandalye sayısı hesaplanıyor
    listes = BlockingObservable.from(electionsMetier.calculerSieges(admin, listes)).first();
    // sonuçlar görüntülenir
    for (int i = 0; i < listes.length; i++) {
      System.out.println(mapper.writeValueAsString(listes[i]));
    }
    // sonuçlar veritabanına kaydedilir
    BlockingObservable.from(electionsMetier.recordResultats(admin, listes)).firstOrDefault(null);
    // sonuçlar kontrol edilir
    listes = BlockingObservable.from(electionsMetier.getListesElectorales(admin)).first();
    // sonuçlar görüntülenir
    for (int i = 0; i < listes.length; i++) {
      System.out.println(mapper.writeValueAsString(listes[i]));
    }
    Assert.assertEquals(2, listes[0].getSieges());
    Assert.assertFalse(listes[0].isElimine());
    Assert.assertEquals(2, listes[1].getSieges());
    Assert.assertFalse(listes[1].isElimine());
    Assert.assertEquals(1, listes[2].getSieges());
    Assert.assertFalse(listes[2].isElimine());
    Assert.assertEquals(1, listes[3].getSieges());
    Assert.assertFalse(listes[3].isElimine());
    Assert.assertEquals(0, listes[4].getSieges());
    Assert.assertFalse(listes[4].isElimine());
    Assert.assertEquals(0, listes[5].getSieges());
    Assert.assertTrue(listes[5].isElimine());
    Assert.assertEquals(0, listes[6].getSieges());
    Assert.assertTrue(listes[6].isElimine());
  }
}

Değişiklikleri inceleyelim:

  • 48. satır: [BlockingObservable.from(Observable).first] statik yöntemi:
    • [from]'in gözlemlenebilir parametresine abone olur;
    • gözlemlenebilir ile ilişkili kodun yürütülmesini başlatır;
    • ilk sonucu almayı bekler. Dolayısıyla bu senkron bir işlemdir;

Burada [firstOrDefault(null)] yöntemini kullanıyoruz çünkü [metier.authenticate] gözlemlenebilir, yürütüldüğünde sonuç döndürmüyor. Dolayısıyla, [firstOrDefault(null)] yönteminin sonucu null olacaktır; bu değer burada kullanılmayacaktır;

[métier] katmanını her çağırmak istediğimizde, kodun geri kalanında da bu şemayı kullanıyoruz.

[Test01] birim testi başarılı olmalıdır:

 

Yapılması gereken iş: [Test01] testinin başarılı olduğunu doğrulamak.


20.1.4.2. Test02

[Test01] testini, artık [IRxElectionsMetier] asenkron arayüzünü, yöntemlerine asenkron çağrılar yaparak test edecek şekilde dönüştürüyoruz.

İlk testi inceleyelim:


  // iş parçacıkları senkronizasyon semaforu
  private CountDownLatch latch;

  // -----------------------------------
  private ElectionsException checkUserUserException;

  @Test()
  public void checkUserUser() throws InterruptedException {
    // 1 değerindeki semafor
    latch = new CountDownLatch((1));
    // asenkron işlem
    electionsMetier.authenticate(user).subscribeOn(Schedulers.io())
            .subscribe((result) -> {
            },
                    (th) -> {
                      checkUserUserException = (ElectionsException) th;
                      latch.countDown();
                    },
                    () -> {
                      latch.countDown();
                    });
    // semafor bekleme
    latch.await();
    // sonuçların doğrulanması
    Assert.assertNotNull(checkUserUserException);
    Assert.assertEquals("403 Forbidden", checkUserUserException.getErreurs().get(0));
}
  • 2. satır: Semafor, iş parçacıkları arasında senkronizasyon sağlamak için kullanılan bir araçtır. İş parçacıkları, paralel olarak çalışan yürütme akışlarıdır. Bir T1 görevini yürütmek için, [Thread1] iş parçacığı, [Thread2] iş parçacığı tarafından yürütülen bir T2 görevinin tamamlanmasını bekleyebilir. Bu durumda, [Thread2] iş parçacığının, T2 görevinin tamamlandığını belirten bir sinyal göndermesini bekler. İki iş parçacığı arasındaki bu senkronizasyonu yönetmenin çeşitli yolları vardır. Burada kullanılan yöntem şöyledir:
    • 10. satır: [Thread1] iş parçacığı, değeri 1 olan bir semafor oluşturur;
    • 12. satır: [Thread1] iş parçacığı, [Thread2] iş parçacığını oluşturur ve başlatır. Bu, şu sözdizimi ile gerçekleştirilir:

electionsMetier.authenticate(user).subscribeOn(Schedulers.io())

[Observable.subscribeOn] yöntemi, gözlemlenen işlemin üzerinde çalışacağı iş parçacığını belirler. [subscribeOn] parametresi bir iş parçacığı havuzudur. RxJava kütüphanesi, farklı durumlara uygun çeşitli havuzlar sunar. [Schedulers.io()] havuzu, ağ işlemleri için önerilen havuzdur;

  • (devamı)
    • 12-13. satırlar:

electionsMetier.authenticate(user).subscribeOn(Schedulers.io()).subscribe(...)

işlemi, [authenticate(user)] gözlemlenebilirinde kapsüllenmiş senkron işlemi yürütür. Ancak bu senkron işlem, [Thread1] iş parçacığından farklı bir iş parçacığında başlatıldığından, [Thread1] iş parçacığı [subscribe] yönteminden yanıt beklemeden bir sonraki komuta geçer;

  • (devamı)
    • 23. satır: [Thread1] iş parçacığı durur ve semaforun 0'a düşmesini bekler (şu anda 1'dir);
  • 13-21. satırlar: [subscribe] yöntemi, parametre olarak üç lambda fonksiyonu alır:
    • birinci lambda işlevi [(result)->{...}], gözlemlenebilir [authenticate(user)] bir [result] sonucu yayınladığında her seferinde çağrılır. Burada, bir işlem yapan ancak hiçbir sonuç yayınlamayan [authenticate(user)] adlı bir gözlemlenebilirimiz var. Dolayısıyla [(result)->{}] lambda fonksiyonu hiçbir zaman çağrılmayacaktır. Bu nedenle kod kısmı boştur: [{}];
    • ikincisi olan [(th)->{...}] ise parametre olarak [Throwable] türünü alır. Bu, gözlemlenebilirin yürütülmesi sırasında bir istisna ile karşılaşıldığında çağrılır. Burada, [Throwable th] parametresini şu şekilde işliyoruz:
      • 16. satır: Yürütülen gözlemlenebilir nesne yalnızca bu tür bir istisna ürettiği için, onu [ElectionsException] türündeki test sınıfının bir alanına kaydediyoruz;
      • 17. satır: [Thread2] iş parçacığının işini tamamladığını belirtmek için semaforu 0'a ayarlıyoruz;
    • Üçüncü [()->{...}], gözlemlenebilirin yayınlayacak öğesi kalmadığında çağrılır. Bu olayı şu şekilde işliyoruz:
      • 20. satır: [Thread2] iş parçacığının işini tamamladığını belirtmek için semaforu 0'a ayarlıyoruz;

Bir istisna meydana gelirse üçüncü lambda işlevinin çağrılmayacağına dikkat edilmelidir. Bu nedenle, 17. satırda da semaforu 0'a ayarlamak zorunda kaldık;

  • 25. satır: Bu satıra geldiğimizde, gözlemlenebilir nesne işini tamamlamıştır. Bu durumda, [Test01] testinde olduğu gibi aynı kontrolleri yapabiliriz;

Başka bir testi inceleyelim:


// -----------------------------------
  private ElectionsException calculSieges1Exception;
  private ListeElectorale[] listesCalculSieges1;

  @Test
  public void calculSieges1() throws InterruptedException {
    // 7 aday listeden oluşan tablo oluşturuluyor
    ListeElectorale[] listes = new ListeElectorale[7];
    listes[0] = new ListeElectorale("A", 32000, 0, false);
    listes[1] = new ListeElectorale("B", 25000, 0, false);
    listes[2] = new ListeElectorale("C", 16000, 0, false);
    listes[3] = new ListeElectorale("D", 12000, 0, false);
    listes[4] = new ListeElectorale("E", 8000, 0, false);
    listes[5] = new ListeElectorale("F", 4500, 0, false);
    listes[6] = new ListeElectorale("G", 2500, 0, false);
    // semafor 1'e ayarlandı
    latch = new CountDownLatch((1));
    // asenkron işlem    
    // her listenin koltuk sayısı hesaplanıyor
    electionsMetier.calculerSieges(admin, listes).subscribeOn(Schedulers.io())
            .subscribe((result) -> {
              listesCalculSieges1 = result;
            },
                    (th) -> {
                      calculSieges1Exception = (ElectionsException) th;
                      latch.countDown();
                    },
                    () -> {
                      latch.countDown();
                    });
    // semafor bekleme
    latch.await();
    // sonuçlar kontrol ediliyor
    Assert.assertNull(calculSieges1Exception);
    Assert.assertEquals(2, listesCalculSieges1[0].getSieges());
    Assert.assertFalse(listesCalculSieges1[0].isElimine());
    Assert.assertEquals(2, listesCalculSieges1[1].getSieges());
    Assert.assertFalse(listesCalculSieges1[1].isElimine());
    Assert.assertEquals(1, listesCalculSieges1[2].getSieges());
    Assert.assertFalse(listesCalculSieges1[2].isElimine());
    Assert.assertEquals(1, listesCalculSieges1[3].getSieges());
    Assert.assertFalse(listesCalculSieges1[3].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[4].getSieges());
    Assert.assertFalse(listesCalculSieges1[4].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[5].getSieges());
    Assert.assertTrue(listesCalculSieges1[5].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[6].getSieges());
    Assert.assertTrue(listesCalculSieges1[6].isElimine());
  }
  • 20-30. satırlar: [electionsMetier.calculerSieges(admin, listes)] gözlemlenebilirinin asenkron yürütülmesi;
  • 21-23. satırlar: gözlemlenebilirin yürütülmesi [ListeElectorale[]] türünde bir değer döndürür; bu değer, 3. satırdaki test sınıfının bir alanına kaydedilir;
  • satır 34-48: Bu doğrulamalar, [Test01] testine ait doğrulamalardır; bunlara, istisna oluşmadığından emin olmak için satır 34'teki doğrulama eklenmiştir;

[Test02] testinin tamamı ders materyallerinde mevcuttur.


Yapılacak ödev: [Test02] testini çalıştırın ve testin başarılı olduğunu doğrulayın.


20.1.4.3. Test03

[Test03] testi, [Test01] testiyle aynı işlevi görür: [IRxElectionsMetier] arayüzünü, bu arayüze yapılan senkron çağrılarla test eder. Bu, [Test02] testinin iki ayrıntı dışında bir kopyasıdır:

  • gözlemlenebilirler artık testleri çalıştıran iş parçacığından farklı bir iş parçacığında çalıştırılmıyor. [Thread1] iş parçacığı, bir gözlemlenebilirin [subscribe] yöntemini çalıştırdığında, bu yöntem de yine [Thread1] iş parçacığı üzerinde sunucuya yönelik bir HTTP işlemi başlatır. Böylece [subscribe] yönteminin tamamı senkron hale gelir;
  • artık tek bir iş parçacığı kaldığı için iş parçacığı senkronizasyonu gereksiz hale gelir ve semafor ortadan kalkar;

İşte iki test örneği:


  // -----------------------------------
  private ElectionsException checkUserUserException;

  @Test()
  public void checkUserUser() throws InterruptedException {
    // senkron işlem
    electionsMetier.authenticate(user)
            .subscribe((result) -> {
            },
                    (th) -> {
                      checkUserUserException = (ElectionsException) th;
                    },
                    () -> {
                    });
    // sonuçların doğrulanması
    Assert.assertNotNull(checkUserUserException);
    Assert.assertEquals("403 Forbidden", checkUserUserException.getErreurs().get(0));
}
  • 7. satır: Varsayılan olarak, [electionsMetier.authenticate(user).subscribe] yöntemi çağıran kodun iş parçacığında yürütülür. Dolayısıyla senkron bir işlem söz konusudur;

  // -----------------------------------
  private ElectionsException calculSieges1Exception;
  private ListeElectorale[] listesCalculSieges1;

  @Test
  public void calculSieges1() throws InterruptedException {
    // 7 aday listesinin tablosu oluşturuluyor
    ListeElectorale[] listes = new ListeElectorale[7];
    listes[0] = new ListeElectorale("A", 32000, 0, false);
    listes[1] = new ListeElectorale("B", 25000, 0, false);
    listes[2] = new ListeElectorale("C", 16000, 0, false);
    listes[3] = new ListeElectorale("D", 12000, 0, false);
    listes[4] = new ListeElectorale("E", 8000, 0, false);
    listes[5] = new ListeElectorale("F", 4500, 0, false);
    listes[6] = new ListeElectorale("G", 2500, 0, false);
    // senkron işlem    
    // her listenin sandalye sayısı hesaplanıyor
    electionsMetier.calculerSieges(admin, listes)
            .subscribe((result) -> {
              listesCalculSieges1 = result;
            },
                    (th) -> {
                      calculSieges1Exception = (ElectionsException) th;
                    },
                    () -> {
                    });
    // sonuçlar kontrol ediliyor
    Assert.assertNull(calculSieges1Exception);
    Assert.assertEquals(2, listesCalculSieges1[0].getSieges());
    Assert.assertFalse(listesCalculSieges1[0].isElimine());
    Assert.assertEquals(2, listesCalculSieges1[1].getSieges());
    Assert.assertFalse(listesCalculSieges1[1].isElimine());
    Assert.assertEquals(1, listesCalculSieges1[2].getSieges());
    Assert.assertFalse(listesCalculSieges1[2].isElimine());
    Assert.assertEquals(1, listesCalculSieges1[3].getSieges());
    Assert.assertFalse(listesCalculSieges1[3].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[4].getSieges());
    Assert.assertFalse(listesCalculSieges1[4].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[5].getSieges());
    Assert.assertTrue(listesCalculSieges1[5].isElimine());
    Assert.assertEquals(0, listesCalculSieges1[6].getSieges());
    Assert.assertTrue(listesCalculSieges1[6].isElimine());
  }

Yapılması gereken iş: [Test03] testini çalıştırın ve başarılı olup olmadığını kontrol edin.


20.2. 2. adım

Şimdi, 17.5. bölümdeki senkron konsol uygulamasını, yine senkron ancak [RxJava, metier, DAO] asenkron arayüzünü kullanan bir uygulamaya dönüştürüyoruz;

17.5. bölümdeki [elections-console-metier-dao-security-webjson] [1] projesinden yola çıkarak, bunu yeni bir proje olan [elections-console-rxjava- metier-dao-security-webjson] [2]'e kopyalıyoruz:

  • [3-4]'e aktarıyoruz; yeni projede eski senkron katman [métier]'e olan bağımlılığı kaldırıyoruz;
  • [5-9]'te, yeni asenkron [métier] katmanına bir bağımlılık eklenir;
  • [10-14]'te, [ElectionsConsole] sınıfının adı [ElectionsConsole01] olarak değiştirilir;

Aynı şekilde, [BootElectionsConsole] sınıfının adı [BootElectionsConsole01] olarak değiştirilir:

 

[BootElectionsConsole01] sınıfının mevcut kodu şöyledir:


package elections.security.client.boot;

import elections.security.client.console.IElectionsUI;


public class BootElectionsConsole01 extends AbstractBootElections{
    public static void main(String[] arguments) {
        new BootElectionsConsole01().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConsole",IElectionsUI.class);
    }
}
  • 13. satır: [ElectionsConsole] sınıfının adını [ElectionsConsole01] olarak değiştirdiğimiz için, artık şu şekilde yazılmalıdır:

        return ctx.getBean("electionsConsole01",IElectionsUI.class);

[ElectionsConsole01] sınıfının koduna geri dönelim:


@Component
public class ElectionsConsole01 implements IElectionsUI {

    @Autowired
    private IElectionsMetier electionsMetier;

  @Autowired
  private User admin;
  
    @Override
    public void run() {
        // yarışan listeler
        ListeElectorale[] listes;
        // verilerin girilmesi
        try (Scanner clavier = new Scanner(System.in)) {
         // [metier] katmanından yarışan listeler isteniyor
         listes = electionsMetier.getListesElectorales(admin);
            ...
        // koltuk sayısı hesaplanıyor
        listes=electionsMetier.calculerSieges(admin,listes);
        // sonuçlar kaydediliyor
        electionsMetier.recordResultats(admin,listes);
        ...
}

20.1.4.1 numaralı paragrafta yer alan [Test01] test örneğini takip edersek, 5, 17, 20 ve 22. satırlar şu şekilde değişecektir:


@Component
public class ElectionsConsole01 implements IElectionsUI {

  @Autowired
  private IRxElectionsMetier electionsMetier;

  @Autowired
  private User admin;

  @Override
  public void run() {
    // yarışmadaki listeler
    ListeElectorale[] listes;
    // verilerin girilmesi
    try (Scanner clavier = new Scanner(System.in)) {
      // [metier] katmanından yarışan listeler isteniyor
      listes = BlockingObservable.from(electionsMetier.getListesElectorales(admin)).first();
      ...
    // koltuk sayısı hesaplanıyor
    listes = BlockingObservable.from(electionsMetier.calculerSieges(admin, listes)).first();
    // sonuçlar kaydediliyor
    BlockingObservable.from(electionsMetier.recordResultats(admin, listes));
    ...
  }

Yapılacak iş: Projeyi, [BootElectionsConsole01] sınıfını [SS, Heures travaillées, Jours travaillés] parametreleriyle çalıştıracak şekilde yapılandırın ve bu şekilde yapılandırılmış projenin çalıştırılmasının beklenen sonuçları verdiğini doğrulayın.



Yapılması gereken: Projeyi, [BootElectionsConsole02, ElectionsConsole02] çiftini çalıştıracak şekilde yapılandırın; burada [ElectionsConsole02] sınıfı, 20.1.4.2 paragrafındaki [Test02] test modeline göre yazılmış olmalıdır.



Yapılacak iş: 20.1.4.3. paragrafındaki [Test03] testinin şablonuna göre yazılmış olan [ElectionsConsole03] sınıfını içeren [BootElectionsConsole03, ElectionsConsole03] çiftini çalıştıracak şekilde projeyi yapılandırın.


20.3. 3. adım

Şimdi Swing uygulamasını asenkron bir ortama taşıma aşamasına geçiyoruz.

İlk olarak, 17.6. bölümdeki [elections-swing-metier-dao-security-webjson] [1] projesini, yeni bir [elections-swing-rxjava-metier-dao-security-webjson] [2] projesine kopyalayacağız:

  • [3, 4]'te, senkron [console] katmanına olan bağımlılığı kaldırıyoruz;
  • [5-9]'te, asenkron konsol katmanına bir bağımlılık ekliyoruz;

[swing] katmanı, [métier] katmanına gerçek asenkron çağrılar yapacaktır. Bu katmandaki bir yönteme çağrı yapıldığında, iki iş parçacığı olacaktır:

  • olayları yöneten UI iş parçacığı;
  • sunucuya HTTP çağrısını gerçekleştirecek bir G/Ç iş parçacığı;

Asenkron çağrı süresince, bir bekleme görüntüsü ve bir iptal düğmesi göstermemiz gerekir. Bunu burada yapmayacağız ve bu, uygulamanın iyileştirilmesi için size önerilecektir. Değişiklikler, [métier] katmanına çağrı yapan iki sınıfta yapılır:

 

20.3.1. Maven Yapılandırması

Burada, [RxJava] kütüphanesine yalnızca Swing ortamında kullanılabilen işlevler ekleyen [RxSwing] kütüphanesini kullanacağız. Bunun için [pom.xml] dosyasını şu şekilde değiştiriyoruz:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>istia.st.elections</groupId>
  <artifactId>elections-swing-rxjava-metier-dao-security-webjson</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>elections-swing-rxjava-metier-dao-security-webjson</name>
  <description>couche swing asynchrone du client web / jSON</description>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- RxSwing -->
    <!-- https://mvnrepository.com/artifact/io.reactivex/rxswing -->
    <dependency>
      <groupId>io.reactivex</groupId>
      <artifactId>rxswing</artifactId>
      <version>0.27.0</version>
    </dependency>
    <!-- alt katmanlar -->
    <dependency>
      <groupId>istia.st.elections</groupId>
      <artifactId>elections-console-rxjava-metier-dao-security-webjson</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>

</project>

20.3.2. [ElectionsConnectForm] sınıfı

Asenkron çalışmada, [ElectionsConnectForm] sınıfı şu şekilde olur:


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.User;

import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.SwingUtilities;

import elections.security.client.metier.IRxElectionsMetier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rx.schedulers.Schedulers;
import rx.schedulers.SwingScheduler;

@Component
public class ElectionsConnectForm extends AbstractElectionsConnectForm implements IElectionsUI {

  private static final long serialVersionUID = 1L;

  // [métier] asenkron katman referansı
  @Autowired
  private IRxElectionsMetier metier;

  // oturum açmış kullanıcı
  private User user;

  // ana form
  @Autowired
  private ElectionsMainForm electionsMainForm;

  // oturum UI
  @Autowired
  private UiSession uiSession;

  @Override
  protected void doConnect() {
    if (isPageValid()) {
      // kullanıcı kimlik doğrulaması
      metier.authenticate(user).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance()).subscribe(
        // yanıt yok
        (result) -> {
        },
        // istisna yönetimi
        (th) -> {
          // hata kaydediliyor
          String info = getInfoForException("Les erreurs suivantes se sont produites :", th);
          // bilgi görüntüleniyor
          jTextPaneErreurs.setText(info);
          jTextPaneErreurs.setCaretPosition(0);

        },
        // kimlik doğrulama tamamlandı
        () -> {
          // kullanıcı oturumda kaydedilir
          uiSession.setUser(user);
          // giriş ekranı gizlenir
          setVisible(false);
          // ana sayfa görüntüleniyor
          electionsMainForm.run();
        });
    }
  }

  // başlatma işlemleri
  @Override
  protected void init() {
    ...
  }

  @Override
  public void run() {
    // grafik kullanıcı arayüzü görüntülenir
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
  }

  private boolean isPageValid() {
    ...
  }

  private String getInfoForException(String message, Throwable ex) {
    ...
  }

}
  • 36-63. satırlar: Kullanıcı [Connexion] menü seçeneğine tıkladığında [doConnect] yöntemi yürütülür:
 

Her şey 40. satırda yer almaktadır:


      metier.authenticate(user).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance()).subscribe(...)
  • Gözlemlenen işlem [metier.authenticate(user)]'tir;
  • bu işlem, [Schedulers.io()] havuzundan alınan bir G/Ç iş parçacığı üzerinde çalıştırılacaktır;
  • UI iş parçacığında gözlemlenecek; bu iş parçacığı, Swing arayüzü [observeOn(SwingScheduler.getInstance())]'in olaylarını yönetir. Bu iş parçacığı, [SwingScheduler.getInstance()] yöntemi ile elde edilir; burada [SwingScheduler], [RxSwing] kütüphanesi tarafından sağlanan bir sınıftır. Bu zorunludur. Asenkron işlemin sonucu alındığında, bu sonuç genellikle Swing arayüzündeki öğeleri değiştirmek için kullanılır. Ancak bu arayüz yalnızca UI iş parçacığında değiştirilebilir; aksi takdirde bir istisna oluşur. Bu nedenle, 41-61. satırların UI iş parçacığında yürütülmesi gerekir. Bu, burada [observeOn(SwingScheduler.getInstance())] yöntemi ile sağlanmaktadır;

Kodun geri kalanını açıklayalım:

  • 42-43. satırlar: Bu satırlar, [subscribe] yönteminin sözdizimine uymak için eklenmiştir. [metier.authenticate(user)] işlemi hiçbir sonuç döndürmediğinden, bu satırlar asla çalıştırılmayacaktır;
  • 35-52. satırlar: Bir istisna alındığında, bu istisna görüntülenir;
  • 54-61. satırlar: [metier.authenticate(user)] işlemi, veri gönderimini tamamladığını bildirdiğinde yürütülür;

20.3.3. [ElectionsMainForm] sınıfı

 

20.3.3.1. Grafik kullanıcı arayüzünün başlatılması


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IRxElectionsMetier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rx.schedulers.Schedulers;
import rx.schedulers.SwingScheduler;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {

  private static final long serialVersionUID = 1L;

  // asenkron [métier] katmanına referans
  @Autowired
  private IRxElectionsMetier metier;

  // UI oturumu
  @Autowired
  private UiSession uiSession;

  // oturum açmış kullanıcı
  private User user;

  // JList listelerinin şablonları
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // yarışan listeler
  private ListeElectorale[] listes;

  // kullanıcı tarafından girilen listeler
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

  // başlatma işlemleri
  @Override
  protected void init() {
    // üst sınıf tarafından bileşenlerin oluşturulması
    super.init();
    // form durumu
    Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
    Utilitaires.setEnabled(
            new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
    // pencereyi ortala
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension frameSize = getSize();
    if (frameSize.height > screenSize.height) {
      frameSize.height = screenSize.height;
    }
    if (frameSize.width > screenSize.width) {
      frameSize.width = screenSize.width;
    }
    setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
    // oturum açmış kullanıcı
    user = uiSession.getUser();
    // yerel başlatmalar
    modèleNomsVoix = new DefaultListModel<>();
    jListNomsVoix.setModel(modèleNomsVoix);
    modèleRésultats = new DefaultListModel<>();
    jListResultats.setModel(modèleRésultats);
    // [métier] katmanından listeler isteniyor
    metier.getListesElectorales(user).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance())
            .subscribe(
                    // yanıt
                    listesElectorales -> {
                      // listeler kaydediliyor
                      listes = listesElectorales;
                    },
                    // istisna
                    (th) -> showException(th),
                    // gözlemlenebilir son
                    () -> {
                      // sonraki adım
                      doInitStep2();
                    });
  }
...
  • 46. satır: [init] yöntemi, ilgili pencere görüntülenecekken yürütülür. Bu yöntemin amacı, aşağıdaki [1-3] bileşenlerini başlatmaktır:
 
  • 71-85. satırlar: Aday listeler asenkron olarak istenir ([1] bileşeni);
  • 71. satır: Gözlemlenen işlem [metier.getListesElectorales(user)]'tir. Bu işlem, [subscribeOn(Schedulers.io())] G/Ç iş parçacığı üzerinde yürütülür ve UI ile [observeOn(SwingScheduler.getInstance()] iş parçacıkları üzerinden gözlemlenir;
  • 74-77. satırlar: Gözlemlenen işlem tarafından döndürülen sonuç, 38. satırdaki [listes] alanına kaydedilir;
  • 79. satır: Olası bir istisna, aşağıdaki yöntemle işlenir:

  private void showException(Throwable th) {
    // istisna görüntüleniyor
    jTextPaneMessages.setText(getInfoForException("Les erreurs suivantes se sont produites : ", th));
    jTextPaneMessages.setCaretPosition(0);
}
  • satır 81-84: Gözlemlenen işlemin sonunda, satır 81-84 yürütülür. Bir istisna meydana gelmişse bu satırlar yürütülmez. [doInitStep2] yöntemi, başlatmanın 2. adımını şu şekilde gerçekleştirir:

  private void doInitStep2() {
    // liste adlarını açılır menüye eşleştirme jComboBoxNomsListes
    for (int i = 0; i < listes.length; i++) {
      jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
    }
    // doldurulacak koltuk sayısı
    metier.getNbSiegesAPourvoir(user).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance())
            .subscribe(
                    // yanıt
                    nbSiegesAPourvoir -> {
                      // bu bilgiyle ilişkili etiket başlatılır
                      jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
                    },
                    // istisna
                    (th) -> showException(th),
                    // gözlemlenebilir son
                    () -> {
                      // sonraki adım
                      doInitStep3();
                    });
}
  • satır 3-5: Bir önceki adımın sonucu kullanılarak açılır menü, aday listelerin adlarıyla doldurulur;
  • 7-20. satırlar: doldurulacak koltuk sayısı asenkron olarak istenir;
  • 7. satır: Gözlemlenen işlem [metier.getNbSiegesAPourvoir(user)]'tir. Bu işlem, [subscribeOn(Schedulers.io())] G/Ç iş parçacığı üzerinde yürütülür ve UI ile [observeOn(SwingScheduler.getInstance()] iş parçacıkları üzerinden gözlemlenir;
  • 10-13. satırlar: İşlem tarafından döndürülen sonuç, grafik kullanıcı arayüzünü güncellemek için kullanılır;
  • 15. satır: Olası bir istisna görüntülenir;
  • satır 17-20: gözlemlenebilirin bitiş sinyali alındığında, başlatma sürecinin 3. aşamasına geçilir;

Başlatma işleminin 3. adımı aşağıdaki kodla gerçekleştirilir:


  private void doInitStep3() {
    // seçim barajı
    metier.getSeuilElectoral(user).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance())
            .subscribe(
                    // yanıt
                    seuilElectoral -> {
                      // bu bilgiyle ilişkili etiketi başlatıyoruz
                      jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
                    },
                    // istisna
                    (th) -> showException(th),
                    // gözlemlenebilir son
                    () -> {
                    });
}
  • 3-4. satırlar: seçim eşiği asenkron olarak istenir;
  • satır 3: gözlemlenen süreç [metier.getSeuilElectoral(user)]'tir. Bu süreç, [subscribeOn(Schedulers.io())] G/Ç iş parçacığı üzerinde yürütülür ve UI [observeOn(SwingScheduler.getInstance()] iş parçacığı üzerinde gözlemlenir;
  • 6-9. satırlar: İşlem tarafından döndürülen sonuç, grafik kullanıcı arayüzünü güncellemek için kullanılır;
  • 11. satır: Olası bir istisna görüntülenir;
  • 13-14. satırlar: Gözlemlenebilirin son sinyali alındığında herhangi bir işlem yapılmaz: Grafik arayüzün başlatma süreci tamamlanmıştır;

20.3.3.2. Farklı listelerin kazandığı koltuk sayısının hesaplanması

[doCalculer] yöntemi, farklı listelerin kazandığı koltuk sayısını hesaplamakla görevlidir:


  @Override
  protected void doCalculer() {
    tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
    // koltuk sayısı hesaplaması
    String info = null;
    metier.calculerSieges(user, tListesSaisies).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance())
            .subscribe(
                    // sonuç işleme
                    result -> consumeResultSieges(result),
                    // istisna işleme
                    th -> showException(th),
                    // gözlemlenebilir son
                    () -> {
                    }
            );
}
  • 6-15. satırlar: farklı listelerin kazandığı koltuk sayısı asenkron olarak hesaplanır;
  • 6. satır: Gözlemlenen işlem [metier.calculerSieges(user, tListesSaisies)]'tir. Bu işlem, [subscribeOn(Schedulers.io())] G/Ç iş parçacığı üzerinde yürütülür ve UI ile [observeOn(SwingScheduler.getInstance()] iş parçacıkları üzerinde gözlemlenir;
  • 9. satır: İşlem tarafından döndürülen sonuç, [consumeResultSieges] yöntemi tarafından kullanılır;
  • 11. satır: Olası bir istisna görüntülenir;
  • 13-14. satırlar: gözlemlenebilirin son sinyali alındığında herhangi bir işlem yapılmaz;

9. satırda, [consumeResultSieges] yöntemi, gözlemlenen işlem tarafından döndürülen sonucu ve [sieges, elimine] alanları güncellenmiş aday listeleri kullanır:


  private void consumeResultSieges(ListeElectorale[] tListesSaisies) {
    // sonuç kaydediliyor
    this.tListesSaisies = tListesSaisies;
    // sonuçların görüntülenmesi
    modèleRésultats.clear();
    for (int i = 0; i < tListesSaisies.length; i++) {
      modèleRésultats.addElement(tListesSaisies[i].toString());
    }
    // form durumunun güncellenmesi
    Utilitaires.setEnabled(new JLabel[]{jLabelEnregistrer}, true);
    Utilitaires.setEnabled(new JLabel[]{jLabelCalculer}, false);
    Utilitaires.setEnabled(new JMenuItem[]{jMenuItemEnregistrer}, true);
    Utilitaires.setEnabled(new JMenuItem[]{jMenuItemCalculer}, false);
    jTextPaneMessages.setText("Calcul terminé");
}
  • 4-14. satırlar: Elde edilen sonuç, grafik arayüzü güncellemek için kullanılır;

20.3.3.3. Seçim sonuçlarının kaydedilmesi

Seçim sonuçlarının kaydedilmesi, aşağıdaki [doEnregistrer] yöntemi ile gerçekleştirilir:


  @Override
  protected void doEnregistrer() {
    // [métier] katmanına kayıt talebi
    metier.recordResultats(user, tListesSaisies).subscribeOn(Schedulers.io()).observeOn(SwingScheduler.getInstance())
            .subscribe(
                    // sonucun işlenmesi - burada sonuç yok
                    (param) -> {
                    },
                    // istisna işleme
                    (th) -> showException(th),
                    // gözlemlenebilir son
                    () -> {
                      // form güncellemesi
                      Utilitaires.setEnabled(new JLabel[]{jLabelEnregistrer}, false);
                      Utilitaires.setEnabled(new JMenuItem[]{jMenuItemEnregistrer}, false);
                      jTextPaneMessages.setText("Enregistrement des résultats réalisé");
                    }
            );
}
  • 4-17. satırlar: Seçim sonuçları eşzamansız olarak kaydedilir;
  • 4. satır: Gözlemlenen işlem [metier.recordResultats(user, tListesSaisies)]'tir. Bu işlem, [subscribeOn(Schedulers.io())] G/Ç iş parçacığı üzerinde yürütülür ve UI [observeOn(SwingScheduler.getInstance()] iş parçacığı üzerinde gözlemlenir;
  • 7-8. satırlar: Gözlemlenen işlem herhangi bir sonuç döndürmediğinden bu satırlar hiçbir zaman yürütülmeyecektir;
  • 10. satır: Olası bir istisna görüntülenir;
  • 14-16. satırlar: gözlemlenebilir nesneden son sinyali alındığında, grafik arayüz güncellenir;

Yapılması gerekenler: Swing uygulamasının çalıştığını kontrol edin. Ardından, web sunucusu / jSON ile asenkron bir işlem sırasında bir bekleme resminin ve devam eden işlemi iptal etme seçeneğinin görünmesi için grafik arayüzü ve kodu geliştirin.