Skip to content

17. [TD]: تأمين خادم الويب / JSON للانتخابات

الكلمات المفتاحية: بنية متعددة المستويات، Spring، حقن التبعية، خدمة ويب آمنة / JSON، العميل / الخادم

سنطبق الآن ما تعلمناه في الفصل السابق على مهمة الانتخابات. ستكون البنية كما يلي:

ستتم العملية على النحو التالي:

  • كتابة الخادم؛
  • كتابة العميل بدون طبقة [ui] ولكن مع اختبار JUnit لطبقة [business
  • كتابة العميل مع طبقة [UI

17.1. الدعم

 

يمكن العثور على مشاريع هذا الفصل في المجلد [support / chap-17]. يُستخدم البرنامج النصي SQL لإنشاء قاعدة البيانات المطلوبة للاختبار.

17.2. قاعدة البيانات

يجب أن تحتوي قاعدة بيانات الخادم الآمن الآن على الجداول [USERS] و[ROLES] و[USERS_ROLES]:

 

يتوفر البرنامج النصي SQL لإنشاء قاعدة البيانات في قسم الدعم بالوثيقة.

17.3. الخادم الآمن

لإعداد خادم الانتخابات الآمن، ما عليك سوى نسخ ولصق المشروع النموذجي [intro-spring-security-server-01]:


المهمة: أعد تسمية الحزم كما هو موضح في [1].


بمجرد الانتهاء من ذلك، نقوم بتعديل ملف [pom.xml] على النحو التالي:


<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-security-webjson-metier-dao-spring-data</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <name>elections-security-webjson-metier-dao-spring-data</name>
    <description>elections spring security</description>
 
    <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>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-webjson-metier-dao-spring-data</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring security -->
        ...
    </dependencies>
    <!-- plugins -->
    <build>
        ...
    </build>

</project>
  • في الأسطر 23–27، استبدل التبعية لمشروع [intro-server-webjson-01] بالتبعية لمشروع [elections-webjson-metier-dao-spring-data] الذي تمت مناقشته في الفقرة 12؛
  • في الأسطر 4-7، أدخل خصائص المشروع الجديد؛

لا يزال يتعين علينا تعديل فئات تكوين المشروع:

[DaoConfig]


package elections.security.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 
@EnableJpaRepositories(basePackages = { "elections.security.repositories" })
@ComponentScan(basePackages = { "elections.security.dao" })
@Import({ elections.dao.config.DaoConfig.class })
public class DaoConfig {
 
    // constants
    final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
 
 
    @Bean
    public String[] packagesToScan() {
        return ENTITIES_PACKAGES;
    }
 
}

التغييرات موجودة في الأسطر التالية:

  • الأسطر 8 و9 و14: أدخل أسماء الحزم الصحيحة؛
  • السطر 10: أصبحت الفئة المستوردة الآن [elections.dao.config.DaoConfig] من مشروع [elections-webjson-metier-dao-spring-data

ملاحظة: كما ذكرنا، لا يعمل هذا التكوين إلا إذا كانت الفئة [elections.dao.config.DaoConfig] تحتوي على التعليق التوضيحي [@Configuration]. يرجى التحقق من ذلك.

[SecurityConfig]


package elections.security.config;
 
...
 
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    ...
}

التغييرات موجودة في الأسطر التالية:

  • السطر 6: تغيير اسم الحزمة؛

هذا كل شيء. قم بإجراء اختبار أولي عن طريق تنفيذ اختبار JUnit []:

  

قم بتشغيل فئة [Boot]، ثم استخدم ملحق Chrome [Advanced Rest Client] لإجراء الاختبار التالي:

في [1]، الطلب. في [3]، الاستجابة. في [2]، رأس HTTP هو رأس مصادقة المستخدم [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=

الآن اطلب القوائم المتنافسة:

17.4. عميل الخادم الآمن بدون طبقة [ui]

انسخ مشروع [elections-ui-metier-dao-webjson] والصقه في مشروع [elections-metier-dao-security-webjson].


المهمة: في [1]، قم بتغيير اسم الحزم إذا لزم الأمر وقم بإزالة تلك الموجودة في طبقة [ui] وفئات بدء التشغيل [boot].


سنمضي الآن كما هو موضح في القسم 16.5.

17.4.1. تكوين Maven

يتغير قسم تعريف المشروع فقط:


<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-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Client jUnit du serveur web / jSON</description>
    <name>elections-metier-dao-security-webjson</name>
...

17.4.2. إعادة هيكلة طبقة [الأعمال]

  

تتطور واجهة [IElectionsMetier] على النحو التالي:


package elections.security.client.metier;
 
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
 
public interface IElectionsMetier {
 
    // authentication
    public void authenticate(User user);
 
    // get the lists in competition
    public ListeElectorale[] getListesElectorales(User user);
 
    // the number of seats to be filled
    public int getNbSiegesAPourvoir(User user);
 
    // the electoral threshold
    public double getSeuilElectoral(User user);
 
    // recording results
    public void recordResultats(User user, ListeElectorale[] listesElectorales);
 
    // calculating seats
    public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
 
}

تحتوي جميع الطرق على المعلمة الأولى وهي المستخدم الذي يرغب في استخدام موارد الخادم الآمن.

يتطور تنفيذ [ElectionsMetier] على النحو التالي:


package elections.security.client.metier;
 
import java.io.IOException;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
 
import elections.security.client.dao.IClientDao;
import elections.security.client.entities.ElectionsConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;

@Component
public class ElectionsMetier implements IElectionsMetier {
 
  @Autowired
  private IClientDao dao;
  @Autowired
  private ApplicationContext context;
 
  // election configuration
  private ElectionsConfig electionsConfig;
 
  private ElectionsConfig getElectionsConfig(User user) {
    if(electionsConfig!=null){
      return electionsConfig;
    }
    // mappers jSON
    ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
    try {
      // request
      Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
              new TypeReference<Response<ElectionsConfig>>() {
      });
      // mistake?
      if (response.getStatus() != 0) {
        // 1 exception is thrown
        throw new ElectionsException(response.getStatus(), response.getMessages());
      } else {
        electionsConfig = response.getBody();
        return electionsConfig;
      }
    } catch (ElectionsException e1) {
      throw e1;
    } catch (IOException | RuntimeException e2) {
      throw new ElectionsException(100, e2);
    }
  }
 
  @Override
  public ListeElectorale[] getListesElectorales(User user) {
   ...
  }
 
  @Override
  public int getNbSiegesAPourvoir(User user) {
    return getElectionsConfig(user).getNbSiegesAPourvoir();
  }
 
  @Override
  public double getSeuilElectoral(User user) {
    return getElectionsConfig(user).getSeuilElectoral();
  }
 
  @Override
  public void recordResultats(User user, ListeElectorale[] listesElectorales) {
    ...
  }
 
  @Override
  public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales) {
    ...
  }
 
  @Override
  public void authenticate(User user) {
    dao.getResponse(user, "/authenticate", null);
  }
}

المهمة: أكمل الكود أعلاه.


17.4.3. تكوين Spring

  

تتطور فئة [MetierConfig] على النحو التالي:


package elections.security.client.config;
 
...
 
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {

السطر 5: يجب تحديث الحزم المراد فحصها.

17.4.4. اختبار JUnit لطبقة [الأعمال]

  

تتغير فئة الاختبار [Test01] على النحو التالي:


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.IElectionsMetier;
 
@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
 
    // layer [electionsMetier]
    @Autowired
    private IElectionsMetier electionsMetier;
 
    // mapper jSON
    private final ObjectMapper mapper = new ObjectMapper();
 
    // users
    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 {
            electionsMetier.authenticate(user);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("403 Forbidden", se.getErreurs().get(0));
    }
 
    @Test()
    public void checkUserUnknown() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(unknown);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("401 Unauthorized", se.getErreurs().get(0));
    }
 
    @Test()
    public void checkUserAdmin() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(admin);
        } 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() {
        // create the table of 7 candidate lists
        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);
        // the seats for each list are calculated
        listes = electionsMetier.calculerSieges(admin,listes);
        // check results
        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() {
        // create the table of 7 candidate lists
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // the voices are hard-fixed
        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);
        // the seats obtained by each list are calculated
        listes = electionsMetier.calculerSieges(admin,listes);
        // check results
        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() {
        // we create a table of 24 candidate lists, each with 1 vote
        ListeElectorale[] listes = new ListeElectorale[25];
        // all 25 lists will have the same number of votes (4%)
        for (int i = 0; i < listes.length; i++) {
            listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
        }
        // calculation of seats - normally there should be a ElectionsException
        // with an electoral threshold of 5%
        electionsMetier.calculerSieges(admin,listes);
    }
 
    /**
     * enregistrement des résultats de l'élection
     * 
     * @throws JsonProcessingException
     */
    @Test
    public void ecritureResultatsElections() throws JsonProcessingException {
        // create the table of 7 candidate lists
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // the voices are hard-fixed
        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);
        // the seats obtained by each list are calculated
        listes = electionsMetier.calculerSieges(admin,listes);
        // display results
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        // results are entered into the database
        electionsMetier.recordResultats(admin,listes);
        // check results
        listes = electionsMetier.getListesElectorales(admin);
        // display results
        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());
    }
}

المهمة: فهم هذا الاختبار والتحقق من نجاحه.


17.5. عميل الخادم الآمن مع طبقة [console]

في NetBeans، نفتح مشروعي Maven [elections-business-dao-security-webjson] و [elections-ui-business-dao-webjson]، ثم ننشئ مشروعًا جديدًا [elections-console-business-dao-security-webjson] عن طريق نسخ مشروع [elections-ui-business-dao-webjson] ولصقه:

تأتي معظم الفئات في المشروع الجديد [3] من مشروع [elections-ui-metier-dao-webjson] [2]، الذي كان العميل لخادم الويب/JSON غير الآمن:

اتبع هذه الخطوات لإنشاء مشروع Maven الجديد:

  • انسخ مشروع [elections-ui-metier-dao-webjson] والصقه في مشروع [elections-ui-metier-dao-security-webjson
  • في المشروع الجديد:
    • احذف الحزم [elections.client.dao، elections.client.metier، elections.client.entities
    • في الحزمة [elections.client.ui]، احتفظ فقط بفئة وحدة التحكم [ElectionsConsole] وواجهتها [IElectionsUI
    • أعد تسمية الحزم كما هو موضح في [3]؛
    • قم بإصلاح مشكلات الاستيراد في الفئات المختلفة عن طريق [النقر بزر الماوس الأيمن / Fix Imports

في هذه المرحلة، يحتوي المشروع الجديد على العديد من الأخطاء.

17.5.1. تكوين Maven

ملف [pom.xml] للمشروع الجديد هو كما يلي:


<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-console-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-console-metier-dao-security-webjson</name>
    <description>couche console du client web / jSON</description>
 
    <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>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</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>
  • الأسطر 4–8: معرف Maven للمشروع الجديد؛
  • الأسطر 22–26: التبعية لمشروع [elections-metier-dao-security-webjson] الذي أنشأناه للتو في القسم 17.4، والذي يوفر طبقتي [DAO] و[business

17.5.2. تكوين Spring

  

فئة [UiConfig] هي كما يلي:


package elections.security.client.config;
 
import elections.security.client.entities.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
 
@Import(MetierConfig.class)
@ComponentScan(basePackages = {"elections.security.client.console","elections.security.client.swing"})
public class UiConfig {
 
  // director
  private final User ADMIN = new User("admin", "admin");
 
  @Bean
  private User admin() {
    return ADMIN;
  }
 
}
  • السطر 8: نستورد الفئة [elections.security.client.config.MetierConfig] من المشروع [elections-metier-dao-security-webjson] الذي تمت مناقشته في القسم 17.4؛
  • السطر 9: نعلن الحزم التي توجد فيها حبوب Spring؛
  • الأسطر 12–18: حبة [admin] هي المستخدم [admin, admin] الذي سيستخدمه تطبيق وحدة التحكم. تذكر أن هذا هو المستخدم الوحيد المصرح له بالاستعلام عن خادم الويب/JSON الآمن؛

17.5.3. تطبيق تشغيل وحدة التحكم

  

تتطور فئة [ElectionsConsole] على النحو التالي:


package elections.security.client.console;
 
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@Component
public class ElectionsConsole implements IElectionsUI {
 
    @Autowired
    private IElectionsMetier electionsMetier;
 
  @Autowired
  private User admin;
 
    @Override
    public void run() {
        // competing lists
        ListeElectorale[] listes;
        // data entry
        try (Scanner clavier = new Scanner(System.in)) {
            // lists in competition are requested from the [metier] layer
            listes = electionsMetier.getListesElectorales(admin);
            // we enter the votes
            System.out.println("Il y a " + listes.length
                    + " listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :");
            for (int i = 0; i < listes.length; i++) {
                boolean saisieOK = false;
                while (!saisieOK) {
                    System.out.print("Nombre de voix de la liste [" + listes[i].getNom() + "] : ");
                    try {
                        listes[i].setVoix(Integer.parseInt(clavier.nextLine()));
                        saisieOK = true;
                    } catch (ElectionsException | NumberFormatException ex) {
                        System.out.println("Nombre de voix incorrect. Veuillez recommencer");
                    }
                }
            }
        }
        // we calculate the number of seats
        listes=electionsMetier.calculerSieges(admin,listes);
        // we record the results
        electionsMetier.recordResultats(admin,listes);
        // lists sorted in descending order of votes
        Arrays.sort(listes, new CompareListesElectorales());
        // we display them
        System.out.println("\nRésultats de l'élection\n");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }
    }
 
    // electoral list comparison class
    class CompareListesElectorales implements Comparator<ListeElectorale> {
 
        // comparison of two candidate lists by number of votes
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // we compare the votes of these two lists
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }
 
}

لم تتغير فئة ElectionsConsole إلا قليلاً. فقط تذكر أن الطرق في طبقة [business] تتطلب الآن مستخدمًا كمعلمة أولى (الأسطر 31 و49 و51). ويتم توفير ذلك عن طريق الحقن في الأسطر 21–22. والمستخدم الذي تم حقنه هو المستخدم المصرح له بالاستعلام عن خادم الويب / jSON.


المهمة: اختبر تطبيق وحدة التحكم (تذكر تشغيل الخادم الآمن أولاً).


17.6. عميل الخادم الآمن مع طبقة [swing]

نقوم بإنشاء مشروع NetBeans جديد [elections-swing-business-dao-security-webjson] عن طريق نسخ ولصق مشروع [elections-ui-business-dao-webjson]:

في المشروع الجديد [2]:

  • احذف الحزم [elections.client.dao، elections.client.metier، elections.client.entities
  • في الحزمة [elections.client.ui]، احذف الفئات [ElectionsConsole، IElectionsUI
  • أعد تسمية الحزم كما هو موضح في [2]؛
  • في الحزمة [elections.security.client.swing]، قم بتغيير اسم الفئة [AbstractElectionsSwing]، التي تنفذ واجهة المستخدم الرسومية، إلى [AbstractElectionsMainForm]، والفئة [ElectionsSwing]، التي تنفذ معالجات أحداث واجهة المستخدم الرسومية، إلى [ElectionsMainForm
  • قم بإصلاح مشكلات الاستيراد في الفئات المختلفة عن طريق النقر بزر الماوس الأيمن واختيار [Fix Imports

في هذه المرحلة، توجد أخطاء عديدة.

17.6.1. تكوين Maven

يتغير ملف [pom.xml] على النحو التالي:


<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-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-swing-metier-dao-security-webjson</name>
    <description>couche swing du client web / jSON</description>
 
    <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>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-console-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</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>
  • الأسطر 4–8: معرف Maven للمشروع الجديد؛
  • الأسطر 22–26: تبعية لمشروع وحدة التحكم الذي درسناه للتو في القسم 17.5؛

17.6.2. تكوين Spring

يستخدم هذا المشروع تكوين Spring الخاص بمشروع وحدة التحكم الذي يستورده (انظر القسم 17.5.2).

17.6.3. طرق عرض تطبيق Swing

 

سيستخدم هذا المشروع عرضين:

  • طريقة عرض [1] لتسجيل الدخول هي طريقة عرض جديدة. تُستخدم لمصادقة المستخدم الذي يرغب في استخدام التطبيق. وهي تستخدم الفئات التالية:
    • [AbstractElectionsConnectForm]، التي تنفذ العرض؛
    • [ElectionsConnectForm]، التي تتعامل مع أحداث العرض؛
  • الطريقة [2] مألوفة. وهي الطريقة المستخدمة حتى الآن. وهي تستخدم الفئات التالية:
    • [AbstractElectionsMainForm]، التي تنفذ العرض؛
    • [ElectionsMainForm]، التي تتعامل مع أحداث العرض؛

17.6.4. الجلسة

  

عندما يحتوي تطبيق Swing على عدة طرق عرض، تكون هناك حاجة إلى آلية تسمح لطريقة عرض ما بتمرير المعلومات إلى طريقة عرض أخرى. نستخدم مفهومًا مستمدًا من تطوير الويب: الجلسة (session)، التي تسمح لطرق العرض المرتبطة بمستخدم معين بمشاركة المعلومات. سيتم تنفيذ هذا المفهوم هنا باستخدام عنصر Spring فريد (singleton) سيتم حقنه في الفئتين اللتين تتعاملان مع الأحداث لكلا طريقتي العرض:


package elections.security.client.swing;
 
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
 
@Component
public class UiSession {
 
    // the connected user
  private User user;
 
  // getters and setters
... 
}
  • السطر 6: فئة [UiSession] هي مكون من مكونات Spring؛
  • السطر 10: تخدم غرضًا واحدًا فقط: تخزين المستخدم الذي يسجل الدخول باستخدام العرض [1]. العرض [2]، الذي يحتاج إلى معرفة هذا المستخدم، سيسترده من هناك؛

17.6.5. فئة التمهيد

  

فيما يلي فئة التشغيل الخاصة بالتطبيق الرسومي:


package elections.security.client.boot;
 
import elections.security.client.console.IElectionsUI;
 
public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }
 
    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConnectForm", IElectionsUI.class);
    }
}
  • السطر 5: تمتد فئة [BootElectionsSwing] إلى فئة [AbstractBootElections] المحددة في مشروع وحدة التحكم المضمنة في تبعيات المشروع؛
  • الأسطر 10–13: ستعرض فئة [BootElectionsSwing] طريقة عرض تسجيل الدخول [ElectionsConnectForm
  • الأسطر 6-8: سيتم تنفيذ طريقة [run] لهذه الفئة؛

17.6.6. فئة [ElectionsMainForm]

فئة [ElectionsMainForm] هي الفئة التي كانت تسمى سابقًا [ElectionsSwing]. وهي تتولى معالجة الأحداث الخاصة بعرض [AbstractElectionsMainForm]. ويتطور كودها على النحو التالي:


package elections.security.client.swing;
 
import elections.security.client.console.IElectionsUI;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;
 
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
 
@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {
 
  private static final long serialVersionUID = 1L;
 
  // reference on the [business] layer
  @Autowired
  private IElectionsMetier metier;
 
  // session UI
  @Autowired
  private UiSession uiSession;
 
  // logged-in user
  private User user;
 
  // list templates JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // competing lists
  private ListeElectorale[] listes;
 
  // user-entered lists
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;
 
  // initializations
  @Override
  protected void init() {
    // generation of components by the parent class
    super.init();
    // local initializations
    modèleNomsVoix = new DefaultListModel<>();
    jListNomsVoix.setModel(modèleNomsVoix);
    modèleRésultats = new DefaultListModel<>();
    jListResultats.setModel(modèleRésultats);
    String info;
    boolean erreur = false;
    try {
      // lists are requested from the [business] layer
      listes = metier.getListesElectorales(user);
      // associate list names with the jComboBoxNomsListes combo
      for (int i = 0; i < listes.length; i++) {
        jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
      }
      // and election parameters
      int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
      double seuilElectoral = metier.getSeuilElectoral(user);
      // we initialize the labels linked to these two pieces of information
      jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
      jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
      // message of success
      info = "Source de données lue avec succès";
    } catch (ElectionsException ex1) {
      // we note the error
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
      erreur = true;
    } catch (RuntimeException ex2) {
      // we note the error
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
      erreur = true;
    }
    // mistake?
    if (erreur) {
      // display info
      jTextPaneMessages.setText(info);
      jTextPaneMessages.setCaretPosition(0);
      return;
    } else {
      jTextPaneMessages.setText("");
    }
    // form status
    Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
    Utilitaires.setEnabled(
            new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
    // center window
    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);
 
  }
...
 
  @Override
  public void run() {
    // user memory
    user = uiSession.getUser();
    // window initialization
    init();
    setVisible(true);
  }
}
  • السطران 32-33: إدراج جلسة التطبيق؛
  • الأسطر 112–119: سيتم تنشيط العرض كما في السابق باستخدام طريقة [run] الخاصة به؛
  • السطر 115: يتم استرداد المستخدم من الجلسة. وقد تم وضع هذا المستخدم هناك بواسطة الكود الموجود في عرض تسجيل الدخول [ElectionsConnectForm]. ثم يتم تعديل الكود بحيث يكون هذا المستخدم هو المعلمة الأولى في استدعاءات طبقة [business] (السطور 63 و69-70، على سبيل المثال)؛

17.6.7. عرض [AbstractElectionsConnectForm]

  

استخدم NetBeans لإنشاء النموذج التالي:

 

لن تقوم الفئة بتنفيذ الطريقة التي تتعامل مع النقر على خيار القائمة [Login] نفسه. بل ستستدعي طريقة [doConnecter] المعلنة على أنها مجردة، وستُعلن فئة العرض نفسها على أنها مجردة. سنقوم بإزالة الطريقة الثابتة [main] التي تم إنشاؤها تلقائيًا.

17.6.8. معالجة الأحداث لعرض تسجيل الدخول

  

تقوم فئة [ElectionsConnectForm] بتنفيذ معالجة الأحداث لعرض تسجيل الدخول على النحو التالي:


package elections.security.client.swing;
 
import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.SwingUtilities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class ElectionsConnectForm extends AbstractElectionsConnectForm implements IElectionsUI {
 
    private static final long serialVersionUID = 1L;
 
    // reference on the [business] layer
  @Autowired
  private IElectionsMetier metier;
 
  // logged-in user
  private User user;
 
  // main form
  @Autowired
  private ElectionsMainForm electionsMainForm;
 
  // session UI
  @Autowired
  private UiSession uiSession;
 
  @Override
  protected void doConnect() {
    String info = null;
    try {
      if (isPageValid()) {
        // user authentication
        metier.authenticate(user);
        // the user is saved in the session
        uiSession.setUser(user);
        // connection view is hidden
        setVisible(false);
        // the main view is displayed
        electionsMainForm.run();
      }
    } catch (ElectionsException ex1) {
      // we note the error
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
    } catch (RuntimeException ex2) {
      // we note the error
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
    }
    // mistake?
    if (info != null) {
      // display info
      jTextPaneErreurs.setText(info);
      jTextPaneErreurs.setCaretPosition(0);
    }
  }
 
  // initializations
  @Override
  protected void init() {
    // generation of components by the parent class
    super.init();
    // local initializations
...
    // center window
...
  }
 
  @Override
  public void run() {
    // the graphical interface is displayed
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
  }
 
  private boolean isPageValid() {
    ...
  }
 
  private String getInfoForException(String message, ElectionsException ex) {
...
  }
 
  private String getInfoForException(String message, RuntimeException ex) {
...
  }
 
}
  • السطور 73–82: طريقة [run]، التي سيتم استدعاؤها بواسطة فئة التمهيد [BootElectionsSwing
  • السطور 26-27: إدخال مرجع إلى العرض رقم 2، الذي يجب عرضه إذا تم التعرف على المستخدم؛
  • السطور 30-31: إدخال جلسة التطبيق؛
  • السطر 84: تقوم طريقة [isPageValid] بأمرين:
    • تتحقق من أن اسم المستخدم ليس فارغًا (قد تكون كلمة المرور فارغة)؛
    • تنشئ مثيل لحقل User من السطر 23 باستخدام المدخلات؛

المهمة: أكمل كود الفصل.



المهمة: اختبر التطبيق.