7. التطبيق QuiEst
نصف هنا تطبيق Struts أكثر تعقيدًا بعض الشيء من التطبيقات السابقة، التي كان لا بد أن تكون بسيطة لأغراض تعليمية.
7.1. فئة users
تتوفر فئة Java لتخزين المعلومات المتعلقة بمستخدمي جهاز Unix. ويتم تسجيل هذه المعلومات في ثلاثة ملفات محددة:
- /etc/passwd: قائمة المستخدمين
- /etc/group: قائمة المجموعات
- /etc/aliases: قائمة الأسماء المستعارة للبريد
محتوى هذه الملفات الثلاثة هو كما يلي:
- /etc/passwd
تتخذ الأسطر في هذا الملف الشكل التالي:
login:pwd:uid:gid:id:dir:shell
مع
اسم تسجيل دخول المستخدم | |
كلمة المرور المشفرة | |
رقم المستخدم | |
رقم مجموعته | |
هويته | |
دليل الاتصال الخاص به | |
مترجم الأوامر الخاص به |
وبالتالي، قد يكون سطر المستخدم كما يلي:
يحمل المستخدم السابق الرقم 110 وينتمي إلى المجموعة 57. ويمكن العثور على تعريف المجموعة 57 في الملف /etc/group.
- /etc/group
تتخذ الأسطر في هذا الملف الشكل التالي:
nomGroupe:pwd:gid:membre1,membre2,....
مع
اسم المجموعة | |
كلمة المرور المشفرة الخاصة به - غالبًا ما يكون هذا الحقل فارغًا | |
رقم المجموعة | |
تسجيلات دخول المستخدمين - قد يكون هذا الحقل فارغًا |
وبالتالي، قد يكون سطر المجموعة 57 السابق كما يلي:
مما يشير إلى أن المجموعة 57 تحمل الاسم iup2-auto.
- /etc/aliases
تتخذ الأسطر في هذا الملف الشكل التالي:
مع
alias | |
فاصل واحد أو أكثر | |
اسم تسجيل الدخول للمستخدم الذي ينتمي إليه الاسم المستعار |
وبالتالي فإن السطر
guillaume.dupond: dupond
يعني أن الاسم المستعار guillaume.dupond ينتمي إلى المستخدم الذي اسم تسجيل الدخول الخاص به هو dupond. تجدر الإشارة إلى أن الأسماء المستعارة تُستخدم في عناوين البريد الإلكتروني. لذا، إذا كان اسم جهاز Unix في المثال السابق هو shiva.istia.univ-angers.fr، فسيتم إرسال رسالة موجهة إلى guillaume.dupond@shiva.istia.univ-angers.fr سيُسلّم إلى صندوق بريد المستخدم الذي يحمل اسم تسجيل الدخول dupond على هذا الجهاز.
لن نهتم هنا بواجهة فئة users بأكملها، بل سنكتفي بالتركيز على منشئها وبعض الطرق:
import java.io.*;
import java.util.*;
public class users{
// السمات
private Hashtable usersByLogin=new Hashtable(); // تسجيل الدخول --> اسم المستخدم، كلمة المرور، ...، الدليل
private ArrayList erreurs=new ArrayList(); // قائمة رسائل الخطأ
....
// المنشئ
public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
// usersFileName: اسم ملف المستخدمين الذي يحتوي على أسطر بالصيغة
// login:pwd:uid:gid:id:dir:shell
// groupsFileName: اسم ملف المجموعات الذي يحتوي على أسطر بالشكل التالي
// الاسم:كلمة المرور:الرقم:العضو1،العضو2،..
// aliasesFileName: اسم ملف الأسماء المستعارة الذي يحتوي على أسطر بالشكل التالي
// الاسم المستعار:[tab]login
// ينشئ القاموس usersByLogin
....
}// منشئ
// قائمة المستخدمين
public Hashtable getUsersByLogin(){
return usersByLogin;
}
// الأخطاء
public ArrayList getErreurs(){
return erreurs;
}
قاموس (Hashtable) تتكون مفاتيحه من أسماء المستخدمين الموجودة في ملف passwd. والقيمة المرتبطة بالمفتاح هي مصفوفة من السلاسل (String [7]) تتكون عناصرها من الحقول السبعة في السطر من ملف passwd المرتبط باسم المستخدم. وقد تكون بعض الحقول فارغة إذا كان السطر يحتوي على أقل من 7 حقول. | |
قائمة رسائل الخطأ - فارغة في حالة عدم وجود أخطاء |
7.2. تطبيق الويب المطلوب
نقترح إنشاء تطبيق الويب التالي (صفحة نموذج):
![]() |
رقم | الاسم | النوع HTML | الدور |
1 | cmbLogins | <select ...>...</select> | يعرض قائمة بجميع أسماء المستخدمين التي يمكن طلب معلومات عنها |
2 | btnChercher | <input type="submit" ...> | لبدء البحث |
عندما يضغط المستخدم على الزر [Chercher] (2)، يتم طلب معلومات تسجيل الدخول (1) من كائن U من نوع users. إذا كان تسجيل الدخول موجودًا، فسيظهر الرد التالي (صفحة المعلومات):
![]() |
كما يوضح URL للمتصفح أعلاه، يتم إرسال معلمات النموذج إلى الخادم عبر GET. لذلك يمكننا تزويد المتصفح مباشرةً بـ URL المُعدة مسبقًا. وهذا ما نقوم به هنا، لإدخال اسم مستخدم غير موجود. نحصل على الرد التالي (صفحة الأخطاء):
![]() |
7.3. بنية التطبيق
![]() |
نجد في هذه البنية المكونات التالية:
- طرق العرض:
- logins.jsp، تُستخدم لعرض قائمة عمليات تسجيل الدخول (الواجهة 1)
- infos.jsp، تُستخدم لعرض المعلومات المتعلقة ببيانات تسجيل الدخول (الطريقة 2)
- erreurs.jsp تُستخدم لعرض قائمة بالأخطاء (الطريقة 3)
- النماذج من النوع ActionForm التي تستخدمها الإجراءات:
- formLogins تُستخدم لجمع بيانات النموذج logins.jsp
- الإجراءات:
- SetupLoginAction الذي يقوم بإعداد محتوى formulaire.jsp ثم يعرض هذه العرض
- InfosLoginAction الذي يعالج محتوى logins.jsp بمجرد إرساله إلى الخادم
- ForwardAction الذي يعالج الرابط [Retour vers le formulaire] الخاص بعروض infos.jsp و erreurs.jsp
- فئة users الخاصة بالمجال المهني التي تستخدمها الإجراءات للحصول على بياناتها
- النموذج الذي توفره الملفات الثلاثة «passwd» و«group» و«aliases»
7.4. ملفات تكوين تطبيق الويب
7.4.1. الملف server.xml
سيُسمى سياق التطبيق /strutsquiest2. لذا سنضيف السطر التالي إلى ملف server.xml في Tomcat:
بعد ذلك، نعيد تشغيل Tomcat حتى يأخذ السياق الجديد في الاعتبار. يمكننا التحقق من صحة هذا السياق عن طريق طلب http://localhost:8080/strutsquiest2 من ملف URL.
7.4.2. الملف web.xml
سيكون ملف التكوين web.xml للتطبيق كما يلي:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>strutsquiest2</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
يقدم هذا الملف web.xml ميزة جديدة. لم يعد عنصر التحكم Struts هو org.apache.struts.action.ActionServlet، بل أصبح فئة مشتقة أطلقنا عليها هنا اسم istia.st.struts.quiest.Quiest2ActionServlet. وهذا سيسمح لنا باسترداد معلمتي التهيئة وهما passwdFileName (موقع ملف passwd) و groupFileName (موقع ملف group). ملف aliases غير ضروري في هذا التطبيق.
7.4.3. الملف struts-config.xml
سيكون ملف struts-config.xml كما يلي:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
<message-resources
parameter="istia.st.struts.quiest.ApplicationResources"
null="false"
/>
</struts-config>
تتضمن هذه الصفحة الأقسام الثلاثة الرئيسية التالية:
- تحديد النماذج في القسم <form-beans>
- إعلان الإجراءات في القسم <action-mappings>
- إعلان ملف الموارد في <message-ressources>
7.4.4. كائنات (بيانات) النماذج في التطبيق
<form-beans>
<form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
لا يوجد سوى عنصر نموذج واحد في تطبيقنا، يُسمى formLogins وهو من النوع المشتق من DynaActionForm. وسيُستخدم في الحالات التالية:
- احتواء البيانات اللازمة لعرض العرض رقم 1
- استرداد قيم النموذج من العرض رقم 1 عندما يقوم المستخدم بتقديمه (submit)
ترتبط بنية البين formLogins بالنموذج الخاص بالعرض رقم 1. دعونا ندرسها:
![]() |
رقم | الاسم | النوع HTML | الدور |
1 | cmbLogins | <select ...>...</select> | يعرض قائمة بجميع أسماء المستخدمين التي يمكن طلب معلومات عنها |
2 | btnChercher | <input type="submit" ...> | لبدء البحث |
لنميز بين عدة حالات:
- من العميل إلى الخادم، يُستخدم الكائن formLogins لاحتواء قيم النموذج HTML أعلاه الذي سيتم إرساله عبر الزر [Envoyer]. لذلك، فإنه يحتاج إلى حقل cmbLogins الذي سيتلقى قيمة الحقل HTML cmbLogins، c.a.d، وهي اسم المستخدم الذي اختاره المستخدم.
- من الخادم إلى العميل، يُستخدم الكائن formLogins لتقديم المحتوى الأولي للعرض رقم 1. وسيُستخدم حقل tLogins الخاص به كمحتوى للقائمة 1. وسيسمح حقلها cmbLogins بتحديد عنصر القائمة 1 المراد تحديده.
7.4.5. إجراءات التطبيق
يتم تنفيذ الإجراءات بواسطة كائنات من نوع Action أو مشتقات منها. يتم تكوين الإجراءات داخل العلامات <action-mappings>:
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
الإجراء /init
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
دعونا نصف كيفية عمل الإجراء /init:
- تُنفَّذ العملية /init عادةً مرة واحدة فقط خلال دورة الطلب-الاستجابة الأولى التي يطلب فيها المستخدم الكائن URL http://localhost:8080/strutsquiest2/init.do
- يتم إنشاء الكائن formsLogins أو إعادة تدويره. ويتم استرداده (إعادة التدوير) أو وضعه (الإنشاء) في الجلسة وفقًا لما يحدده السمة scope.
- يتم استدعاء طريقة reset الخاصة به. تجدر الإشارة إلى أن هذه الطريقة لا تقوم بأي شيء افتراضيًا في الفئات ActionForm والمشتقة منها. يتم استدعاؤها مباشرة قبل نسخ بيانات طلب العميل إلى الكائن ActionForm، وتُستخدم لتنظيف الكائن قبل عملية النسخ هذه. ما هو طلب العميل هنا؟ يتم تشغيل الإجراء /init عندما يكون عنوان URL المطلوب هو http://localhost:8080/strutsquiest2/init.do. يمكن طلب هذا الكائن URL بواسطة كائن GET أو POST. يكفي إدراج معلمات تحمل أسماء حقول formLogins في هذا الطلب حتى يتم تهيئة هذه الحقول كما هو موضح في المثال التالي:

- يحتوي الطلب على المعلمة cmbLogins (afterpak). وبالتالي، قام وحدة التحكم Struts بنسخ قيمة هذه المعلمة إلى الحقل cmbLogins في formLogins. ثم تم تنفيذ الإجراء SetupLoginsAction وانتهى بعرض العرض logins.jsp. يحتوي هذا العرض على نموذج تتلقى بعض حقوله قيمتها من formLogins. وبالتالي، فإن حقل القائمة المنسدلة HTML المسمى cmbLogins قد استمد قيمته من الحقل cmbLogins (=afterpak) التابع لـ formLogins. ولهذا السبب، تظهر قائمة أسماء المستخدمين مركزة على اسم المستخدم afterpak.
- يمكننا أيضًا تجربة تمرير المعلمة tLogins بالطريقة التالية:
سيؤدي ذلك إلى تهيئة الحقل tLogins من القيمة formLogins باستخدام المصفوفة {"login1","login2"}. إلا أننا سنرى لاحقًا أن الإجراء SetupLoginsAction يعين قيمة للحقل tLogins ويستبدل المصفوفة التي تم إنشاؤها بهذه الطريقة بمصفوفة جديدة. وهذه المصفوفة الأخيرة هي التي تظهر في العرض logins.jsp.
- المناقشة السابقة، على الرغم من تعقيدها بعض الشيء، لها ميزة إظهار أنه لا يمكن الافتراض بأن الإجراء /init سيتم تشغيله دون معلمات قادمة من العميل. لذا قد يكون من المفيد استخدام الطريقة reset لمسح formLogins. في هذه الحالة، سيكون علينا اشتقاق الفئة DynaActionForm. لم نقم بذلك هنا.
- بمجرد استدعاء الطريقة reset الخاصة بـ formLogins، يقوم وحدة التحكم بنسخ بيانات طلب العميل إلى الحقول التي تحمل نفس الاسم في formLogins. عادةً ما يتم استدعاء الإجراء /init بدون معلمات من العميل، لكننا أوضحنا سابقًا أنه لا يوجد ما يمنع العميل من استدعاء الإجراء /init بمعلمات عشوائية. وبالتالي، في نهاية هذه المرحلة، من الممكن جدًّا أن تحتوي الحقول cmbLogins و tLogins على قيم. وقد رأينا أن الحقل cmbLogins سيحتفظ بهذه القيمة، على عكس الحقل tLogins.
- ثم يتحقق المُراقب من السمة validate الخاصة بالإجراء. وهنا، تكون قيمتها "false". ولن يتم استدعاء الطريقة validate الخاصة بـ formLogins. ولذلك لن نكتبها.
- يتم إنشاء الكائن SetupLoginsAction أو إعادة استخدامه إذا كان موجودًا بالفعل، ثم يتم تشغيل طريقة execute الخاصة به. وتتمثل مهمته الوحيدة في تعيين قيمة للحقل tLogins التابع لـ formLogins. هذه القيمة هي مصفوفة أسماء المستخدمين، وهي مصفوفة سيُطلب توفيرها من الفئة الوظيفية users. قد تفشل هذه العملية. ولهذا السبب، يمكن أن يتبع الإجراء /init عرضان:
- عرض erreurs.jsp إذا لم تتمكن فئة users من توفير مصفوفة معلومات تسجيل الدخول
- عرض logins.jsp في الحالات الأخرى
- سيقوم وحدة التحكم بعرض إحدى هاتين الطريقتين
- تنتهي دورة الطلب والاستجابة للإجراء /init.
الإجراء /infosLogin
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
دعونا نصف كيفية عمل الإجراء / infosLogin:
- يتم تنفيذ الإجراء /infosLogin عادةً عندما ينقر المستخدم على الزر [Chercher] في العرض logins.jsp. ثم يتم إرسال طلب إلى الخادم، يتم تحديده بواسطة العلامة HTML <form> في طريقة العرض:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
- ونلاحظ أن الطلب يُرسل إلى الخادم باستخدام طريقة get. وبالتالي، يمكن للمستخدم كتابته يدويًّا:

- يتم إنشاء الكائن formsLogins أو إعادة استخدامه. ويتم استرداده (إعادة الاستخدام) أو وضعه (الإنشاء) في الجلسة وفقًا لما يحدده السمة scope.
- يتم استدعاء طريقة reset الخاصة به مباشرة قبل نسخ بيانات طلب العميل إلى الكائن ActionForm. وعادةً ما تكون على الشكل http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx حيث يمثل xx اسم تسجيل دخول تم اختياره من قائمة أسماء تسجيل الدخول. ولكن يمكن أن يكون أي شيء أيضًا إذا كان المستخدم قد استخدم الكائن URL السابق مع تمرير معلمات عشوائية. لنفترض تسلسل الصفحات التالي:

- تم استدعاء الإجراء /infosLogin بسلسلة المعلمات cmbLogins=xx&tLogins=login1&tLogins=login2. وبالتالي، ستتلقى الحقول cmbLogins و tLogins التابعة لـ formLogins القيمتين "xx" و {"login1","login2"} على التوالي. ستطلب العملية /infosLogin من فئة users المعلومات المرتبطة باسم المستخدم "xx". وسترد فئة users بأن اسم المستخدم هذا غير موجود. ومن هنا تأتي العرض المرسَل أعلاه. والآن، دعونا نستخدم الرابط [Retour au formulaire] المذكور أعلاه:

- إن الإجراء /retourLogins هو الذي يتم تشغيله بواسطة الرابط [Retour au formulaire]. ويقتصر هذا الإجراء على عرض العرض logins.jsp دون أي إجراء وسيط. نتذكر أن الحقل tLogins يُستخدم لتغذية قائمة أسماء المستخدمين في العرض logins.jsp. وبما أن المستخدم قد غيّر هذه القيمة إلى {"login1","login2"}، فإن هذين الاسمين هما اللذان يظهران الآن في القائمة. مرة أخرى، لا يسعنا إلا أن نؤكد على الضرورة المطلقة لمراعاة حالة المعلمات التعسفية التي يحددها المستخدم أو البرنامج في طريقة عمل التطبيق. الحل للمشكلة المطروحة هنا هو أن يشير الرابط [Retour au formulaire] إلى الإجراء /init. وبذلك نضمن الحصول على قائمة أسماء المستخدمين الصحيحة.
- لنعد إلى استعلام عادي إلى الإجراء /infosLogin من النوع التالي:
http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak
- سيقوم وحدة التحكم Struts بتعيين قيمة للحقل cmbLogins في الكائن ActionForm. أما الحقل tLogins فلن يتم تعيين قيمة له (لا يوجد حقل مطابق في الاستعلام المرسل). وهذا الأداء يناسبنا. لذلك لن نضطر إلى كتابة طريقة إعادة تعيين مخصصة لـ formLogins.
- بمجرد استدعاء طريقة إعادة الضبط الخاصة بـ formLogins، يقوم وحدة التحكم بنسخ بيانات طلب العميل إلى الحقول التي تحمل نفس الاسم في formLogins. سيتلقى الحقل cmbLogins قيمة، وهي اسم المستخدم الذي اختاره المستخدم (afterpak).
- ثم يتحقق وحدة التحكم من السمة validate الخاصة بالإجراء. وهنا، تكون قيمتها "false". ولن يتم استدعاء الطريقة validate الخاصة بـ formLogins.
- يتم إنشاء الكائن InfosLoginAction أو إعادة استخدامه إذا كان موجودًا بالفعل، ثم يتم تشغيل طريقة execute الخاصة به. وتتمثل مهمته في الحصول على المعلومات المرتبطة باسم المستخدم cmbLogins. وسيتم طلب هذه المعلومات من فئة users الخاصة بالأعمال. وقد تفشل هذه العملية (على سبيل المثال، في حالة عدم وجود اسم المستخدم). ولهذا السبب، يمكن أن يتبع الإجراء /infosLogin عرضان:
- عرض erreurs.jsp إذا لم تتمكن فئة users من توفير المعلومات المطلوبة
- عرض infos.jsp في الحالات الأخرى
- سيقوم وحدة التحكم بعرض إحدى هاتين الطريقتين
- تنتهي دورة الطلب والاستجابة للإجراء /infosLogin.
الإجراء /retourLogins
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
- يتم تشغيل الإجراء /retourLogins عن طريق تنشيط الرابط [Retour au formulaire] في العرضين erreurs.jsp و infos.jsp.
- لا يوجد هنا نموذج مرتبط بالإجراء. لذلك ننتقل مباشرةً إلى تنفيذ الطريقة execute لكائن ForwardAction الذي سيعيد كائن ActionForward يشير إلى العرض /vues/logins.jsp.
7.4.6. ملف رسائل التطبيق
القسم الثالث من الملف struts-config.xml هو ملف الرسائل:
يوجد الملف ApplicationResources.properties في WEB-INF/classes/istia/st/struts/quiest. ومحتواه كما يلي:
errors.header=<ul>
errors.footer=</ul>
parametreManquant=<li>Le paramètre [{0}] n'a pas été initialisé</li>
usersException=<li>Erreur d'initialisation de l'application : {0}</li>
loginInconnu=<li>Le login [{0}] n'existe pas</li>
7.5. كود طرق العرض
يُرجى من القارئ مراجعة الدرس الخاص بإدارة النماذج إذا لم يفهم كود العروض الموضحة أدناه.
7.5.1. عرض logins.jsp
تجدر الإشارة إلى أن هذه العرضة تُعرض في حالتين:
- عند استدعاء الإجراء /init خلال الدورة الأولى من الطلب والاستجابة
- عند استدعاء الإجراء /retourLogins خلال الدورات التالية
كود العرض logins.jsp هو كما يلي:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Quiest - formulaire</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<center>
<h2>Application QuiEst</h2>
<hr>
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
<table>
<tr>
<td>Login cherché</td>
<td>
<html:select name="formLogins" property="cmbLogins">
<html:options name="formLogins" property="tLogins"/>
</html:select>
</td>
<td>
<html:submit value="Chercher"/>
</td>
</tr>
</table>
</html:form>
</center>
</body>
</html>
7.5.2. طريقة العرض infos.jsp
يتم عرض هذه الشاشة عند نجاح استدعاء الإجراء /infosLogin. ورمزها هو كما يلي:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
تستخدم هذه العرض كائنًا يُسمى infosLoginBean ويتم إدراجه في الاستعلام بواسطة الإجراء /infosLogin. يحتوي هذا الكائن على حقلين:
String titre; // العنوان المراد عرضه في طريقة العرض
String[] infosLogin; // جدول المعلومات المراد عرضها في العرض
سنفصل في هذه الفئة عند تناولنا كود فئة InfosLoginAction.
7.5.3. طريقة العرض erreurs.jsp
يتم عرض هذه النظرة عندما تنتهي الإجراءات /init أو /infosLogin بحدوث خطأ. وكودها هو كما يلي:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Application QuiEst - erreurs</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2 align="center">Application QuiEst - Erreurs</h2>
<hr>
<h2>Les erreurs suivantes se sont produites</h2>
<html:errors/>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
7.6. فئات Java
يشير الملف web.xml إلى فئة جافا:
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
....
</servlet>
...
</web-app>
ويشير ملف التكوين struts-config.xml إلى فئتين في لغة جافا:
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
7.6.1. الفئة Quiest2ActionServlet
الفئة Quiest2ActionServlet مشتقة من الفئة ActionServlet، وهي فئة وحدة التحكم Struts. نقوم بتشتيت الفئة ActionServlet لتخصيص طريقة init الخاصة بها. في الواقع، ستسمح لنا هذه الطريقة، التي تُنفَّذ مرة واحدة فقط عند التحميل الأولي للـservlet، بإنشاء كائن عمل من نوع users. لا يحتاج هذا الكائن في الواقع إلى الإنشاء سوى مرة واحدة، وتُعد طريقة init المكان المناسب للقيام بهذا الإنشاء. يحتاج كائن users إلى ملفين لإنشائه، وهما الملفان passwd و group. يتم تمرير مسار هذين الملفين كمعلمات إلى السيرفلت في الملف web.xml الخاص بالتطبيق:
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
رمز السيرفلت هو كما يلي:
package istia.st.struts.quiest;
import java.util.*;
import javax.servlet.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class Quiest2ActionServlet
extends ActionServlet {
// سمات السيرفلت
private users u = null;
private ActionErrors erreurs = new ActionErrors();
private String[] tLogins;
//التهيئة
public void init() throws ServletException {
// لا تنسَ تهيئة الفئة الأم
super.init();
// المتغيرات المحلية
final String[] initParams = {"passwdFileName", "groupFileName"};
Properties params = new Properties();
// نسترد معلمات تهيئة السيرفلت
ServletConfig config = getServletConfig();
String servletPath = config.getServletContext().getRealPath("/");
for (int i = 0; i < initParams.length; i++) {
String valeur = config.getInitParameter(initParams[i]);
if (valeur == null) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("parametreManquant", initParams[i]));
valeur = "";
}
// نقوم بتخزين المعلمة
params.setProperty(initParams[i], valeur);
} //for
// العودة في حالة وجود أخطاء في التهيئة
if (erreurs.size() != 0) {
return;
}
// يتم إنشاء كائن users
try {
u = new users(servletPath + "/" + params.getProperty("passwdFileName"),
servletPath + "/" + params.getProperty("groupFileName"), null);
}
catch (Exception ex) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("usersException", ex.getMessage()));
return;
} //catch
// نسترد قائمة أسماء المستخدمين
tLogins = new String[u.getUsersByLogin().size()];
Enumeration eLogins = u.getUsersByLogin().keys();
for (int i = 0; i < tLogins.length; i++) {
tLogins[i] = (String) eLogins.nextElement();
}
// يتم فرز أسماء المستخدمين
Arrays.sort(tLogins);
} //التهيئة
// طريقة الوصول إلى المعلومات الخاصة بالسيرفلت
public Object[] getInfos() {
return new Object[] {erreurs, u, tLogins};
}
}
باختصار، تعمل طريقة init على النحو التالي:
- أولاً، يتم استدعاء طريقة init للفئة الأم (ActionServlet) حتى يتم تهيئتها بشكل صحيح
- ثم تتم قراءة معلمات التهيئة. إذا كانت هناك معلمات مفقودة، يتم تعبئة السمة الخاصة ActionErrors «أخطاء».
- إذا كانت معلمات التهيئة موجودة بالفعل، يتم إنشاء كائن users. قد يؤدي هذا الإنشاء إلى إثارة استثناء. في هذه الحالة، يتم تعبئة السمة ActionErrors «erreurs».
- إذا سارت عملية الإنشاء بشكل جيد، يتم استخراج قائمة بجميع أسماء المستخدمين من الكائن الذي تم إنشاؤه، ثم يتم فرزها في مصفوفة يتم وضعها في السمة الخاصة String[] tLogins.
- يتم تخزين الكائن users الذي تم إنشاؤه في السمة الخاصة users u.
- تسمح الطريقة العامة getInfos بالحصول على السمات الخاصة الثلاث (u، erreurs، tLogins) في مصفوفة من الكائنات.
7.6.2. الفئة SetupLoginsAction
الغرض من هذا الإجراء هو تهيئة الكائن DynaActionForm formLogins. ولن تكون هناك حاجة لإعادة تهيئة هذا الكائن الموجود في الجلسة لاحقًا. وبالتالي، لا يتم تنفيذ الإجراء SetupLoginsAction إلا مرة واحدة. وفيما يلي كوده:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class SetupLoginsAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// إعداد النموذج للعرض
// استرداد المعلومات من السيرفلت المتحكم
// المعلومات=(ActionErrors الأخطاء، المستخدمون u، String[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// هل حدثت أخطاء في التهيئة؟
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// نقوم بإدخال بيانات تسجيل الدخول في النموذج
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
}
كما هو الحال مع جميع إجراءات Struts، يوجد الكود في الأسلوب execute. ويقوم هذا الأسلوب بما يلي:
- تسترد من وحدة التحكم Struts المعلومات التي قامت بتخزينها بواسطة طريقة init الخاصة بها. وتقوم طريقة getServlet() التابعة لفئة Action بتحقيق ذلك.
- ومن بينها السمة ActionErrors «أخطاء وحدة التحكم». إذا كانت قائمة الأخطاء هذه غير فارغة، يتم إدراجها في الاستعلام ويُطلب عرض العرض erreurs.jsp.
- وإذا كانت قائمة الأخطاء فارغة، يتم تعيين قائمة عمليات تسجيل الدخول التي أنشأها وحدة التحكم في البداية إلى الحقل tLogins في الكائن formLogins. ثم يُطلب عرض طريقة العرض logins.jsp التي ستعرض قائمة عمليات تسجيل الدخول.
7.6.3. الفئتان InfosLoginBean و InfosLoginAction
تهدف العملية InfosLoginAction إلى استرداد المعلومات المرتبطة بتسجيل الدخول الذي اختاره المستخدم وعرضها عليه. سيتم تجميع المعلومات في كائن من النوع InfosLoginBean:
package istia.st.struts.quiest;
public class InfosLoginBean implements java.io.Serializable{
// الـbean الذي يحتوي على المعلومات اللازمة لصفحة المعلومات
private String titre;
private String[] infosLogin;
// المنشئ
public InfosLoginBean(String titre, String[] infosLogin){
this.titre=titre;
this.infosLogin=infosLogin;
}
// المنادب
public String getTitre(){
return this.titre;
}
public String[] getInfosLogin(){
return this.infosLogin;
}
public String getInfosLogin(int i){
return this.infosLogin[i];
}
}
الفئة السابقة هي bean، c.a.d. وهي فئة جافا حيث يتم ربط السمة الخاصة T unAttribut تلقائيًا بطريقتين خاصتين:
- void setUnAttribut(T قيمة){unAttribut=قيمة;}
- T getUnAttribut(){ return unAttribut;}
تجدر الإشارة إلى الصيغة الخاصة لطريقتي get و set. إذا كان السمة عبارة عن مصفوفة T[] unAttribut، فيمكن إنشاء طريقتي get و set لعناصر المصفوفة:
- void setUnAttribut(T القيمة, int i){unAttribut[i]=القيمة;}
- T getUnAttribut(int i){ return unAttribut[i];}
لفهم الأمر بشكل أفضل، لنراجع كود العرض infos.jsp الذي يجب إرساله عقب الإجراء InfosLoginAction:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
لنأخذ العلامة التالية:
تطلب هذه العملية كتابة قيمة حقل «title» (property) للكائن infosLoginBean (name) الموجود في الاستعلام (scope). وسيتم الحصول على القيمة المراد كتابتها من خلال request.getAttribute("infosLoginBean").getTitre(). لذا، يجب أن تكون الطريقة getTitre موجودة في الفئة InfosLoginBean. وهذا هو الحال بالفعل. العلامة
كتابة قيمة العنصر infosLogin[0] الخاص بالكائن infosLoginBean المضمن في الاستعلام. وسيتم الحصول على القيمة المراد كتابتها عن طريق request.getAttribute("infosLoginBean").getInfosLogin(0). لذلك يجب أن تكون الطريقة getInfosLogin(int i) موجودة في الفئة InfosLoginBean. وهذا هو الحال بالفعل.
الغرض من الفئة InfosLoginAction هو إنشاء الكائن InfosLoginBean السابق بناءً على اسم تسجيل الدخول الذي يختاره المستخدم. وفيما يلي شفرة البرمجة الخاصة بها:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class InfosLoginAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// يجب أن تعرض المعلومات المرتبطة ببيانات تسجيل الدخول
// يتم استرداد المعلومات من سيرفلت التحكم
// المعلومات=(ActionErrors الأخطاء، المستخدمون u، LoginBean[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// هل حدثت أخطاء في التهيئة؟
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// أولاً، استرجع بيانات تسجيل الدخول هذه
String login = (String) ( (DynaActionForm) form).get("cmbLogins");
// هل لدينا أي شيء؟
if (login == null) {
// هذا غير طبيعي - سنعيد إرسال نموذج تسجيل الدخول
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
// لدينا اسم مستخدم - نبحث عنه
String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);
// هل تم العثور عليه؟
if (infosLogin == null) {
// لم يتم العثور على اسم المستخدم - يتم عرض صفحة الأخطاء
ActionErrors erreurs2=new ActionErrors();
erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
this.saveErrors(request, erreurs2);
return mapping.findForward("afficherErreurs");
}
// تم العثور على معلومات تسجيل الدخول - يتم إدراج المعلومات التي تم العثور عليها في الطلب
String titre="Application QuiEst - login["+login+"]";
InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
request.setAttribute("infosLoginBean",infosLoginBean);
return mapping.findForward("afficherInfos");
}
}
تعمل طريقة execute على النحو التالي:
- يتم استرداد المعلومات التي جمعها وحدة التحكم Struts أثناء تهيئتها. إذا كانت وحدة التحكم قد سجلت أخطاءً، يتوقف التنفيذ عند هذه النقطة مع طلب عرض هذه الأخطاء.
- يتم التحقق من وجود اسم مستخدم. إذا كان المستخدم قد استخدم نموذج اختيار اسم المستخدم، فسيكون اسم المستخدم موجودًا بالفعل. لكن يمكن للمستخدم أيضًا كتابة عنوان URL الخاص بالإجراء مباشرةً في متصفحه دون تمرير أي معلمات. في حالة عدم وجود اسم مستخدم، يتم إعادة عرض قائمة أسماء المستخدمين.
- في حالة وجود اسم مستخدم، يتم طلب المعلومات المرتبطة بفئة «users» الخاصة بالمجال. إذا لم تعثر هذه الفئة على اسم المستخدم المطلوب، يتم عرض صفحة الأخطاء. وإلا، يتم إنشاء كائن InfosLoginBean لتضمين المعلومات التي تحتاجها طريقة العرض infos.jsp. يتم إدراج هذا الكائن في الاستعلام، ثم يتم عرض الصفحة infos.jsp.
7.7. النشر
هيكل التطبيق كالتالي:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
7.8. الخلاصة
لقد استخدمنا Struts في تطبيق واقعي يستخدم فئة الأعمال. كما أوضحنا أنه يجب إيلاء اهتمام خاص للطلب المرسل من العميل وعدم وضع أي افتراضات بشأن طبيعته. فقد يكون الطلب بأي شكل من الأشكال، ويجب على أي تطبيق أن يبدأ بالتحقق من صحته.











