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 باستخدام المدخلات؛
![]() | ![]() |
![]() | ![]() |
المهمة: أكمل كود الفصل.
المهمة: اختبر التطبيق.































