Skip to content

3. برنامهٔ نمونه – 01: rdvmedecins-jsf2-ejb

متن زیر به اسناد زیر در اشاره دارد:

  • [ref7]: مقدمه‌ای بر Java EE 5 (ژوئن 2010) [http://tahe.developpez.com/java/javaee]. این سند مقدمه‌ای بر JSF 1 و EJB3 ارائه می‌دهد.
  • [ref8]: پایداری جاوا در عمل (ژوئن ۲۰۰۷) [http://tahe.developpez.com/java/jpa]. این سند پایداری داده‌ها را با JPA (پایداری جاوا API) معرفی می‌کند.
  • [ref9]: ساخت سرویس وب جاوا EE با استفاده از NetBeans و سرور GlassFish (ژانویه ۲۰۰۹) [http://tahe.developpez.com/java/webservice-jee]. این سند فرایند ساخت یک سرویس وب را بررسی می‌کند.

برنامهٔ مثال مورد بررسی از [ref9] گرفته شده است.

3.1. L'application

یک شرکت خدمات فناوری اطلاعات، [ISTIA-AGI]، مایل است یک سرویس رزرو وقت ملاقات ارائه دهد. بازار هدف اصلی، پزشکان متخصص مستقل است. این پزشکان معمولاً منشی ندارند. بنابراین مراجعانی که مایل به رزرو وقت ملاقات هستند، مستقیماً با پزشک تماس می‌گیرند. این امر به این معناست که پزشک در طول روز مکرراً مزاحمتی را تجربه می‌کند، که زمان قابل تخصیص او به بیمارانش را کاهش می‌دهد. شرکت [ISTIA-AGI] مایل است خدمتی برای رزرو وقت ملاقات ارائه دهد که بر اساس اصل زیر عمل می‌کند:

  • یک دفتر اداری نوبت‌دهی را برای تعداد زیادی از پزشکان انجام می‌دهد. این دفتر می‌تواند تنها توسط یک نفر اداره شود. حقوق آن فرد به طور مساوی بین تمام پزشکانی که از این سرویس استفاده می‌کنند تقسیم می‌شود.
  • دفتر اداری و همه پزشکان به اینترنت متصل هستند
  • قرارها در یک پایگاه داده متمرکز ثبت می‌شوند که هم برای دفتر اداری و هم برای پزشکان از طریق اینترنت قابل دسترسی است
  • ویزیت‌های RV معمولاً توسط دبیرخانه رزرو می‌شوند. همچنین ممکن است توسط خود پزشکان رزرو شوند. این امر به‌ویژه زمانی صادق است که در پایان یک ویزیت، خود پزشک یک قرار ملاقات جدید RV برای بیمار خود تعیین می‌کند.

معماری سرویس رزرو RV به شرح زیر است:

پزشکان زمانی که دیگر مجبور به مدیریت RV نباشند، کارآمدتر می‌شوند. اگر تعداد آنها کافی باشد، سهم آنها در هزینه‌های جاری دبیرخانه حداقلی خواهد بود.

شرکت [ISTIA-AGI] تصمیم گرفته است اپلیکیشن را در دو نسخه توسعه دهد:

  • نسخه JSF / EJB3 / JPA EclipseLink / سرور GlassFish:
  • و نسخه‌ای JSF / Spring / JPA Hibernate / Tomcat server:

3.2. نحوه کار برنامه

ما به این برنامه [RdvMedecins] خواهیم گفت. در زیر اسکرین‌شات‌هایی آمده است که نحوه کار آن را نشان می‌دهد.

صفحه اصلی برنامه به شرح زیر است:

از این صفحهٔ اولیه، کاربر (دبیر، دکتر) تعدادی اقدام را انجام خواهد داد. این موارد در زیر نشان داده شده‌اند. نمای سمت چپ، صفحه‌ای را نشان می‌دهد که کاربر از آن درخواست می‌کند؛ نمای سمت راست، پاسخی را که توسط سرور ارسال می‌شود، نشان می‌دهد.

در نهایت، ممکن است یک صفحه خطا نیز نمایش داده شود:

3.3. پایگاه داده

بیایید به معماری برنامه‌ای که در حال ساخت آن هستیم بازگردیم:

پایگاه داده، که آن را [dbrdvmedecins2] می‌نامیم، یک پایگاه داده MySQL5 است که شامل چهار جدول می‌باشد:

  

3.3.1. جدول [MEDECINS]

این شامل اطلاعاتی دربارهٔ پزشکانی است که توسط برنامهٔ [RdvMedecins] مدیریت می‌شوند.

  • ID: شماره شناسایی پزشک – کلید اصلی جدول
  • VERSION: شماره‌ای که نسخهٔ سطر در جدول را شناسایی می‌کند. این شماره هر بار که تغییری در سطر ایجاد شود، یک واحد افزایش می‌یابد.
  • NOM: نام خانوادگی پزشک
  • PRENOM: نام کوچک آنها
  • TITRE: عنوان آنها (خانم، بانو، آقای)

3.3.2. جدول [CLIENTS]

بیماران پزشکان مختلف در جدول [CLIENTS] ثبت می‌شوند:

  • ID: شماره شناسه مشتری – کلید اصلی جدول
  • VERSION: شماره‌ای که نسخهٔ سطر در جدول را شناسایی می‌کند. این شماره هر بار که تغییری در سطر ایجاد می‌شود، یک واحد افزایش می‌یابد.
  • NOM: نام خانوادگی مشتری
  • PRENOM: نام کوچک آنها
  • TITRE: عنوان آنها (خانم، بانو، آقا)

3.3.3. جدول [CRENEAUX]

این جدول بازه‌های زمانی را که ثبت رکوردهای RV امکان‌پذیر است، فهرست می‌کند:

  • ID: شماره‌ای که جایگاه زمانی را شناسایی می‌کند – کلید اصلی جدول (ردیف 8)
  • VERSION: عددی که نسخهٔ سطر در جدول را شناسایی می‌کند. این عدد هر بار که تغییری در سطر ایجاد شود، یک واحد افزایش می‌یابد.
  • ID_MEDECIN: شماره‌ای که پزشک مربوط به این اسلات را شناسایی می‌کند – کلید خارجی روی ستون MEDECINS (ID).
  • HDEBUT: زمان شروع اسلات
  • MDEBUT: دقیقه شروع اسلات
  • HFIN: زمان پایان اسلات
  • MFIN: دقیقه پایان اسلات

رده‌ی دوم جدول [CRENEAUX] (به [1] بالا مراجعه کنید) نشان می‌دهد که برای مثال، اسلات شمارهٔ ۲ از ساعت ۸:۲۰ شروع و در ساعت ۸:۴۰ پایان می‌یابد و به پزشک شمارهٔ ۱ اختصاص داده شده است. (خانم ماری PELISSIER).

3.3.4. جدول [RV]

ورودی‌های RV را برای هر پزشک فهرست می‌کند:

  • ID: شناسه یکتا برای RV – کلید اصلی
  • JOUR: روز RV
  • ID_CRENEAU: بازه زمانی برای RV – کلید خارجی که به فیلد [ID] در جدول [CRENEAUX] ارجاع می‌دهد – هم بازه زمانی و هم پزشک مربوطه را مشخص می‌کند.
  • ID_CLIENT: شماره مشتری که رزرو برای او انجام می‌شود – کلید خارجی روی فیلد [ID] در جدول [CLIENTS]

این جدول دارای محدودیت یکتایی بر روی « » برای مقادیر در ستون‌های الحاقی (JOUR, ID_CRENEAU) است:

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

اگر یک ردیف در جدول [RV] دارای مقدار (JOUR1, ID_CRENEAU1) برای ستون‌ها (JOUR, ID_CRENEAU)، این مقدار نمی‌تواند در هیچ جای دیگری ظاهر شود. در غیر این صورت، این بدان معناست که دو رکورد RV هم‌زمان برای یک پزشک ثبت شده‌اند. از منظر برنامه‌نویسی جاوا، درایور پایگاه داده JDBC هنگام وقوع این امر یک SQLException را فعال می‌کند.

ورودی مربوط به id که برابر با ۳ است (رجوع شود به [1] در بالا)، نشان می‌دهد که یک RV برای اسلات شماره ۲۰ و مشتری شماره ۴ در تاریخ ۲۳ اوت ۲۰۰۶ رزرو شده است. جدول [CRENEAUX] به ما می‌گوید که اسلات شمارهٔ ۲۰ معادل بازهٔ زمانی ۱۶:۲۰–۱۶:۴۰ است و متعلق به پزشک شمارهٔ ۱ (خانم ماری PELISSIER) می‌باشد. جدول [CLIENTS] نشان می‌دهد که مشتری شمارهٔ ۴ خانم بریژیت BISTROU است.

3.3.5. ایجاد پایگاه داده

برای ایجاد جداول و پر کردن آن‌ها می‌توانید از اسکریپت [dbrdvmedecins2.sql] استفاده کنید که در وب‌سایت مثال‌ها موجود است. با استفاده از [WampServer] (بخش 1.3.3 را ببینید)، می‌توانید به شرح زیر عمل کنید:

  • در [1]، روی آیکون [WampServer] کلیک کرده و گزینه [PhpMyAdmin] [2] را انتخاب کنید،
  • به [3]؛ در پنجره‌ای که باز می‌شود، پیوند [Bases de données] را انتخاب کنید،
  • به [2]، یک پایگاه داده با نام [4] با کدگذاری [5] ایجاد کنید،
  • در [7]، پایگاه داده ایجاد شده است. روی لینک آن کلیک کنید،
  • در [8]، ما یک فایل SQL را وارد می‌کنیم،
  • که آن را با استفاده از دکمه [9] از سیستم فایل انتخاب می‌کنید،
  • در [11]، اسکریپت SQL را انتخاب کرده و در [12] آن را اجرا کنید،
  • در [13]، چهار جدول پایگاه داده ایجاد شده‌اند. یکی از پیوندها را دنبال کنید،
  • در [14]، محتویات جدول.

پس از این به این پایگاه داده بازنمی‌گردیم. با این حال، از خواننده دعوت می‌شود تا روند توسعه آن را با پیشرفت برنامه‌ها دنبال کند، به‌ویژه زمانی که کارها به درستی پیش نمی‌روند.

3.4. لایه‌های [DAO] و [JPA]

بیایید به معماری‌ای که باید بسازیم بازگردیم:

ما قصد داریم چهار پروژه Maven بسازیم:

  • یک پروژه برای لایه‌های [DAO] و [JPA
  • یک پروژه برای لایه [métier
  • یک پروژه برای لایه [web]،
  • یک پروژه شرکتی که سه پروژه قبلی را گرد هم می‌آورد.

ما اکنون در حال ساخت پروژه Maven برای لایه‌های [DAO] و [JPA] هستیم.

توجه: درک لایه‌های [métier]، [DAO] و [JPA] نیازمند دانش Java EE است. برای این کار می‌توانید به [ref7] (بند ۳ را ببینید) مراجعه کنید.

3.4.1. پروژه NetBeans

به شرح زیر است:

  • در [1]، ما یک پروژه Maven از نوع [EJB Module] [2] ایجاد می‌کنیم،
  • در [3]، به پروژه یک نام می‌دهیم،
  • در [4]، سرور GlassFish را انتخاب می‌کنیم،
  • در [5]، پروژهٔ تولیدشده.

3.4.2. تولید لایه [JPA]

بیایید به معماری‌ای که باید بسازیم بازگردیم:

با NetBeans، می‌توان به‌طور خودکار لایه [JPA] و لایه [EJB] را تولید کرد که کنترل دسترسی به اشیاء تولیدشده JPA را بر عهده دارد. آشنایی با این روش‌های تولید خودکار ارزشمند است، زیرا کد تولیدشده بینش‌های ارزشمندی در مورد نحوه نوشتن اشیاء JPA یا کدی که از آن‌ها استفاده می‌کند (EJB) ارائه می‌دهد.

اکنون برخی از این ابزارهای تولید خودکار را شرح خواهیم داد. برای درک کد تولیدشده، نیاز به درک خوبی از اشیاء JPA، [ref8EJB و [ref7] دارید (به پاراگراف ۳ مراجعه کنید).

3.4.2.1. ایجاد یک اتصال NetBeans به پایگاه داده

  • SGBD و MySQL را اجرا کنید تا BD در دسترس باشد،
  • یک اتصال NetBeans به پایگاه داده [dbrdvmedecins2] ایجاد کنید،
  • در برگه [Services] [1]، در شاخه [Databases] [2]، درایور JDBC MySQL [3] را انتخاب کنید،
  • سپس گزینه [4] «اتصال با استفاده از» را برای ایجاد یک اتصال به پایگاه داده MySQL انتخاب کنید،
  • در [5]، اطلاعات مورد درخواست را وارد کنید. در [6]، نام پایگاه داده را وارد کنید؛ در [7]، نام کاربری و رمز عبور پایگاه داده را وارد کنید؛
  • در [8]، می‌توانید جزئیاتی را که ارائه کرده‌اید، آزمایش کنید،
  • در [9]، پیامی که در صورت صحیح بودن جزئیات انتظار دارید ببینید،
  • در [10]، اتصال برقرار می‌شود. چهار جدول در پایگاه داده متصل نمایش داده می‌شوند.

3.4.2.2. ایجاد یک واحد پایداری

بیایید به معماری که در حال حاضر در دست ساخت است بازگردیم:

ما در حال حاضر در حال ساخت لایه [JPA] هستیم. پیکربندی آن در فایلی به نام [persistence.xml] تعریف شده است که در آن واحدهای پایداری تعریف می‌شوند. هر یک از این موارد به اطلاعات زیر نیاز دارد:

  • اطلاعات دسترسی به پایگاه داده (JDBC: URL، نام کاربری، رمز عبور)،
  • کلاس‌هایی که نمایانگر جداول پایگاه داده خواهند بود،
  • پیاده‌سازی JPA مورد استفاده. JPA یک مشخصه است که توسط محصولات مختلف پیاده‌سازی شده است. در اینجا، ما از EclipseLink استفاده خواهیم کرد که پیاده‌سازی پیش‌فرض سرور GlassFish است. این کار ما را از افزودن کتابخانه‌های یک پیاده‌سازی دیگر به GlassFish بی‌نیاز می‌کند.

NetBeans می‌تواند این فایل پایداری را با استفاده از یک جادوگر ایجاد کند.

  • روی پروژه کلیک راست کرده و «ایجاد یک واحد پایداری» [1] را انتخاب کنید،
  • در [2]، برای واحد پایداری که ایجاد می‌کنید، نامی انتخاب کنید،
  • در [3]، پیاده‌سازی JPA EclipseLink (JPA 2.0
  • در [4]، مشخص کنید که تراکنش‌های پایگاه داده توسط کانتینر EJB روی سرور GlassFish مدیریت خواهند شد،
  • در [5]، مشخص کنید که جداول در BD قبلاً ایجاد شده‌اند و بنابراین نباید دوباره ایجاد شوند،
  • در [6]، یک منبع داده جدید برای سرور GlassFish ایجاد کنید،
  • در [7]، نام JNDI (Java Naming Directory Interface) را اختصاص دهید،
  • در [8]، این نام را به اتصال MySQL که در مرحله قبلی ایجاد شده است، پیوند دهید،
  • به [9]، جادوگر را تکمیل کنید،
  • به‌عنوان [10]، پروژه جدید،
  • در [11]، فایل [persistence.xml] در پوشه [META-INF] ایجاد شد،
  • در [12]، پوشه‌ای به نام [setup] ایجاد شد،
  • در [13]، وابستگی‌های جدید به پروژه Maven اضافه شدند.

فایل تولیدشده [META-INF/persistence.xml] به شرح زیر است:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="dbrdvmedecins2-PU" transaction-type="JTA">
    <jta-data-source>jdbc/dbrdvmedecins2</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

این فایل حاوی اطلاعاتی است که در ویزارد ارائه شده است:

  • خط ۳: نام واحد پایداری،
  • خط ۳: نوع تراکنش‌های پایگاه داده، در این مورد JTA (Java Transaction API) تراکنش‌هایی که توسط کانتینر EJB3 روی سرور GlassFish مدیریت می‌شوند،
  • خط ۴: نام منبع داده JNDI.

به طور معمول، این فایل شامل نوع پیاده‌سازی JPA است که در حال استفاده می‌باشد. در ویزارد، ما EclipseLink را مشخص کردیم. از آنجا که این پیاده‌سازی JPA است که به‌طور پیش‌فرض توسط سرور GlassFish استفاده می‌شود، در فایل [persistence.xml] ذکر نشده است.

در زبانه [Design] می‌توانید نمایی کلی از فایل [persistence.xml] را مشاهده کنید:

برای دریافت لاگ‌های EclipseLink، از فایل [persistence.xml] زیر استفاده خواهیم کرد:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="dbrdvmedecins2-PU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/dbrdvmedecins2</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/> 
    </properties>
  </persistence-unit>
</persistence>
  • خط ۴: مشخص می‌کند که ما از پیاده‌سازی JPA از EclipseLink استفاده می‌کنیم،
  • خطوط ۷–۹: این‌ها حاوی ویژگی‌های پیکربندی برای ارائه‌دهنده JPA هستند؛ در این مورد، EclipseLink،
  • خط ۸: این ویژگی ثبت دستورات SQL را که توسط EclipseLink صادر می‌شوند، فعال می‌کند.

فایل [glassfish-resources.xml] که ایجاد شده است به شرح زیر است:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-connection-pool allow-non-component-callers="false" ... steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
        <property name="serverName" value="localhost"/>
        <property name="portNumber" value="3306"/>
        <property name="databaseName" value="dbrdvmedecins2"/>
        <property name="User" value="root"/>
        <property name="Password" value=""/>
        <property name="URL" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    </jdbc-connection-pool>
    <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins2" object-type="user" pool-name="mysql_dbrdvmedecins2_rootPool"/>
</resources>

این فایل حاوی اطلاعاتی است که ما در دو جادوگر قبلی ارائه دادیم:

  • خطوط ۵–۱۱: ویژگی‌های JDBC پایگاه داده MySQL5 [dbrdvmedecins2
  • خط ۱۳: نام منبع داده JNDI.

این فایل برای ایجاد منبع داده JNDI [jdbc/dbrdvmedecins2] روی سرور GlassFish استفاده خواهد شد. این مورد به آن سرور خاص مربوط است. برای سرور دیگر، معمولاً از طریق یک ابزار مدیریتی، به رویکرد متفاوتی نیاز خواهد بود. چنین ابزاری برای GlassFish نیز موجود است.

در نهایت، وابستگی‌ها به پروژه اضافه شده‌اند. فایل [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</groupId>
    <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>ejb</packaging>

    <name>mv-rdvmedecins-ejb-dao-jpa</name>

    ...
    <dependencies>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.3.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>javax.persistence</artifactId>
            <version>2.0.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
            <version>2.3.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

...
    <repositories>
        <repository>
            <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
            <id>eclipselink</id>
            <layout>default</layout>
            <name>Repository for library Library[eclipselink]</name>
        </repository>
    </repositories>
</project>
  • خطوط ۳۲–۳۷: یک لایه [JPA] به آرتیفکت [javaee-api] نیاز دارد؛
  • خطوط 16، 22، 28: آرتفکت‌های مورد نیاز پیاده‌سازی JPA / EclipseLink که در اینجا استفاده شده است.
  • خطوط ۱۸، ۲۴، ۳۰ و ۳۶: همهٔ مصنوعات دارای ویژگی provided هستند. شایان ذکر است که این بدان معناست که این آرتفکت‌ها برای کامپایل لازم هستند اما برای اجرا ضروری نیستند. در واقع، در زمان اجرا، این آرتفکت‌ها (provided) توسط سرور GlassFish فراهم می‌شوند،
  • خطوط ۴۱–۴۸: یک مخزن جدید آرتفکت Maven تعریف کنید، جایی که آرتفکت‌های EclipseLink یافت می‌شوند.

3.4.2.3. تولید اجزای JPA

اشیاء JPA را می‌توان با استفاده از یک جادوگر NetBeans تولید کرد:

  • در [1]، اشیاء JPA از یک پایگاه داده ایجاد می‌شوند،
  • در [2]، منبع دادهٔ ایجادشدهٔ قبلی [jdbc / dbrdvmedecins2] انتخاب می‌شود،
  • در [3]، فهرست جداول برای این منبع داده،
  • در [4]، همهٔ آن‌ها را انتخاب کنید،
  • در [5]، جداول انتخاب‌شده،
  • در [6]، ما نامی را به کلاس‌های جاوا که با چهار جدول مرتبط هستند، اختصاص می‌دهیم،
  • و همچنین یک نام بسته ([7])،
  • در [8]، JPA سطرهای جدول از BD را در مجموعه‌ها گروه‌بندی می‌کند. ما یک لیست را به عنوان مجموعه انتخاب می‌کنیم،
  • در [9]، کلاس‌های جاوا که توسط جادوگر ایجاد شده‌اند.

3.4.2.4. اشیاء تولیدشده JPA

اینتیتی [Medecin] نمایانگر جدول [medecins] است. کلاس جاوا مملو از آنوتیشن‌ها است که خواندن کد را در نگاه اول دشوار می‌کند. اگر تنها آنچه برای درک نقش اینتیتی ضروری است را حفظ کنیم، کد زیر به دست می‌آید:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
  
@Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;
  
  @Column(name = "TITRE")
  private String titre;

  @Column(name = "NOM")
  private String nom;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "PRENOM")
  private String prenom;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
  private List<Creneau> creneauList;

// سازنده‌ها
....

   // گیرنده و تنظیم‌کننده
....

  @Override
  public int hashCode() {
  ...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • در خط ۴، anotation @Entity کلاس [Medecin] را به عنوان یک entity JPA، c.a.d تعیین می‌کند. یک کلاس مرتبط با جدولی برای BD از طریق API و JPA،
  • خط ۵، نام جدول BD که با انتیتای JPA مرتبط است. هر فیلد در جدول با یک فیلد در کلاس جاوا مطابقت دارد؛
  • خط ۶، این کلاس رابط Serializable را پیاده‌سازی می‌کند. این امر در برنامه‌های کاربردی کلاینت/سرور ضروری است، جایی که اشیاء بین کلاینت و سرور سریالیزه می‌شوند.
  • خطوط ۱۰–۱۱: فیلد id کلاس [Medecin] با فیلد [ID] (خط ۱۰) در جدول [medecins] مطابقت دارد،
  • خطوط ۱۳–۱۴: فیلد «title» کلاس [Medecin] با فیلد [TITRE] (خط ۱۳) در جدول [medecins] مطابقت دارد،
  • رده‌های ۱۶–۱۷: فیلد «name» در کلاس [Medecin] با فیلد [NOM] (رده‌ی ۱۶) در جدول [medecins] مطابقت دارد،
  • رده‌های ۱۹–۲۰: فیلد «نسخه» در کلاس [Medecin] با فیلد [VERSION] (رده‌ی ۱۹) در جدول [medecins] مطابقت دارد. در اینجا، جادوگر تشخیص نمی‌دهد که این ستون در واقع یک ستون نسخه است که باید هر بار که ردیفی که به آن تعلق دارد تغییر می‌کند، افزایش یابد. برای اختصاص این نقش به آن، باید تگ @Version اضافه شود. ما این کار را در مرحله بعد انجام خواهیم داد،
  • سطور 22–23: فیلد 'prenom' کلاس [Medecin] با فیلد [PRENOM] در جدول [medecins] مطابقت دارد،
  • خطوط ۱۰–۱۱: فیلد id با کلید اصلی [ID] جدول مطابقت دارد. حاشیه‌نویسی‌های روی خطوط ۸–۹ این نکته را روشن می‌کنند،
  • خط ۸: انوتیشن @Id نشان می‌دهد که فیلد انوته‌شده با کلید اصلی جدول مرتبط است،
  • خط ۹: لایه [JPA] کلید اصلی را برای سطرهایی که در جدول [Medecins] درج می‌کند، تولید خواهد کرد. چندین استراتژی ممکن وجود دارد. در اینجا، استراتژی GenerationType.IDENTITY مشخص می‌کند که لایه JPA از حالت auto_increment در جدول MySQL استفاده خواهد کرد،
  • سطور ۲۵–۲۶: جدول [creneaux] دارای یک کلید خارجی است که به جدول [medecins] ارجاع می‌دهد. یک اسلات متعلق به یک پزشک است. برعکس، یک پزشک چندین اسلات مرتبط با خود دارد. بنابراین ما یک رابطه یک‌به‌چند (یک پزشک به چندین نوبت) داریم، رابطه‌ای که توسط انوتیشن @OneToMany از طریق JPA (خط 25) تعریف شده است. میدان در خط 26 شامل تمام بازه‌های زمانی دکتر خواهد بود. این کار بدون هیچ برنامه‌نویسی انجام می‌شود. برای درک کامل خط 25، باید کلاس [Creneau] را معرفی کنیم.

این به شرح زیر است:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "MDEBUT")
  private int mdebut;

  @Column(name = "HFIN")
  private int hfin;

  @Column(name = "HDEBUT")
  private int hdebut;

  @Column(name = "MFIN")
  private int mfin;

  @Column(name = "VERSION")
  private int version;

  @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Medecin idMedecin;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
  private List<Rv> rvList;

// سازنده‌ها
...
// گیرنده‌ها و تنظیم‌کننده‌ها
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}

ما فقط در مورد حاشیه‌نویسی‌های جدید نظر می‌دهیم:

  • ما بیان کرده‌ایم که جدول [creneaux] یک کلید خارجی به جدول [medecins] دارد: یک اسلات با یک پزشک مرتبط است. چندین اسلات ممکن است با یک پزشک مرتبط باشند. یک رابطه از جدول [creneaux] به جدول [medecins] وجود دارد که به صورت چند (فاصله زمانی) به یک (پزشک) تعریف شده است. توضیح @ManyToOne در خط ۳۲ برای تعریف کلید خارجی استفاده می‌شود،
  • ردیف ۳۱، با حاشیه‌نویسی @JoinColumn، رابطه کلید خارجی را مشخص می‌کند: ستون [ID_MEDECIN] در جدول [creneaux] یک کلید خارجی بر روی ستون [ID] در جدول [medecins] است،
  • خط ۳۳: مرجعی به پزشکی که اسلات را در اختیار دارد. این نیز بدون هیچ برنامه‌نویسی به‌دست می‌آید.

بنابراین رابطه کلید خارجی بین انتیت [Creneau] و انتیت [Medecin] با دو انوتیشن نمایش داده می‌شود:

  • در موجودیت [Creneau]:

@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
private Medecin idMedecin;
  • در موجوده [Medecin]:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;

هر دو نشانه‌گذاری، یک رابطه را منعکس می‌کنند: رابطه کلید خارجی از جدول [creneaux] به جدول [medecins]. گفته می‌شود که این دو معکوس یکدیگر هستند. فقط رابطه @ManyToOne ضروری است. این رابطه به طور صریح، رابطه کلید خارجی را تعریف می‌کند. رابطه @OneToMany اختیاری است. در صورت وجود، این رابطه صرفاً به رابطه @ManyToOne که با آن مرتبط است، ارجاع می‌دهد. این معنای ویژگی mappedBy در خط ۱ از موجودیت [Medecin] است. مقدار این ویژگی، نام فیلدی در موجودیت [Creneau] است که دارای نشانه‌گذاری @ManyToOne است، که کلید خارجی را مشخص می‌کند. همچنان در همین خط 1 از موجودیت [Medecin]، ویژگی cascade=CascadeType.ALL رفتار موجوده [Medecin] را در رابطه با موجوده [Creneau] تعیین می‌کند:

  • اگر یک انتیت جدید [Medecin] در پایگاه داده درج شود، آنگاه انتیت‌های [Creneau] در فیلد روی خط 2 نیز باید درج شوند،
  • اگر یک موجودیت [Medecin] در پایگاه داده اصلاح شود، آنگاه موجودیت‌های [Creneau] در فیلد روی خط 2 نیز باید اصلاح شوند،
  • اگر یک موجودیت [Medecin] از پایگاه داده حذف شود، آنگاه موجودیت‌های [Creneau] در فیلد روی خط ۲ نیز باید حذف شوند.

ما کد دو موجودیت دیگر را بدون هیچ‌گونه توضیحات خاص ارائه می‌دهیم، زیرا آن‌ها هیچ نشانه جدیدی معرفی نمی‌کنند.

وجود [Client]


package rdvmedecins.jpa;

...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "TITRE")
  private String titre;

  @Column(name = "NOM")
  private String nom;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "PRENOM")
  private String prenom;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
  private List<Rv> rvList;

// سازنده‌ها
...
//گیرنده و تنظیم‌کننده
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • خطوط ۲۴–۲۵ رابطه کلید خارجی بین جدول [rv] و جدول [clients] را نشان می‌دهند.

وجودی [Rv]:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;

  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneau idCreneau;

  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client idClient;

   // سازندگان
...

   //گیرنده و تنظیم‌کننده
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • ردیف ۱۳ فیلد «day» از نوع Java را در Date تعریف می‌کند. این مشخص می‌کند که در جدول [rv]، ستون [JOUR] (ردیف ۱۲) از نوع تاریخ (بدون زمان) است،
  • رده‌های ۱۶–۱۸: رابطه کلید خارجی را از جدول [rv] به جدول [creneaux] تعریف می‌کنند،
  • خطوط ۲۰–۲۲: رابطه کلید خارجی را از جدول [rv] به جدول [clients] تعریف می‌کند.

تولید خودکار اشیاء JPA یک پایهٔ کاری در اختیار ما قرار می‌دهد. گاهی این کافی است، گاهی کافی نیست. در اینجا چنین است:

  • ما باید انوتیشن @Version را به فیلدهای نسخهٔ مختلف این اِنتِیتی‌ها اضافه کنیم،
  • ما باید متدهای toString را بنویسیم که صریح‌تر از متدهای تولیدشده باشند،
  • اشیاء [Medecin] و [Client] مشابه هستند. ما آنها را از کلاسی به نام [Personne] مشتق خواهیم کرد،
  • ما روابط معکوس @OneToMany را از روابط @ManyToOne حذف خواهیم کرد. این روابط ضروری نیستند و باعث پیچیدگی‌های برنامه‌نویسی می‌شوند،
  • ما اعتبارسنجی @NotNull را روی کلیدهای اصلی حذف می‌کنیم. هنگام پایدارسازی یک موجودیت JPA با MySQL، موجودیت اصلی دارای کلید اصلی null است. فقط پس از پایداری در پایگاه داده است که کلید اصلی عنصر پایدار شده، مقدار می‌یابد.

با این مشخصات، کلاس‌های مختلف به شرح زیر هستند:

کلاس Person برای نمایش پزشکان و مشتریان استفاده می‌شود:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@MappedSuperclass
public class Personne implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Basic(optional = false)
  @Size(min = 1, max = 5)
  @Column(name = "TITRE")
  private String titre;

  @Basic(optional = false)
  @NotNull
  @Size(min = 1, max = 30)
  @Column(name = "NOM")
  private String nom;

  @Basic(optional = false)
  @NotNull
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @Basic(optional = false)
  @NotNull
  @Size(min = 1, max = 30)
  @Column(name = "PRENOM")
  private String prenom;
// سازندگان
...

// گیرنده و تنظیم‌کننده
  ...

  @Override
  public String toString() {
    return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
  }
  
}
  • خط ۸: توجه کنید که کلاس [Personne] خود یک انتیت (Entity) نیست. این کلاس به‌عنوان کلاس والد برای انتیت‌ها عمل خواهد کرد. انوتیشن @MappedSuperClass این موضوع را نشان می‌دهد.

انتیتی [Client] سطرهای جدول [clients] را در بر می‌گیرد. این کلاس از کلاس قبلی [Personne] ارث می‌برد:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
  private static final long serialVersionUID = 1L;

// سازندگان
...

  @Override
  public int hashCode() {
...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
  }
  
}
  • خط ۶: کلاس [Client] یک انتیت JPA است،
  • خط ۷: این کلاس با جدول [clients] مرتبط است،
  • خط ۸: از کلاس [Personne] مشتق شده است.

اِنتیتی [Medecin] که سطرهای جدول [medecins] را در بر می‌گیرد، از همان الگو پیروی می‌کند:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
  private static final long serialVersionUID = 1L;

  // سازنده‌ها
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
  }
  
}

اِنتیته [Creneau] سطرهای جدول [creneaux] را در بر می‌گیرد:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "MDEBUT")
  private int mdebut;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "HFIN")
  private int hfin;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "HDEBUT")
  private int hdebut;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "MFIN")
  private int mfin;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Medecin medecin;

  // سازنده‌ها
  ...

  // گیرنده‌ها و تنظیم‌کننده‌ها
  ...
 
  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    //TODO: هشدار – این روش در صورتی که فیلدهای id تنظیم نشده باشند کار نخواهد کرد
    ...
  }

  @Override
  public String toString() {
    return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
  }
}
  • خطوط ۴۵–۴۷ رابطه «چند به یک» بین جدول [creneaux] و جدول [medecins] در پایگاه داده را مدل‌سازی می‌کنند: یک پزشک چندین نوبت ملاقات دارد، و یک نوبت ملاقات متعلق به یک پزشک واحد است.

اِنتیتِ [Rv] سطرهای جدول [rv] را در بر می‌گیرد:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "rv")
public class Rv implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;
  
  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneau creneau;
  
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client client;

   // سازنده‌ها
...

   // گیرنده و تنظیم‌کننده
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Rv[%s, %s, %s]", id, creneau, client);
  }
}
  • رده‌های ۲۹ تا ۳۱ رابطه «چند به یک» بین جدول [rv] و جدول [clients] را مدل‌سازی می‌کنند (یک مشتری ممکن است در چندین Rv ظاهر شود) در پایگاه داده، در حالی که خطوط 25–27 رابطه «چند به یک» بین جدول [rv] و جدول [creneaux] را مدل‌سازی می‌کنند (یک بازه زمانی ممکن است در چندین Rv ظاهر شود).

3.4.3. کلاس استثنا

کلاس استثنا [RdvMedecinsException] برای برنامه به شرح زیر است:


package rdvmedecins.exceptions;

import java.io.Serializable;
import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class RdvMedecinsException extends RuntimeException implements Serializable{

  // fields خصوصی
  private int code = 0;

  // سازنده‌ها
  public RdvMedecinsException() {
    super();
  }

  public RdvMedecinsException(String message) {
    super(message);
  }

  public RdvMedecinsException(String message, Throwable cause) {
    super(message, cause);
  }

  public RdvMedecinsException(Throwable cause) {
    super(cause);
  }

  public RdvMedecinsException(String message, int code) {
    super(message);
    setCode(code);
  }

  public RdvMedecinsException(Throwable cause, int code) {
    super(cause);
    setCode(code);
  }

  public RdvMedecinsException(String message, Throwable cause, int code) {
    super(message, cause);
    setCode(code);
  }

  // گیرنده‌ها و تنظیم‌کننده‌ها
  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }
}
  • خط ۷: این کلاس از کلاس [RuntimeException] ارث می‌برد. بنابراین کامپایلر نیازی ندارد که با استفاده از بلوک‌های try/catch به آن رسیدگی شود.
  • خط ۶: تگ @ApplicationException تضمین می‌کند که استثنا توسط یک استثنای نوع [EjbException] «بلعیده» نخواهد شد.

برای درک تگ @ApplicationException، بیایید به معماری سمت سرور بازگردیم:

استثنایی از نوع [RdvMedecinsException] توسط متدهای EJB در لایه [DAO] درون کانتینر EJB3 پرتاب شده و توسط آن رهگیری می‌شود. بدون تگ @ApplicationException، کانتینر EJB3 استثنایی را که رخ داده است در یک استثنای از نوع [EjbException] محصور می‌کند و آن را مجدداً پرتاب می‌کند. ممکن است نخواهید این محصورسازی انجام شود و بخواهید اجازه دهید استثناء از نوع [RdvMedecinsException] از محفظه EJB3 منتشر شود. این همان کاری است که تگ @ApplicationException آن را ممکن می‌سازد. علاوه بر این، ویژگی (rollback=true) این انوتیشن به کانtejner EJB3 دستور می‌دهد که اگر یک استثنا از نوع [RdvMedecinsException] در داخل متدی که به عنوان بخشی از یک تراکنش با SGBD اجرا می‌شود، رخ دهد، مبادله باید بازگردانده شود. در اصطلاح فنی، این عمل به عنوان انجام یک rollback روی مبادله شناخته می‌شود.

3.4.4. EJB لایه [DAO]

رابط جاوا [IDao] برای لایه [DAO] به شرح زیر است:


package rdvmedecins.dao;


import java.util.Date;
import java.util.List;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;

public interface IDao {

  // فهرست مشتریان
  public List<Client> getAllClients();
  // فهرست پزشکان
  public List<Medecin> getAllMedecins();
  // فهرست شکاف‌های قرار ملاقات پزشک
  public List<Creneau> getAllCreneaux(Medecin medecin);
  // فهرست قرارهای ملاقات پزشک در یک روز مشخص
  public List<Rv> getRvMedecinJour(Medecin medecin, Date jour);
  //یافتن مشتری بر اساس شناسه
  public Client getClientById(Long id);
  // پیدا کردن یک مشتری با شناسهٔ آن
  public Medecin getMedecinById(Long id);
  //یافتن یک قرار ملاقات با شناسهٔ آن
  public Rv getRvById(Long id);
  // پیدا کردن یک بازه زمانی مشخص‌شده با شناسه آن
  public Creneau getCreneauById(Long id);
  // افزودن یک RV
  public Rv ajouterRv(Date jour, Creneau creneau, Client client);
  // حذف یک RV
  public void supprimerRv(Rv rv);
}

این رابط پس از شناسایی نیازمندی‌های لایه [web] توسعه داده شد:

  • خط ۱۴: فهرست مشتریان. ما به این مورد برای پر کردن فهرست کشویی مشتریان نیاز خواهیم داشت،
  • خط 16: فهرست پزشکان. به این برای پر کردن فهرست کشویی پزشکان نیاز خواهیم داشت،
  • خط ۱۸: فهرست نوبت‌های خالی پزشک. ما به این فهرست برای نمایش دفترچه یادداشت پزشک در یک روز مشخص نیاز خواهیم داشت،
  • خط ۲۰: فهرست نوبت‌های دکتر برای یک روز مشخص. این مورد در ترکیب با روش قبلی به ما امکان می‌دهد تا دفترچه یادداشت دکتر را برای یک روز مشخص نمایش دهیم و نوبت‌های رزرو شده قبلی او را نشان دهیم،
  • خط ۲۲: به ما امکان می‌دهد تا یک مراجع را با شماره او جستجو کنیم. این روش به ما امکان می‌دهد تا مراجع را بر اساس انتخابی از لیست کشویی مراجع جستجو کنیم،
  • خط ۲۴: همین امر در مورد پزشکان نیز صدق می‌کند،
  • خط ۲۶: یک قرار ملاقات را با شماره آن بازیابی می‌کند. این قابلیت هنگام حذف یک قرار ملاقات کاربرد دارد تا پیش از آن بررسی شود که آیا واقعاً وجود دارد یا خیر،
  • خط ۲۸: یک بازه زمانی را با شماره آن بازیابی می‌کند. به شما امکان می‌دهد بازه زمانی را که کاربر مایل به افزودن یا حذف آن است، شناسایی کنید،
  • خط ۳۰: برای افزودن یک قرار ملاقات،
  • خط ۳۲: برای حذف یک قرار ملاقات.

رابط محلی [IDaoLocal] از EJB به سادگی از رابط قبلی [IDao] مشتق شده است:


package rdvmedecins.dao;

import javax.ejb.Local;

@Local
public interface IDaoLocal extends IDao{

}

همین امر در مورد رابط راه دور [IDaoRemote] نیز صدق می‌کند:


package rdvmedecins.dao;

import javax.ejb.Remote;

@Remote
public interface IDaoRemote extends IDao{

}

EJB و [DaoJpa] هر دو رابط‌های محلی و راه دور را پیاده‌سازی می‌کنند:


package rdvmedecins.dao;

...

@Singleton (mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal, IDaoRemote, Serializable {
  • خط ۵ نشان می‌دهد که رابط دور EJB با نام «rdvmedecins.dao» نامگذاری شده است. علاوه بر این، anotation @Singleton (Java EE6) تضمین می‌کند که تنها یک نمونه از EJB ایجاد شود. آناوتیشن @Stateless (جاوا EE5) یک EJB را تعریف می‌کند که می‌تواند در چندین نمونه ایجاد شود تا یک استخر از EJB را پر کند،
  • خط ۶ نشان می‌دهد که تمام متدهای EJB در داخل یک تراکنش که توسط کانتینر EJB3 مدیریت می‌شود، اجرا می‌گردند،
  • خط ۷ نشان می‌دهد که EJB رابط‌های محلی و راه دور را پیاده‌سازی می‌کند و همچنین قابل سریالی‌سازی است.

کد کامل برای EJB به شرح زیر است:


package rdvmedecins.dao;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import rdvmedecins.exceptions.RdvMedecinsException;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;

@Singleton (mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal, IDaoRemote, Serializable {

  @PersistenceContext
  private EntityManager em;

  // فهرست مراجعین
  public List<Client> getAllClients() {
    try {
      return em.createQuery("select rc from Client rc").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 1);
    }
  }

  //فهرست پزشکان
  public List<Medecin> getAllMedecins() {
    try {
      return em.createQuery("select rm from Medecin rm").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 2);
    }
  }

  // فهرست اسلات‌های قرار ملاقات برای یک پزشک مشخص
  // پزشک: پزشک
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    try {
      return em.createQuery("select rc from Creneau rc join rc.medecin m where m.id=:idMedecin").setParameter("idMedecin", medecin.getId()).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 3);
    }
  }

  // فهرست قرار ملاقات‌ها برای یک پزشک خاص در یک روز مشخص
  // پزشک: پزشک
  // روز: روز
  public List<Rv> getRvMedecinJour(Medecin medecin, Date jour) {
    try {
      return em.createQuery("select rv from Rv rv join rv.creneau c join c.idMedecin m where m.id=:idMedecin and rv.jour=:jour").setParameter("idMedecin", medecin.getId()).setParameter("jour", jour).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 3);
    }
  }

  // افزودن یک قرار ملاقات
  // روز: روز قرار ملاقات
  // slot: بازه زمانی قرار ملاقات
  // مشتری: مشتری‌ای که وقت ملاقات برای او رزرو شده است
  public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
    try {
      Rv rv = new Rv(null, jour);
      rv.setClient(client);
      rv.setCreneau(creneau);
      em.persist(rv);
      return rv;
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 4);
    }
  }

  // حذف یک قرار ملاقات
  // appointment: قرار ملاقات حذف‌شده
  public void supprimerRv(Rv rv) {
    try {
      em.remove(em.merge(rv));
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 5);
    }
  }

  //بازیابی یک مشتری خاص
  public Client getClientById(Long id) {
    try {
      return (Client) em.find(Client.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

  //بازیابی یک پزشک خاص
  public Medecin getMedecinById(Long id) {
    try {
      return (Medecin) em.find(Medecin.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

  //بازیابی یک قرار ملاقات مشخص
  public Rv getRvById(Long id) {
    try {
      return (Rv) em.find(Rv.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

  // بازیابی یک بازه زمانی مشخص
  public Creneau getCreneauById(Long id) {
    try {
      return (Creneau) em.find(Creneau.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }
}
  • خط ۲۲: شیء EntityManager که مدیریت دسترسی به زمینه پایداری را بر عهده دارد. هنگامی که کلاس نمونه برداری می‌شود، این فیلد توسط کانtejner EJB از طریق anotation @PersistenceContext در خط 21 مقداردهی اولیه خواهد شد،
  • خط ۲۷: پرس‌وجوی JPQL (زبان پرس‌وجوی پایداری جاوا) که تمام ردیف‌های جدول [clients] را به صورت لیستی از اشیاء [Client] برمی‌گرداند،
  • ردیف ۳۶: پرس‌وجویی مشابه برای پزشکان،
  • خط ۴۶: یک پرس‌وجوی JPQL که یک پیوند (join) بین جداول [creneaux] و [medecins] برقرار می‌کند. این پرس‌وجو با شناسهٔ پزشک پارامترگذاری شده است،
  • خط ۵۷: یک پرس‌وجوی JPQL که یک پیوند بین جداول [rv]، [creneaux] و [medecins] برقرار می‌کند و دارای دو پارامتر است: شناسهٔ پزشک و تاریخ قرار ملاقات،
  • خطوط ۶۹–۷۳: ایجاد یک قرار ملاقات، و سپس ذخیره‌سازی آن در پایگاه داده،
  • خط ۸۳: حذف یک قرار ملاقات از پایگاه داده،
  • خط ۹۲: اجرای یک پرس‌وجوی select در پایگاه داده برای یافتن یک مشتری خاص،
  • خط ۱۰۱: همانند مورد بالا برای یک پزشک،
  • خط ۱۱۰: مشابه مورد بالا برای یک قرار ملاقات،
  • خط ۱۱۹: همانند موارد بالا برای یک بازه زمانی،
  • تمام عملیات با استفاده از زمینه پایداری تعریف‌شده در خط ۲۲ احتمالاً با مشکلی در پایگاه داده مواجه خواهند شد. بنابراین همگی در یک بلوک try/catch قرار گرفته‌اند. هر استثنا در داخل استثنای «داخلی» RdvMedecinsException محصور می‌شود.

3.4.5. پیاده‌سازی درایور JDBC از MySQL

در معماری نشان داده شده در زیر:

EclipseLink به درایور JDBC از MySQL نیاز دارد. این باید در کتابخانه‌های سرور GlassFish در پوشه <glassfish>/domains/domain1/lib/ext نصب شود، که در آن <glassfish> پوشه نصب سرور GlassFish است. می‌توان آن را به شرح زیر دریافت کرد:

پوشه‌ای که باید درایور JDBC از MySQL را در آن قرار دهید، <پوشه Domains>[1]/domain1/lib/ext [2] است. این درایور در URL [http://www.mysql.fr/downloads/connector/j/] در دسترس است. پس از نصب، باید سرور GlassFish را مجدداً راه‌اندازی کرد تا این کتابخانهٔ جدید را شناسایی کند.

3.4.6. استقرار لایه EJB از لایه [DAO]

بیایید به معماری ساخته‌شده تا اینجا بازگردیم:

اسمبلی [web, métier, DAO, JPA] باید روی سرور GlassFish مستقر شود. این کار را به شرح زیر انجام می‌دهیم:

  • در [1]، پروژه Maven را ساخت می‌کنیم،
  • در [2]، آن را اجرا می‌کنیم،
  • در [3]، آن به سرور GlassFish مستقر شده است (زبانه‌ی [Services])

ممکن است کنجکاو باشید تا نگاهی به لاگ‌های GlassFish بیندازید:

در [1]، لاگ‌های GlassFish در تب [Output / Glassfish Server 3+] در دسترس هستند. آنها به شرح زیر هستند:

Config: The access type for the persistent class [class rdvmedecins.jpa.Personne] is set to [FIELD].
Config: The access type for the persistent class [class rdvmedecins.jpa.Client] is set to [FIELD].
Config: The access type for the persistent class [class rdvmedecins.jpa.Rv] is set to [FIELD].
Config: The target entity (reference) class for the many to one mapping element [field client] is being defaulted to: class rdvmedecins.jpa.Client.
Config: The target entity (reference) class for the many to one mapping element [field creneau] is being defaulted to: class rdvmedecins.jpa.Creneau.
Config: The access type for the persistent class [class rdvmedecins.jpa.Medecin] is set to [FIELD].
Config: The access type for the persistent class [class rdvmedecins.jpa.Creneau] is set to [FIELD].
Config: The target entity (reference) class for the many to one mapping element [field medecin] is being defaulted to: class rdvmedecins.jpa.Medecin.
Config: The alias name for the entity class [class rdvmedecins.jpa.Client] is being defaulted to: Client.
Config: The alias name for the entity class [class rdvmedecins.jpa.Rv] is being defaulted to: Rv.
Config: The alias name for the entity class [class rdvmedecins.jpa.Medecin] is being defaulted to: Medecin.
Config: The alias name for the entity class [class rdvmedecins.jpa.Creneau] is being defaulted to: Creneau.
Infos: rdvmedecins.jpa.Creneau actually got transformed
Infos: rdvmedecins.jpa.Medecin actually got transformed
Infos: rdvmedecins.jpa.Personne actually got transformed
Infos: rdvmedecins.jpa.Client actually got transformed
Infos: rdvmedecins.jpa.Rv actually got transformed
Infos: EclipseLink, version: Eclipse Persistence Services - 2.3.2.v20111125-r10461
Précis: Detected database platform: org.eclipse.persistence.platform.database.MySQLPlatform
Config: connecting(DatabaseLogin(
    platform=>DatabasePlatform
    user name=> ""
    connector=>JNDIConnector datasource name=>null
))
Config: Connected: jdbc:mysql://localhost:3306/dbrdvmedecins2
    User: root@localhost
    Database: MySQL  Version: 5.5.8-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
Config: connecting(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> ""
    connector=>JNDIConnector datasource name=>null
))
Config: Connected: jdbc:mysql://localhost:3306/dbrdvmedecins2
    User: root@localhost
    Database: MySQL  Version: 5.5.8-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
Infos: file:/D:/data/istia-1112/netbeans/dvp/jsf2-pf-pfm/maven/netbeans/rdvmedecins-jsf2-ejb/mv-rdvmedecins-ejb-dao-jpa/target/classes/_dbrdvmedecins2-PU login successful
Infos: EJB5181:Portable JNDI names for EJB DaoJpa: [java:global/istia.st_mv-rdvmedecins-ejb-dao-jpa_ejb_1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoLocal, java:global/istia.st_mv-rdvmedecins-ejb-dao-jpa_ejb_1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoRemote]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB DaoJpa: [rdvmedecins.dao#rdvmedecins.dao.IDaoRemote, rdvmedecins.dao]
Infos: istia.st_mv-rdvmedecins-ejb-dao-jpa_ejb_1.0-SNAPSHOT a été déployé en 270 ms.

خطوطی که توسط [Config] و [Précis] مشخص شده‌اند، لاگ‌های مربوط به EclipseLink هستند، در حالی که آنهایی که توسط [Infos] مشخص شده‌اند از GlassFish می‌آیند.

  • خطوط ۱–۱۲: EclipseLink اِنتیته‌های JPA را که شناسایی کرده است، پردازش می‌کند،
  • خطوط ۱۳–۱۷: اطلاعاتی که نشان می‌دهد پردازش موجودیت‌های JPA با موفقیت به پایان رسیده است،
  • خط ۱۸: EclipseLink گزارش خود را ارائه می‌دهد،
  • خط ۱۹: EclipseLink تشخیص می‌دهد که در حال تعامل با SGBD MySQL است،
  • خطوط ۲۰–۲۴: EclipseLink تلاش می‌کند به BD متصل شود،
  • خطوط ۲۵–۲۸: موفق شد،
  • رده‌های ۲۹–۳۳: در حال تلاش برای برقراری مجدد اتصال است، این بار به طور خاص با استفاده از پلتفرم MySQL (رده‌ی ۳۰)،
  • خطوط ۳۴–۳۷: در اینجا نیز موفقیت‌آمیز است،
  • خط ۳۸: تأیید اینکه واحد پایداری [dbrdvmedecins-PU] ایجاد شده است،
  • خط ۳۹: نام‌های قابل حمل رابط‌های راه دور و محلی EJB و [DaoJpa]، که در آن «قابل حمل» به معنای شناخته‌شده توسط همه سرورهای برنامه‌های کاربردی جاوا EE 6 است،
  • خط ۴۰: نام رابط‌های راه دور و محلی برای EJB و [DaoJpa]، که مختص GlassFish هستند. در آزمون پیش رو، از نام «rdvmedecins.dao» استفاده خواهیم کرد.

خطوط ۳۹ و ۴۰ مهم هستند. هنگام نوشتن یک کلاینت برای EJB روی GlassFish، لازم است از آن‌ها آگاه باشید.

3.4.7. آزمایش EJB لایه [DAO]

اکنون که لایه EJB از [DAO] برنامه ما مستقر شده است، می‌توانیم آن را آزمایش کنیم. این کار را به عنوان بخشی از یک برنامه کلاینت/سرور انجام خواهیم داد:

کلاینت رابط دور EJB [DAO] را که روی سرور GlassFish مستقر شده است، آزمایش خواهد کرد.

ما با ایجاد یک پروژه جدید Maven project شروع می‌کنیم:

  • در [1]، یک پروژه جدید ایجاد می‌کنیم،
  • در [2,3]، ما یک پروژه Maven از نوع [Java Application] ایجاد می‌کنیم،
  • در [4]، به آن نام می‌دهیم و آن را در همان پوشه با EJB و [DAO] قرار می‌دهیم،
  • به [5]، پروژه تولیدشده،
  • در [6]، کلاسی به نام [App.java] ایجاد شده است. آن را حذف خواهیم کرد،
  • در [7]، یک شاخه به نام [Source Packages] ایجاد شده است. ما هنوز با آن مواجه نشده بودیم. می‌توانیم تست‌های JUnit را به این شاخه اضافه کنیم. این کار را انجام خواهیم داد. ما کلاس تست تولیدشده [AppTest] را نگه نمی‌داریم،
  • در [8]، وابستگی‌های پروژه Maven قرار دارند. شاخه [Dependencies] خالی است. ما باید وابستگی‌های جدید را به آن اضافه کنیم. شعبه [Test Dependencies] شامل وابستگی‌های مورد نیاز برای تست است. در اینجا، کتابخانه مورد استفاده متعلق به فریم‌ورک JUnit نسخه ۳.۸ است. ما باید این را تغییر دهیم.

پروژه به شرح زیر در حال پیشرفت است:

  • به [1]، پروژه‌ای که در آن دو کلاس تولید شده به همراه وابستگی JUnit حذف شده‌اند.

بیایید به معماری کلاینت/سرور که برای تست استفاده خواهد شد بازگردیم:

کلاینت باید از رابط دوردستی که توسط EJB و [DAO] ارائه می‌شود، مطلع باشد. علاوه بر این، آن با JPA مبادله خواهد کرد. بنابراین به تعاریف این انتیت‌ها نیاز دارند. برای اطمینان از اینکه پروژه آزمایشی EJB به این اطلاعات دسترسی داشته باشد، ما پروژه‌های EJB و [DAO] را به‌عنوان وابستگی به پروژه اضافه خواهیم کرد:

  • در [1]، وابستگی به شاخه [Test Dependencies] را اضافه می‌کنیم،
  • در [2]، زبانه [Open Projects] را انتخاب کنید،
  • در [3]، پروژه Maven را برای EJB [DAO] انتخاب کنید،
  • در [4]، وابستگی اضافه شده است.

بیایید به معماری کلاینت/سرور تست بازگردیم:

در زمان اجرا، کلاینت و سرور از طریق شبکه TCP-IP با یکدیگر ارتباط برقرار می‌کنند. ما این تبادل‌ها را کدنویسی نخواهیم کرد. برای هر سرور برنامه‌ای، کتابخانه‌ای وجود دارد که باید در وابستگی‌های کلاینت گنجانده شود. کتابخانه مربوط به GlassFish با نام [gf-client] شناخته می‌شود. آن را اضافه می‌کنیم:

  • در [1]، یک وابستگی اضافه می‌کنیم،
  • در [2]، ویژگی‌های آرتیفکت مورد نظر را مشخص می‌کنیم،
  • در [3]، تعداد زیادی وابستگی اضافه می‌شود. Maven آن‌ها را دانلود خواهد کرد. این کار ممکن است چند دقیقه طول بکشد. سپس آن‌ها در مخزن محلی Maven ذخیره می‌شوند.

اکنون می‌توانیم تست JUnit را ایجاد کنیم:

  • در [2]، روی [Test Packages] کلیک راست کرده تا تست جدید JUnit ایجاد شود،
  • به [3]، به کلاس تست یک نام بدهید و یک پکیج ([4]) برای آن تعیین کنید،
  • به [5]، فریم‌ورک JUnit 4.x را انتخاب کنید،
  • در [6]، کلاس تست تولید شده،
  • و [7]، وابستگی‌های پروژه جدید 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</groupId>
  <artifactId>mv-client-rdvmedecins-ejb-dao</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-client-rdvmedecins-ejb-dao</name>
  <url>http://maven.apache.org</url>

  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>junit_4</id>
      <layout>default</layout>
      <name>Repository for library Library[junit_4]</name>
    </repository>
  </repositories>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
      <version>${project.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish.appclient</groupId>
      <artifactId>gf-client</artifactId>
      <version>3.1.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

توجه:

  • خطوط ۳۲–۵۱، وابستگی‌های پروژه،
  • خطوط ۱۳–۲۶: دو مخزن Maven تعریف شده‌اند، یکی برای EclipseLink (خطوط ۱۴–۱۹) و دیگری برای JUnit4 (خطوط ۲۰–۲۵).

کلاس تست به صورت زیر خواهد بود:


package rdvmedecins.tests.dao;

import java.util.Date;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import junit.framework.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import rdvmedecins.dao.IDaoRemote;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;

public class JUnitTestDao {

  // [dao] لایه آزمایش شد
  private static IDaoRemote dao;
  // تاریخ امروز
  Date jour = new Date();

  @BeforeClass
  public static void init() throws NamingException {
    // JNDI راه‌اندازی محیط
    InitialContext initialContext = new InitialContext();
    // مثال‌سازی لایه DAO
    dao = (IDaoRemote) initialContext.lookup("rdvmedecins.dao");
  }

  @Test
  public void test1() {
    // نمایش مشتریان
    List<Client> clients =dao.getAllClients();
    display("Liste des clients :", clients);
    // نمایش پزشکان
    List<Medecin> medecins =dao.getAllMedecins();
    display("Liste des médecins :", medecins);
    //نمایش شکاف‌های نوبت‌دهی پزشک
    Medecin medecin = medecins.get(0);
    List<Creneau> creneaux = dao.getAllCreneaux(medecin);
    display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
    // فهرست قرار ملاقات‌های یک پزشک در یک روز معین
    display(String.format("Liste des créneaux du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    // افزودن یک RV
    Rv rv = null;
    Creneau creneau = creneaux.get(2);
    Client client = clients.get(0);
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    rv = dao.ajouterRv(jour, creneau, client);
    System.out.println("Rv ajouté");
    display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    //افزودن یک RV به همان نوبت در همان روز
    //باید یک استثنا ایجاد کند
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    Boolean erreur = false;
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
    } catch (Exception ex) {
      Throwable th = ex;
      while (th != null) {
        System.out.println(ex.getMessage());
        th = th.getCause();
      }
      // خطا ثبت می‌شود
      erreur=true;
    }
    // ما بررسی می‌کنیم که خطایی رخ داده است
    Assert.assertTrue(erreur);
    //فهرست ورودی‌های RV
    display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    // حذف یک RV
    System.out.println("Suppression du Rv ajouté");
    dao.supprimerRv(rv);
    System.out.println("Rv supprimé");
    display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
  }

  // متد Utility – عناصر یک مجموعه را نمایش می‌دهد
  private static void display(String message, List elements) {
    System.out.println(message);
    for (Object element : elements) {
      System.out.println(element);
    }
  }
}
  • خطوط ۲۳–۲۹: متدی که با @BeforeClass علامت‌گذاری شده است، قبل از سایر متدها اجرا می‌شود. در اینجا، یک مرجع به رابط راه دور EJB [DaoJpa] ایجاد می‌شود. به یاد داشته باشید که ما آن را با نام JNDI "rdvmedecins.dao" نام‌گذاری کرده بودیم،
  • خطوط ۳۴–۳۵: نمایش لیست مشتریان،
  • خطوط ۳۷–۳۸: نمایش لیست پزشکان،
  • خطوط 40–42: نمایش بازه‌های زمانی برای اولین پزشک،
  • خط ۴۴: قرار ملاقات‌های دکتر اول را در روزی که در خط ۲۱ مشخص شده است، نمایش می‌دهد،
  • خطوط ۴۶–۵۱: اضافه کردن یک قرار ملاقات برای دکتر اول، برای نوبت شماره ۲ و روز مشخص‌شده در خط ۲۱،
  • خط ۵۲: برای تأیید، قرارهای ملاقات دکتر اول را در روزی که در خط ۲۱ مشخص شده است نمایش می‌دهد. باید حداقل یک مورد وجود داشته باشد – موردی که همین الان اضافه شده است،
  • خطوط ۵۵–۷۰: اضافه کردن همان قرار ملاقات. از آنجایی که جدول [RV] محدودیت یکتایی دارد، این افزودن باید یک خطا ایجاد کند. ما این مورد را در خط ۷۰ بررسی می‌کنیم،
  • خط ۷۲: برای اهداف تأیید، قرارهای ملاقات اولین دکتر برای روزی که در خط ۲۱ مشخص شده است را نمایش می‌دهد. قرار ملاقاتی که قصد افزودن آن را داریم نباید در آنجا وجود داشته باشد،
  • خطوط ۷۴–۷۶: ما همان قرار ملاقاتی را که اضافه شده بود، حذف می‌کنیم،
  • خط ۷۷: قرارهای ملاقات اولین دکتر را برای روزی که در خط ۲۱ مشخص شده است برای تأیید نمایش می‌دهد. قرار ملاقاتی که همین حالا حذف شده است نباید آنجا باشد.

این تست یک تست نمونه‌ای (dummy) است: JUnit. این تست تنها شامل یک گزاره (در خط ۷۰) است. این یک تست بصری با نواقص مربوطه است.

اگر همه چیز صحیح باشد، تست‌ها باید با موفقیت اجرا شوند:

  • در [1]، پروژه تست را می‌سازیم،
  • در [2]، تست را اجرا می‌کنیم،
  • در [3]، تست با موفقیت انجام شده است.

بیایید نگاهی دقیق‌تر به خروجی تست بیندازیم:

Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 15:34:15 CEST 2012]
Ajout d'un Rv le [Wed May 23 15:34:15 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 15:34:15 CEST 2012]
Rv[242, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Ajout d'un Rv le [Wed May 23 15:34:15 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 15:34:15 CEST 2012]
Rv[242, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 15:34:15 CEST 2012]

از خوانندگان دعوت می‌شود این لاگ‌ها را همراه با کدی که آن‌ها را تولید کرده است، مطالعه کنند. ما بر استثناء‌ای که هنگام افزودن یک قرار ملاقات موجود رخ داده است، در خطوط ۴۱–۴۹ تمرکز خواهیم کرد. ردپای استثناء در خطوط ۴۲–۴۸ نشان داده شده است. این غیرمنتظره است. بیایید به کد متدی که قرار ملاقات را اضافه می‌کند بازگردیم:


  // افزودن یک قرار ملاقات
  // تاریخ: تاریخ قرار ملاقات
  // slot: بازه زمانی قرار ملاقات
  // مشتری: مشتری‌ای که برای او قرار ملاقات تعیین شده است
  public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
    try {
      Rv rv = new Rv(null, jour);
      rv.setClient(client);
      rv.setCreneau(creneau);
      System.out.println(String.format("avant persist : %s",rv));
      em.persist(rv);
      System.out.println(String.format("après persist : %s",rv));
      return rv;
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 4);
    }
}

بیایید نگاهی به لاگ‌های GlassFish هنگام افزودن دو قرار ملاقات بیندازیم:

...
Infos: avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Infos: après persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Précis: INSERT INTO rv (JOUR, ID_CLIENT, ID_CRENEAU) VALUES (?, ?, ?)
    bind => [3 parameters bound]
Précis: SELECT LAST_INSERT_ID()
Précis: SELECT t1.ID, t1.JOUR, t1.ID_CLIENT, t1.ID_CRENEAU FROM creneaux t0, rv t1 WHERE (((t0.ID_MEDECIN = ?) AND (t1.JOUR = ?)) AND (t0.ID = t1.ID_CRENEAU))
    bind => [2 parameters bound]
Infos: avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Infos: après persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Précis: INSERT INTO rv (JOUR, ID_CLIENT, ID_CRENEAU) VALUES (?, ?, ?)
    bind => [3 parameters bound]
Précis: SELECT 1
Avertissement: Local Exception Stack: 
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2012-05-23-3' for key 'UNQ1_RV'
Error Code: 1062
...
  • خط ۲: قبل از اولین persist،
  • خط ۳: پس از اولین persist،
  • خط ۴: دستور INSERT که در شرف اجرا است. توجه داشته باشید که این همزمان با عملیات persist رخ نمی‌دهد. اگر این‌طور بود، این ورودی لاگ قبل از خط ۲ ظاهر می‌شد. بنابراین عملیات INSERT معمولاً در پایان تراکنشی که متد در آن اجرا می‌شود، انجام می‌گیرد،
  • خط ۶: EclipseLink از MySQL آخرین کلید اصلی استفاده شده را می‌پرسد. این مقدار، کلید اصلی قرار ملاقات اضافه شده را به دست می‌آورد. این مقدار، فیلد id موجودیت پایدار [Rv] را پر خواهد کرد،
  • خطوط ۷–۸: پرس‌وجوی SELECT، که قرارهای ملاقات پزشک را نمایش خواهد داد،
  • خطوط ۹–۱۰: صفحه نمایش برای persist دوم،
  • خطوط ۱۱–۱۲: دستور INSERT که باید اجرا شود. این دستور باید یک استثنا ایجاد کند. این مورد در خطوط ۱۵–۱۶ ظاهر شده و واضح است. این خطا در ابتدا توسط درایور JDBC از MySQL به‌دلیل نقض محدودیت یکتایی قرار ملاقات ایجاد می‌شود. بنابراین می‌توان نتیجه گرفت که باید این خطاها را در لاگ‌های تست JUnit مشاهده کنیم. با این حال، چنین نیست:
1
2
3
4
5
6
7
8
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe
java.rmi.MarshalException: CORBA MARSHAL 1330446347 Maybe; nested exception is: 
    org.omg.CORBA.MARSHAL: Avertissement: IOP00810011: Exception from readValue on ValueHandler in CDRInputStream  vmcid: OMG  minor code: 11 completed: Maybe

بیایید معماری کلاینت/سرور این تست را به یاد بیاوریم:

هنگامی که EJB یا [DAO] یک استثنا را پرتاب می‌کند، باید سریالیزه شود تا به کلاینت برسد. به احتمال زیاد این عملیات به دلیلی که من متوجه نشده‌ام، ناموفق بوده است. از آنجایی که کل اپلیکیشن ما در پیکربندی کلاینت/سرور کار نخواهد کرد، می‌توانیم این مشکل را نادیده بگیریم.

اکنون که EJB در لایه [DAO] عملیاتی است، می‌توانیم به EJB در لایه [métier] برویم.

3.5. لایه [métier]

بیایید به معماری برنامه‌ای که در حال حاضر در حال توسعه است بازگردیم:

ما قصد داریم یک پروژهٔ جدید Maven برای EJB و [métier] بسازیم. همان‌طور که در بالا نشان داده شده است، این پروژه به پروژه‌ی Maven که برای لایه‌های [DAO] و [JPA] ساخته شده بود، وابسته خواهد بود.

3.5.1. پروژه NetBeans

ما در حال ایجاد یک پروژهٔ جدید Maven از نوع EJB هستیم. برای این کار، کافی است مراحل قبلی را که در صفحهٔ 174 توضیح داده شده است، دنبال کنید.

  • در [1]، پروژه Maven برای لایه [métier
  • به [2] یک وابستگی اضافه کنید،
  • در [3]، پروژه Maven را برای لایه‌های [DAO] و [JPA] انتخاب کنید،
  • در [4]، ما دامنه [provided] را انتخاب می‌کنیم. توجه داشته باشید که این بدان معناست که برای کامپایل کردن پروژه ضروری است اما برای اجرای آن لازم نیست. در واقع،لایه EJB از لایه [métier] در کنار لایه EJB از لایه‌های [DAO] و [JPA] روی سرور GlassFish مستقر خواهد شد. بنابراین وقتی اجرا می‌شود، لایه EJB که شامل لایه‌های [DAO] و [JPA] است، از قبل موجود خواهد بود،
  • در [6]، پروژه جدید با وابستگی‌اش.

اکنون بیایید به کد منبع لایه [métier] نگاهی بیندازیم:

EJB [Metier] دارای رابط زیر خواهد بود: [IMetier]:


package rdvmedecins.metier.service;

import java.util.Date;
import java.util.List;

import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;

public interface IMetier {

    // لایه CAD
    // فهرست مشتریان
    public List<Client> getAllClients();

    // فهرست پزشکان
    public List<Medecin> getAllMedecins();

    // فهرست بازه‌های زمانی نوبت‌دهی پزشک
    public List<Creneau> getAllCreneaux(Medecin medecin);

    // فهرست قرارهای ملاقات پزشک در یک روز مشخص
    public List<Rv> getRvMedecinJour(Medecin medecin, Date jour);

    // پیدا کردن مشتری با شناسهٔ آن
    public Client getClientById(Long id);

    // پیدا کردن یک مشتری با شناسهٔ آن
    public Medecin getMedecinById(Long id);

    //یافتن یک قرار ملاقات با شناسهٔ آن
    public Rv getRvById(Long id);

    //یافتن یک بازه زمانی مشخص‌شده با شناسه آن
    public Creneau getCreneauById(Long id);

    // افزودن یک RV
    public Rv ajouterRv(Date jour, Creneau creneau, Client client);

    // حذف یک RV
    public void supprimerRv(Rv rv);
    
    // شغل
  public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour);

}

برای درک این رابط، مهم است که معماری پروژه را در نظر داشته باشید:

ما رابط لایه [DAO] (بخش 3.4.4) را تعریف کرده و اشاره نموده‌ایم که این لایه الزامات لایه [web]، یعنی الزامات کاربر، را برآورده می‌سازد. لایه [web] تنها از طریق لایه [métier] با لایه [DAO] ارتباط برقرار می‌کند. این موضوع توضیح می‌دهد که چرا لایه [métier] شامل تمام متدهای لایه [DAO] است. این متدها به سادگی درخواست را از لایه [web] به لایه [DAO] واگذار می‌کنند. هیچ چیز بیشتر.

در حین تحلیل برنامه، یک نیاز مطرح شد: قابلیت نمایش دفترچه یادداشت پزشک برای یک روز مشخص در یک صفحه وب، به منظور نشان دادن اینکه کدام نوبت‌ها رزرو شده‌اند و کدام‌ها در آن روز خالی هستند. این حالت معمولاً زمانی رخ می‌دهد که منشی درخواستی را از طریق تلفن ثبت می‌کند. تماس‌گیرنده درخواست وقت ملاقات در روز مشخصی با پزشک معینی را دارد. برای برآورده کردن این نیاز، لایه [métier] متد را در خط ۴۶ فراهم می‌کند.


    // شغل
  public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour);

ممکن است این سؤال پیش بیاید که این متد را کجا قرار دهیم:

  • می‌توان آن را در لایه [DAO] قرار داد. با این حال، این متد در واقع یک نیازمندی دسترسی به داده را برطرف نمی‌کند، بلکه یک نیازمندی کسب‌وکار را برآورده می‌سازد؛
  • می‌توان آن را در لایه [web] قرار داد. این یک ایده بد است. زیرا اگر لایه [web] به لایه [Swing] تغییر یابد، متد از بین خواهد رفت، حتی اگر نیاز همچنان وجود داشته باشد.

این متد پزشک و روزی را که برای آن می‌خواهیم تقویم رزرو را داشته باشیم، به‌عنوان پارامتر می‌پذیرد. این متد یک شیء [AgendaMedecinJour] را بازمی‌گرداند که نمایانگر تقویم پزشک برای آن روز است:


package rdvmedecins.metier.entites;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import rdvmedecins.jpa.Medecin;

public class AgendaMedecinJour implements Serializable {

    private static final long serialVersionUID = 1L;
    // میدان‌ها
    private Medecin medecin;
    private Date jour;
    private CreneauMedecinJour[] creneauxMedecinJour;

    // سازنده‌ها
    public AgendaMedecinJour() {

    }

    public AgendaMedecinJour(Medecin medecin, Date jour, CreneauMedecinJour[] creneauxMedecinJour) {
        this.medecin = medecin;
        this.jour = jour;
        this.creneauxMedecinJour = creneauxMedecinJour;
    }

    public String toString() {
        StringBuffer str = new StringBuffer("");
        for (CreneauMedecinJour cr : creneauxMedecinJour) {
            str.append(" ");
            str.append(cr.toString());
        }
        return String.format("Agenda[%s,%s,%s]", medecin, new SimpleDateFormat("dd/MM/yyyy").format(jour), str.toString());
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
...
  
}
  • خط ۱۲: پزشکی که این برنامه اوست،
  • خط ۱۳: روز برنامه،
  • خط 14: نوبت‌های دکتر برای آن روز.
  • این کلاس شامل سازندها (lines 17, 21) و یک متد سفارشی toString (line 27) است.

کلاس [CreneauMedecinJour] (خط 14) به شرح زیر است:


package rdvmedecins.metier.entites;

import java.io.Serializable;
import rdvmedecins.jpa.Creneau;

import rdvmedecins.jpa.Rv;

public class CreneauMedecinJour implements Serializable {

    private static final long serialVersionUID = 1L;
    // fields
    private Creneau creneau;
    private Rv rv;

    // سازنده‌ها
    public CreneauMedecinJour() {

    }

    public CreneauMedecinJour(Creneau creneau, Rv rv) {
        this.creneau=creneau;
    this.rv=rv;
    }

    //toString
    @Override
    public String toString() {
        return String.format("[%s %s]", creneau,rv);
    }

    // گیرنده و تنظیم‌کننده

  ...
}
  • خط ۱۲: یک نوبت ویزیت دکتر،
  • خط ۱۳: قرار ملاقات مرتبط، null در صورتی که اسلات موجود باشد.

بنابراین می‌توانیم ببینیم که فیلد creneauxMedecinJour در خط 14 از کلاس [AgendaMedecinJour] به ما امکان می‌دهد تا تمام بازه‌های زمانی دکتر را با وضعیت «رزرو شده» یا «در دسترس» برای هر کدام بازیابی کنیم. این هدف متد جدید [getAgendaMedecinJour] در داخل رابط [IMetier] بود.

EJB [Metier] ما دارای یک رابط محلی و یک رابط از راه دور خواهد بود که هر دو به سادگی رابط اصلی [IMetier] را گسترش می‌دهند:


package rdvmedecins.metier.service;
import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}

package rdvmedecins.metier.service;
import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

EJB و [Metier] این رابط‌ها را به شرح زیر پیاده‌سازی می‌کنند:


package rdvmedecins.metier.service;

import java.io.Serializable;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.ejb.EJB;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import rdvmedecins.dao.IDaoLocal;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;
import rdvmedecins.metier.entites.CreneauMedecinJour;

@Singleton
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote, Serializable {

  //لایه DAO
  @EJB
  private IDaoLocal dao;

  public Metier() {
  }

  @Override
  public List<Client> getAllClients() {
    return dao.getAllClients();
  }

  @Override
  public List<Medecin> getAllMedecins() {
    return dao.getAllMedecins();
  }

  @Override
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    return dao.getAllCreneaux(medecin);
  }

  @Override
  public List<Rv> getRvMedecinJour(Medecin medecin, Date jour) {
    return dao.getRvMedecinJour(medecin, jour);
  }

  @Override
  public Client getClientById(Long id) {
    return dao.getClientById(id);
  }

  @Override
  public Medecin getMedecinById(Long id) {
    return dao.getMedecinById(id);
  }

  @Override
  public Rv getRvById(Long id) {
    return dao.getRvById(id);
  }

  @Override
  public Creneau getCreneauById(Long id) {
    return dao.getCreneauById(id);
  }

  @Override
  public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

  @Override
  public void supprimerRv(Rv rv) {
    dao.supprimerRv(rv);
  }

  @Override
  public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour) {
    // فهرست اسلات‌های نوبت‌دهی دکتر
    List<Creneau> creneauxHoraires = dao.getAllCreneaux(medecin);
    // فهرست نوبت‌های رزرو شده برای همان پزشک در همان روز
    List<Rv> reservations = dao.getRvMedecinJour(medecin, jour);
    // یک فرهنگ‌لغت از قرارهای رزروشده ایجاد می‌شود
    Map<Long, Rv> hReservations = new Hashtable<Long, Rv>();
    for (Rv resa : reservations) {
      hReservations.put(resa.getCreneau().getId(), resa);
    }
    //دفترچه یادداشت برای روز مورد نظر ایجاد می‌شود
    AgendaMedecinJour agenda = new AgendaMedecinJour();
    // پزشک
    agenda.setMedecin(medecin);
    // روز
    agenda.setJour(jour);
    // اسلات‌های رزرو
    CreneauMedecinJour[] creneauxMedecinJour = new CreneauMedecinJour[creneauxHoraires.size()];
    agenda.setCreneauxMedecinJour(creneauxMedecinJour);
    //پر کردن شکاف‌های رزرو
    for (int i = 0; i < creneauxHoraires.size(); i++) {
      // ردیف تقویم i
      creneauxMedecinJour[i] = new CreneauMedecinJour();
      //شناسه نوبت
      creneauxMedecinJour[i].setCreneau(creneauxHoraires.get(i));
      //آیا اسلات آزاد است یا رزرو شده؟
      if (hReservations.containsKey(creneauxHoraires.get(i).getId())) {
        // اسلات اشغال شده است – توجه به رزرو
        Rv resa = hReservations.get(creneauxHoraires.get(i).getId());
        creneauxMedecinJour[i].setRv(resa);
      }
    }
    // نتیجه بازگردانده شد
    return agenda;
  }
}
  • در خط ۲۲، کلاس [Metier] یک نمونهٔ تک‌نسخه (singleton) از EJB است؛
  • خط ۲۳، هر متد EJB در داخل یک تراکنش اجرا می‌شود. این بدان معناست که تراکنش در ابتدای متد، در لایه [métier] آغاز می‌شود. این لایه متدهایی را از لایه [DAO] فراخوانی می‌کند. این متدها در همان تراکنش اجرا خواهند شد،
  • خط ۲۴: EJB رابط‌های محلی و راه دوری خود را پیاده‌سازی می‌کند و همچنین قابل سریالی‌سازی است،
  • خط ۲۷: مرجعی به EJB از لایه [DAO
  • خط ۲۹: این توسط کانtejner EJB در سرور GlassFish با استفاده از anotation @EJB تزریق خواهد شد. بنابراین، هنگامی که متدهای کلاس [Metier] اجرا می‌شوند، ارجاع به EJB در لایه [DAO] مقداردهی اولیه شده است،
  • خطوط ۳۳–۸۱: این مرجع برای واگذاری فراخوانی انجام‌شده به لایه [métier] به لایه [DAO] استفاده می‌شود،
  • خط ۸۴: متد getAgendaMedecinJour که برنامهٔ یک پزشک را برای یک روز مشخص بازیابی می‌کند. به خواننده واگذار می‌کنیم که توضیحات را دنبال کند.

3.5.2. استقرار لایه [métier]

لایه [métier] به لایه [DAO] وابسته است. هر لایه با استفاده از EJB پیاده‌سازی شده است. برای آزمایش EJB و [métier]، باید هر دو لایه EJB را مستقر کنیم. برای این کار، به یک پروژه سازمانی نیاز داریم.

  • [1]، ما یک پروژه جدید ایجاد می‌کنیم،
  • از نوع Maven [2] و Enterprise Application [3]،
  • و نام آن را [4] می‌دهیم. پسوند ear به‌طور خودکار اضافه خواهد شد،
  • به [5]؛ ما سرور GlassFish و نسخه ۶ جاوا را برای EE انتخاب می‌کنیم،
  • به [6]؛ یک برنامهٔ سازمانی شامل ماژول‌ها است، معمولاً ماژول‌های EJB و ماژول‌های وب. در اینجا، برنامه کاربردی سازمانی شامل ماژول‌های دو پروژه EJB خواهد بود که ما ساخته‌ایم. از آنجایی که این ماژول‌ها از قبل وجود دارند، ما تیک کادرها را نمی‌زنیم،
  • در [7,8]، دو پروژه ایجاد شده است. [8] پروژه‌ی سازمانی است که قرار است از آن استفاده کنیم. [7] پروژه‌ای است که از هدف آن مطمئن نیستم. من نیازی به استفاده از آن نداشته‌ام و از آنجا که به طور عمیق مِیوِن را بررسی نکرده‌ام، نمی‌دانم برای چه کاری ممکن است استفاده شود. بنابراین آن را نادیده می‌گیریم.

اکنون که پروژه اصلی ایجاد شده است، می‌توانیم ماژول‌های آن را تعریف کنیم.

  • در [1]، یک وابستگی جدید ایجاد می‌کنیم،
  • در [2]، ما پروژه را از EJB [DAO] انتخاب می‌کنیم،
  • در [3]، مشخص می‌کنیم که این یک EJB است. نوع را خالی نگذارید، زیرا در این صورت نوع jar استفاده خواهد شد و این نوع برای اینجا مناسب نیست،
  • در [4]، دامنه [compile] استفاده می‌شود،
  • در [5]، پروژه با وابستگی جدید خود،
  • در [6, 7, 8]، ما دوباره شروع به افزودن EJB از لایه [métier] می‌کنیم،
  • به [9]، دو وابستگی،
  • در [10]، پروژه را می‌سازیم،
  • در [11]، آن را اجرا می‌کنیم،
  • در [12]، در زبانه [Services]، می‌توانیم ببینیم که پروژه روی سرور GlassFish مستقر شده است. این بدان معناست که هر دو EJB اکنون روی سرور موجود هستند.

در لاگ‌های سرور GlassFish، اطلاعاتی در مورد استقرار دو فایل EJB پیدا خواهید کرد:

  • در برگهٔ لاگ‌های GlassFish برای [1].

لاگ‌های زیر را می‌توان در آنجا یافت:

Infos: rdvmedecins.jpa.Creneau actually got transformed
Infos: rdvmedecins.jpa.Medecin actually got transformed
Infos: rdvmedecins.jpa.Personne actually got transformed
Infos: rdvmedecins.jpa.Client actually got transformed
Infos: rdvmedecins.jpa.Rv actually got transformed
Infos: EclipseLink, version: Eclipse Persistence Services - 2.3.2.v20111125-r10461
Infos: file:/D:/data/istia-1112/netbeans/dvp/jsf2-pf-pfm/maven/netbeans/rdvmedecins-jsf2-ejb/mv-rdvmedecins-metier-dao/mv-rdvmedecins-metier-dao-ear/target/gfdeploy/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-dao-jpa-1.0-SNAPSHOT_jar/_dbrdvmedecins2-PU login successful
Infos: EJB5181:Portable JNDI names for EJB DaoJpa: [java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-dao-jpa-1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoRemote, java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-dao-jpa-1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoLocal]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB DaoJpa: [rdvmedecins.dao#rdvmedecins.dao.IDaoRemote, rdvmedecins.dao]
Infos: EJB5181:Portable JNDI names for EJB Metier: [java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote, java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierLocal]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB Metier: [rdvmedecins.metier.service.IMetierRemote#rdvmedecins.metier.service.IMetierRemote, rdvmedecins.metier.service.IMetierRemote]
  • خطوط ۱–۵: اشیاء JPA شناسایی شده‌اند،
  • خط ۷: نشان می‌دهد که واحد پایداری [dbrdvmedecins2-PU] با موفقیت ساخته شده و یک اتصال به پایگاه داده مربوطه برقرار شده است،
  • خط ۸: نام‌های قابل حمل رابط‌های راه دور و محلی برای EJB و [DaoJpa]. portable به معنای شناخته‌شده توسط همه سرورهاى برنامه‌نویسى است،
  • خط ۹: همین، اما با استفاده از نام‌های مخصوص GlassFish،
  • خطوط ۱۰–۱۱: همین امر در مورد EJB و [Metier] نیز صدق می‌کند.

ما نام قابل حمل رابط راه دور را برای EJB و [Metier] حفظ خواهیم کرد:

java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote

این مورد هنگام آزمایش لایه [métier] به کار خواهد آمد.

3.5.3. آزمایش لایه [métier]

همان‌طور که برای لایه [DAO] انجام دادیم، لایه [métier] را نیز به‌عنوان بخشی از یک برنامه کلاینت/سرور آزمایش خواهیم کرد:

کلاینت رابط دور EJB [Metier] را که روی سرور GlassFish مستقر شده است، آزمایش خواهد کرد.

ما کار را با ایجاد یک پروژه جدید Maven آغاز می‌کنیم. برای این کار، رویه‌ای را که برای ایجاد پروژه تست لایه [dao] استفاده می‌شود دنبال می‌کنیم (به بخش 3.4.7 مراجعه کنید)، با این تفاوت که ایجاد تست JUnit را حذف می‌کنیم. پروژه‌ای که به این روش ایجاد می‌شود به شرح زیر است

  • در [1]، پروژه ایجاد شده با وابستگی‌های آن: روی EJB از لایه [dao]، روی EJB از لایه [métier]، و کتابخانه [gf-client].

در این مرحله، فایل [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</groupId>
  <artifactId>mv-client-rdvmedecins-ejb-metier</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-client-rdvmedecins-ejb-metier</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.glassfish.appclient</groupId>
      <artifactId>gf-client</artifactId>
      <version>3.1.1</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-ejb-metier</artifactId>
      <version>${project.version}</version>
    </dependency>
  </dependencies>
</project>

اطمینان حاصل کنید که وابستگی‌های توصیف‌شده در خطوط 17–33 در محل خود قرار دارند. تست یک کلاس کنسول ساده خواهد بود:

کد کلاس [ClientRdvMedecinsMetier] به شرح زیر است:


package istia.st.client;

import java.util.Date;
import java.util.List;

import javax.naming.InitialContext;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Creneau;

import rdvmedecins.jpa.Medecin;
import rdvmedecins.jpa.Rv;
import rdvmedecins.metier.entites.AgendaMedecinJour;
import rdvmedecins.metier.service.IMetierRemote;

public class ClientRdvMedecinsMetier {

  // نام رابط راه دور EJB [Metier]
  private static String IDaoRemoteName = "java:global/istia.st_mv-rdvmedecins-metier-dao-ear_ear_1.0-SNAPSHOT/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote";
  // تاریخ امروز
  private static Date jour = new Date();

  public static void main(String[] args) {
    try {
      // زمینه JNDI  سرور GlassFish
      InitialContext initialContext = new InitialContext();
      // مرجع بر روی لایهٔ [metier] دور
      IMetierRemote metier = (IMetierRemote) initialContext.lookup(IDaoRemoteName);
      // دید مشتری
      List<Client> clients = metier.getAllClients();
      display("Liste des clients :", clients);
      // نمای پزشکان
      List<Medecin> medecins = metier.getAllMedecins();
      display("Liste des médecins :", medecins);
      // نمایش شکاف‌های نوبت‌دهی پزشک
      Medecin medecin = medecins.get(0);
      List<Creneau> creneaux = metier.getAllCreneaux(medecin);
      display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
      // فهرست قرار ملاقات‌های یک پزشک در یک روز معین
      display(String.format("Liste des rendez-vous du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
      //نمای تقویم
      AgendaMedecinJour agenda = metier.getAgendaMedecinJour(medecin, jour);
      System.out.println(agenda);
      // افزودن یک RV
      Rv rv = null;
      Creneau creneau = creneaux.get(2);
      Client client = clients.get(0);
      System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
      rv = metier.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
      //نمای تقویم
      agenda = metier.getAgendaMedecinJour(medecin, jour);
      System.out.println(agenda);
      // حذف یک RV
      System.out.println("Suppression du Rv ajouté");
      metier.supprimerRv(rv);
      System.out.println("Rv supprimé");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), metier.getRvMedecinJour(medecin, jour));
      // نمایندگی تقویم
      agenda = metier.getAgendaMedecinJour(medecin, jour);
      System.out.println(agenda);
    } catch (Throwable ex) {
      System.out.println("Erreur...");
      while (ex != null) {
        System.out.println(String.format("%s : %s", ex.getClass().getName(), ex.getMessage()));
        ex = ex.getCause();
      }
    }
  }

  // متد ابزار – موارد را در یک مجموعه نمایش می‌دهد
  private static void display(String message, List elements) {
    System.out.println(message);
    for (Object element : elements) {
      System.out.println(element);
    }
  }
}
  • خط ۱۸: نام قابل حمل رابط راه دور برای EJB [Metier] از لاگ‌های GlassFish گرفته شده است،
  • خطوط ۲۴–۲۷: مرجعی به رابط راه دور EJB [Metier] گرفته می‌شود،
  • خطوط ۲۹–۳۰: نمایش مشتریان،
  • خطوط ۳۲–۳۳: نمایش پزشکان،
  • خطوط ۳۵–۳۷: نمایش نوبت‌های خالی یک پزشک،
  • خط ۳۹: نمایش قرارهای ملاقات یک پزشک در یک روز مشخص،
  • خطوط ۴۱–۴۲: دفترچه یادداشت همان پزشک برای همان روز،
  • خطوط ۴۴–۴۹: افزودن یک قرار ملاقات،
  • خط ۵۰: قرارهای دکتر را نمایش می‌دهد. باید یکی دیگر هم باشد،
  • خطوط ۵۲–۵۳: دفترچه یادداشت پزشک را نمایش می‌دهد. قرار ملاقاتی که همین الان اضافه شده باید قابل مشاهده باشد،
  • خطوط ۵۵–۵۷: قرار ملاقاتی را که همین حالا اضافه کرده‌اید، حذف کنید،
  • خط ۵۸: این باید در فهرست قرار ملاقات‌های دکتر منعکس شود،
  • خطوط ۶۰–۶۱: و در دفترچه یادداشت او.

ما تست را اجرا می‌کنیم:

 

نمایش‌های حاصل از اجرا ( ) به شرح زیر است:


Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Ajout d'un Rv le [Wed May 23 16:25:26 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Rv[252, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] Rv[252, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Wed May 23 16:25:26 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],23/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
  • خط ۳۷: دفترچه یادداشت خانم PELISSIER، ۲۳ مه ۲۰۱۲. هیچ ساعتی رزرو نشده است،
  • خط ۳۹: افزودن یک قرار ملاقات،
  • خط ۴۲: دفترچه یادداشت جدید خانم PELISSIER. یک نوبت اکنون برای آقای MARTIN رزرو شده است،
  • خط ۴۴: قرار ملاقات حذف شده است،
  • خط ۴۶: تقویم خانم PELISSIER نشان می‌دهد که هیچ بازه زمانی رزرو نشده است.

اکنون لایه‌های [DAO] و [métier] را عملیاتی در نظر می‌گیریم. ما هنوز باید لایه [web] را با استفاده از چارچوب JSF بنویسیم. برای انجام این کار، از دانش کسب‌شده در ابتدای این سند استفاده خواهیم کرد.

3.6. لایه [web]

بیایید به معماری که در حال حاضر در دست ساخت است بازگردیم:

ما قصد داریم لایه نهایی، لایه [web] را بسازیم.

3.6.1. پروژه NetBeans

ما در حال ساخت یک پروژه Maven هستیم:

  • در [1]، ما یک پروژه جدید ایجاد می‌کنیم،
  • در [2, 3]، یک پروژه Maven از نوع [Web Application
  • در [4]، برای آن نامی انتخاب می‌کنیم،
  • در [5]، سرور GlassFish و Java EE 6 Web را انتخاب کنید،
  • در [6]، پروژه‌ای که به این ترتیب ایجاد شده است،
  • در [7]، پس از حذف پروژه و صفحه [index.jsp] و بسته در [Source Packages
  • به [8, 9]؛ در ویژگی‌های پروژه، یک فریم‌ورک اضافه کنید،
  • در [10]، Java Server Faces را انتخاب کنید،
  • در [11]، پیکربندی Java Server Faces. مقادیر پیش‌فرض را حفظ کنید. توجه داشته باشید که از JSF 2 استفاده می‌شود،
  • در [12]، پروژه سپس به دو روش تغییر داده می‌شود: یک فایل با نام [web.xml] و همچنین یک صفحه با نام [index.html] ایجاد می‌شود.

فایل [web.xml] به شرح زیر است:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

ما قبلاً با این فایل مواجه شده‌ایم.

  • خطوط ۷–۱۱: سرولتی را تعریف می‌کنند که تمام درخواست‌های ارسال‌شده به برنامه را مدیریت خواهد کرد. این سرولت JSF است،
  • خطوط ۱۲–۱۵: درخواست‌های URL را که توسط این سرولت پردازش می‌شوند، تعریف می‌کنند. این‌ها درخواست‌های URL با فرمت /faces/* هستند،
  • خطوط ۲۱–۲۳: صفحه [index.xhtml] را به‌عنوان صفحه اصلی تعریف کنید.

این صفحه به شرح زیر است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

ما قبلاً با آن مواجه شده‌ایم. می‌توانیم این پروژه را اجرا کنیم:

  • در [1]؛ پروژه را اجرا می‌کنیم و نتیجه [2] را در مرورگر دریافت می‌کنیم.

اکنون پروژه کامل را ارائه می‌دهیم و سپس به توضیح جزئیات اجزای مختلف آن می‌پردازیم.

  • به [1]، صفحات XHTML پروژه،
  • در [2]، کد جاوا،
  • در [3]، فایل‌های پیام، از آنجا که برنامه بین‌المللی‌سازی شده است،
  • به [4]، وابستگی‌های پروژه.

3.6.2. وابستگی‌های پروژه

بیایید به معماری پروژه بازگردیم:

لایه JSF به لایه‌های [métier]، [DAO] و [JPA] وابسته است. این سه لایه در دو پروژه Maven که ساخته‌ایم، بسته‌بندی شده‌اند که وابستگی‌های پروژه [4] را توضیح می‌دهد. بیایید به سادگی نشان دهیم که این وابستگی‌ها چگونه اضافه شده‌اند:

  • در [1]، ما 'ejb' را مشخص می‌کنیم تا نشان دهیم که وابستگی به پروژه EJB است،
  • در [2]، ما [provided] را مشخص خواهیم کرد. این به این دلیل است که پروژه وب در کنار دو پروژه EJB مستقر خواهد شد. بنابراین، نیازی به شامل کردن JARهای EJB نیست.

3.6.3. پیکربندی پروژه

پیکربندی پروژه همانند پروژه‌های JSF است که در ابتدای این سند بررسی کردیم. فایل‌های پیکربندی را در اینجا بدون توضیح مجدد فهرست می‌کنیم.

 

[web.xml]: وب‌اپلیکیشن را پیکربندی می‌کند.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Production</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>

</web-app>

توجه کنید که در خط ۲۶، صفحه [index.xhtml] صفحه اصلی برنامه است.

[faces-config.xml]: برنامه JSF را پیکربندی می‌کند


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

[beans.xml]: خالی اما برای حاشیه‌نویسی @Named الزامی است


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

[styles.css]: برگهٔ سبک برنامه


.reservationsHeaders {
   text-align: center;
   font-style: italic;
   color: Snow;
   background: Teal;
}

.creneau {
   height: 25px;
   text-align: center;
   background: MediumTurquoise;
}
.client {
   text-align: left;
   background: PowderBlue;
}

.action {
   width: 6em;
   text-align: left;
   color: Black;
   background: MediumTurquoise;
}
.erreursHeaders {
   background: Teal;
   background-color: #ff6633;
   color: Snow;
   font-style: italic;
   text-align: center

}

.erreurClasse {
   background: MediumTurquoise;
   background-color: #ffcc66;
   height: 25px;
   text-align: center
}

.erreurMessage {
   background: PowderBlue;
   background-color: #ffcc99;
   text-align: left
}

[messages_fr.properties]: فایل پیام فرانسوی


# قالب‌بندی
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers
layout.entete.langue1=Fran\u00e7ais
layout.entete.langue2=Anglais
# استثناء
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
# فرم ۱
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.button.agenda=Agenda
form1.jour.required=date requise
form1.jour.erreur=date erron\u00e9e
# فرم ۲
form2.titre=Agenda de {0} {1} {2} le {3}
form2.titre_detail=Agenda de {0} {1} {2} le {3}
form2.creneauHoraire=Cr\u00e9neau horaire
form2.client=Client
form2.accueil=Accueil
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
# فرم ۳
form3.titre=Prise de rendez-vous de {0} {1} {2}, le {3} dans le cr\u00e9neau {4,number,#00}:{5,number,#00} - {6,number,#00}:{7,number,#00}
form3.titre_detail=Prise de rendez-vous de {0} {1} {2}, le {3} dans le cr\u00e9neau {4,number,#00}:{5,number,#00} - {6,number,#00}:{7,number,#00}
form3.client=Client
form3.valider=Valider
form3.annuler=Annuler
# خطا
erreur.titre=Une erreur s'est produite.
erreur.message=Message d'erreur
erreur.accueil=Page d'accueil
erreur.classe=Cause

[messages_en.properties]: فایل پیام انگلیسی


# قالب‌بندی
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university
layout.entete.langue1=French
layout.entete.langue2=English
# استثناء
exception.header=The following exceptions occurred
exception.httpCode=Error HTTP code
exception.message=Exception message
exception.requestUri=Url targeted when error occurred
exception.servletName=Servlet targeted's name when error occurred
# فرم ۱
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=Date (dd/mm/yyyy)
form1.button.agenda=Diary
form1.jour.required=The date is required
form1.jour.erreur=The date is invalid
# فرم ۲
form2.titre={0} {1} {2}'' diary on {3}
form2.titre_detail={0} {1} {2}'' diary on {3}
form2.creneauHoraire=Time Period
form2.client=Client
form2.accueil=Welcome Page
form2.supprimer=Delete
form2.reserver=Reserve
# فرم ۳
form3.titre=Reservation for {0} {1} {2}, on {3} in the time period {4,number,#00}:{5,number,#00} - {6,number,#00}:{7,number,#00}
form3.titre_detail=Reservation for {0} {1} {2}, on {3} in the time period {4,number,#00}:{5,number,#00} - {6,number,#00}:{7,number,#00}
form3.client=Client
form3.valider=Submit
form3.annuler=Cancel
#خطا
erreur.titre=An error occurred
erreur.message=Error message
erreur.accueil=Welcome Page
erreur.classe=Cause

3.6.4. نمای پروژه‌ها

بیایید مرور کنیم که برنامه چگونه کار می‌کند. صفحهٔ اصلی به شرح زیر است:

 

از این صفحهٔ اولیه، کاربر (دبیر، دکتر) تعدادی اقدام را انجام خواهد داد. این موارد در زیر نشان داده شده‌اند. نمای سمت چپ صفحه‌ای را نشان می‌دهد که کاربر از آن درخواست می‌کند؛ نمای سمت راست پاسخ ارسال‌شده توسط سرور را نشان می‌دهد.

در نهایت، ممکن است یک صفحه خطا نیز نمایش داده شود:

این نماهای مختلف توسط صفحات زیرِ پروژه وب تولید می‌شوند:

  • در [1]، صفحات [basdepage, entete, layout] قالب‌بندی تمام نماها را مدیریت می‌کنند،
  • در [2]، نمایی که توسط [layout.xhtml] تولید شده است.

از فناوری Facelets در اینجا استفاده شده است. این موضوع در بخش 2.11 توضیح داده شده است. ما صرفاً کد صفحات XHTML را که برای طرح‌بندی استفاده می‌شوند، ارائه می‌دهیم:

[entete.xhtml]


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <body>
    <h2><h:outputText value="#{msg['layout.entete']}"/></h2>
    <div align="left">
      <h:commandLink value="#{msg['layout.entete.langue1']}" actionListener="#{changeLocale.setFrenchLocale}"/>
      <h:outputText value=" "/>
      <h:commandLink value="#{msg['layout.entete.langue2']}" actionListener="#{changeLocale.setEnglishLocale}"/>
  </div>
  </body>
</html>

به خطوط ۱۰–۱۲ توجه کنید که حاوی دو پیوند برای تغییر زبان برنامه هستند.

[basdepage.xhtml]


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h:outputText value="#{msg['layout.basdepage']}"/>
  </body>
</html>

[layout.xhtml]


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>RdvMedecins</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <table style="width: 1200px">
          <tr>
            <td colspan="2" bgcolor="#ccccff">
              <ui:include src="entete.xhtml"/>
            </td>
          </tr>
          <tr>
            <td style="width: 100px; height: 200px" bgcolor="#ffcccc">
            </td>
            <td>
              <ui:insert name="contenu" >
                <h2>Contenu</h2>
              </ui:insert>
            </td>
          </tr>
          <tr bgcolor="#ffcc66">
            <td colspan="2">
              <ui:include src="basdepage.xhtml"/>
            </td>
          </tr>         
        </table>
      </h:form>
    </h:body>
  </f:view>
</html>

این صفحه قالب صفحه [index.xhtml] است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h:panelGroup rendered="#{form.form1Rendered}">
        <ui:include src="form1.xhtml"/>
      </h:panelGroup>
      <h:panelGroup rendered="#{form.form2Rendered}">
        <ui:include src="form2.xhtml"/>
      </h:panelGroup>
      <h:panelGroup rendered="#{form.form3Rendered}">
        <ui:include src="form3.xhtml"/>
      </h:panelGroup>
      <h:panelGroup rendered="#{form.erreurRendered}">
        <ui:include src="erreur.xhtml"/>
      </h:panelGroup>
    </ui:define>
  </ui:composition>
</html>

خطوط ۸–۲۱ ناحیه‌ای به نام «محتوا» (خط ۸) را در [layout.xhtml] (خط ۷) تعریف می‌کنند. این ناحیهٔ مرکزی نماها است:

 

صفحه [index.xhtml] تنها صفحه در برنامه است. بنابراین هیچ ناوبری بین صفحات وجود نخواهد داشت. این نمایش یکی از چهار صفحه [form1.xhtml, form2.xhtml, form3.xhtml, erreur.xhtml] را نشان می‌دهد. این نمایش توسط چهار متغیر بولی [form1Rendered, form2Rendered, form3Rendered, erreurRendered] در بیون فرم کنترل می‌شود که به‌زودی توضیح خواهیم داد.

3.6.5. بیان‌های پروژه

کلاس‌های موجود در بسته [utils] قبلاً معرفی شده‌اند:

  • کلاس [ChangeLocale] کلاسی است که مسئول تغییر زبان است. این کلاس پیش از این مورد بحث قرار گرفته است (بخش 2.4.4).
  • کلاس [Messages] بین‌المللی‌سازی پیام‌های یک برنامه را تسهیل می‌کند. این کلاس در بخش 2.8.5.7 مورد بحث قرار گرفته است.

3.6.5.1. بیان Application

بیان [Application] به شرح زیر است:


package beans;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.jpa.Client;
import rdvmedecins.jpa.Medecin;
import rdvmedecins.metier.service.IMetierLocal;

@Named(value = "application")
@ApplicationScoped
public class Application implements Serializable{

  //لایه کسب‌وکار
  @EJB
  private IMetierLocal metier;
  // کش
  private List<Medecin> medecins;
  private List<Client> clients;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  private Map<Long, Client> hClients = new HashMap<Long, Client>();
  // خطاها
  private List<Erreur> erreurs = new ArrayList<Erreur>();
  private Boolean erreur = false;

  public Application() {
  }

  @PostConstruct
  public void init() {
    // پزشکان و مشتریان در حافظه پنهان ذخیره شده‌اند
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      //خطا ثبت می‌شود
      erreur = true;
      erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
      while (th.getCause() != null) {
        th = th.getCause();
        erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
      }
      return;
    }
    //بررسی فهرست‌ها
    if (medecins.size() == 0) {
      //خطا ثبت شد
      erreur = true;
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      //خطا ثبت شد
      erreur = true;
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // خطا؟
    if (erreur) {
      return;
    }

    // فرهنگ‌ها
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
  }

  // گیرنده و تنظیم‌کننده
  ...
}
  • خطوط ۱۵–۱۶: کلاس [Application] یک بین (bean) در مقیاس برنامه (Application-scoped) است. این کلاس یک‌بار در ابتدای چرخه عمر برنامه JSF ایجاد می‌شود و برای تمام درخواست‌های همه کاربران در دسترس است. این کلاس معمولاً برای ذخیره داده‌های فقط-خواندنی (read-only) استفاده می‌شود. در اینجا، ما لیست پزشکان و لیست مشتریان را ذخیره خواهیم کرد. بنابراین فرض می‌کنیم که این موارد به ندرت تغییر می‌کنند. صفحات XHTML از طریق نام برنامه به آن دسترسی پیدا می‌کنند،
  • سطور ۲۰–۲۱: یک مرجع به رابط محلی EJB [Metier] توسط کانتینر GlassFish EJB تزریق خواهد شد. بیایید معماری برنامه را به یاد بیاوریم:

برنامه JSF و بین‌های EJB و [Metier] در همان JVM (ماشین مجازی جاو) اجرا خواهند شد. بنابراین، لایه [JSF] از رابط محلی EJB استفاده خواهد کرد. در اینجا، بین (bean) برنامه از EJB و [Metier] استفاده می‌کند. حتی اگر این‌طور نبود، یافتن مرجعی به لایه [métier] در اینجا طبیعی است. این در واقع اطلاعاتی است که می‌تواند توسط تمام درخواست‌ها از همه کاربران به اشتراک گذاشته شود و بنابراین داده‌ای با دامنه Application است.

  • خطوط ۳۴–۳۵: متد init بلافاصله پس از نمونه‌سازی کلاس [Application] اجرا می‌شود (همانطور که توسط تگ @PostConstruct نشان داده شده است)،
  • در خطوط ۳۶–۷۳، روش موارد زیر را ایجاد می‌کند: فهرست پزشکان در خط ۲۳، فهرست مشتریان در خط ۲۴، یک دیکشنری از پزشکان که با شناسهٔ آن‌ها در خط ۲۵ فهرست‌بندی شده است، و همین‌طور برای مشتریان در خط ۲۶. ممکن است خطاهایی رخ دهد. این خطاها در فهرست خط ۲۸ ثبت می‌شوند.

کلاس [Erreur] به شرح زیر است:


package beans;

public class Erreur {
  
  public Erreur() {
  }
  
  // میدان
  private String classe;
  private String message;

  // سازنده
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // گیرنده و تنظیم‌کننده
...  
}
  • خط ۹: نام یک کلاس استثنا در صورتی که یک استثنا پرتاب شده باشد،
  • خط ۱۰: یک پیام خطا.

3.6.5.2. بیان [Form]

کد آن به شرح زیر است:


package beans;

...

@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }

  // بیان برنامه
  @Inject
  private Application application;

  // مدل
  private Long idMedecin;
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean form3Rendered = false;
  private Boolean erreurRendered = false;
  private String form2Titre;
  private String form3Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneau;
  private Medecin medecin;
  private Client client;
  private Long idClient;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;

  @PostConstruct
  private void init() {
    // آیا инициалиزاسيون با موفقیت انجام شد؟
    if (application.getErreur()) {
      //بازیابی فهرست خطاها
      erreurs = application.getErreurs();
      // نماش صفحهٔ خطا نمایش داده می‌شود
      setForms(false, false, false, true);
    }
  }

  // نمایش ویو
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }
.................................................
}
  • خطوط ۵–۷: کلاس [Form] یک بین به نام «form» با دامنه جلسه است. توجه داشته باشید که بنابراین این کلاس باید قابل سریالی‌سازی باشد.
  • خطوط ۱۳–۱۴: بین «form» دارای مرجعی به بین «application» است. این مورد توسط کانتینر سرولت که برنامه در آن در حال اجراست تزریق می‌شود (همان‌طور که با anotation @Inject نشان داده شده است).
  • خطوط 17–31: قالب صفحات [form1.xhtml, form2.xhtml, form3.xhtml, erreur.xhtml]. نمایش این صفحات توسط متغیرهای بولی در خطوط 19–22 کنترل می‌شود. توجه داشته باشید که، به طور پیش‌فرض، صفحه [form1.xhtml] رندر می‌شود،
  • خطوط ۳۳–۳۴: متد `init` بلافاصله پس از instantiation کلاس اجرا می‌شود (که با annotation `@PostConstruct` نشان داده شده است)،
  • خطوط ۳۵–۴۱: متد `init` برای تعیین اینکه کدام صفحه باید ابتدا نمایش داده شود، استفاده می‌شود: معمولاً صفحه `[form1.xhtml]` (خط ۱۹)، مگر اینکه inicialization برنامه ناموفق بوده باشد (خط ۳۶)، که در این صورت صفحه `[erreur.xhtml]` نمایش داده خواهد شد (خط ۴۰).

صفحه [erreur.xhtml] به شرح زیر است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <body>
    <h2><h:outputText value="#{msg['erreur.titre']}"/></h2>
    <p>
      <h:commandButton value="#{msg['erreur.accueil']}" actionListener="#{form.accueil()}"/>
    </p>
    <hr/>
    <h:dataTable value="#{form.erreurs}" var="erreur" headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
      <h:column>
        <f:facet name="header">
          <h:outputText value="#{msg['erreur.classe']}"/>
        </f:facet>
        <h:outputText value="#{erreur.classe}"/>
      </h:column>
      <h:column>
        <f:facet name="header">
          <h:outputText value="#{msg['erreur.message']}"/>
        </f:facet>
        <h:outputText value="#{erreur.message}"/>
      </h:column>
    </h:dataTable>
  </body>
</html>

این صفحه از تگ <h:dataTable> (خطوط 14–27) برای نمایش لیست خطاها استفاده می‌کند. این کار صفحه‌ای مشابه شکل زیر تولید می‌کند:

Image

اکنون مراحل مختلف چرخهٔ عمر برنامه را تعریف خواهیم کرد.

3.6.6. تعاملات بین صفحات و مدل

3.6.6.1. نمایش صفحهٔ اصلی

اگر همه چیز به‌خوبی پیش برود، اولین صفحه‌ای که نمایش داده می‌شود [form1.xhtml] است. این صفحه نمای زیر را ایجاد می‌کند:

 

صفحه [form1.xhtml] به شرح زیر است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <body>
    <h2><h:outputText value="#{msg['form1.titre']}"/></h2>
    <h:panelGrid columns="3">
      <h:panelGroup>
      <div align="center"><h3><h:outputText value="#{msg['form1.medecin']}"/></h3></div>
      </h:panelGroup>
      <h:panelGroup>
      <div align="center"><h3><h:outputText value="#{msg['form1.jour']}"/></h3></div>
      </h:panelGroup>
      <h:panelGroup/>
      <h:selectOneMenu value="#{form.idMedecin}">  
        <f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>  
      </h:selectOneMenu>              
      <h:inputText id="jour" value="#{form.jour}"  required="true" requiredMessage="#{msg['form1.jour.required']}" converterMessage="#{msg['form1.jour.erreur']}">
        <f:convertDateTime pattern="dd/MM/yyyy"/>
      </h:inputText>
      <h:message for="jour" styleClass="error"/>
    </h:panelGrid>
    <h:commandButton value="#{msg['form1.button.agenda']}" actionListener="#{form.getAgenda}"/>
  </body>
</html>

این صفحه توسط قالب زیر تولید می‌شود:


@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  // بیان برنامه
  @Inject
  private Application application;
  //مدل
  private Long idMedecin;
  private Date jour = new Date();
  
// فهرست پزشکان
  public List<Medecin> getMedecins() {
    return application.getMedecins();
  }
  // تقویم
  public void getAgenda() {
    ...
}
  • میدان در خط ۹ از مقدار لیست در خط ۱۸ صفحه می‌خواند و در آن می‌نویسد. هنگامی که صفحه برای اولین بار نمایش داده می‌شود، مقدار انتخاب‌شده در لیست کشویی را تعیین می‌کند. در نمایش اولیه، idMedecin برابر با null است، بنابراین اولین پزشک انتخاب خواهد شد؛
  • متد در خطوط ۱۳–۱۵ آیتم‌ها را برای لیست کشویی پزشکان (خط ۱۹ صفحه) تولید می‌کند. هر گزینه تولید شده، برچسب (itemLabel) آن شامل عنوان، نام خانوادگی و نام پزشک خواهد بود و مقدار (itemValue) آن، شناسه پزشک خواهد بود،
  • میدان در خط ۱۰ دسترسی خواندن/نوشتن را به میدان ورودی در خط ۲۱ صفحه فراهم می‌کند. بنابراین، در نمایش اولیه، تاریخ فعلی نمایش داده می‌شود،
  • خطوط 17–19: متد getAgenda کلیک روی دکمه [Agenda] در خط 26 صفحه را مدیریت می‌کند. از آنجا که هیچ ناوبری وجود ندارد (صفحه [index.html] همیشه درخواست می‌شود)، اغلب از ویژگی actionListener به جای ویژگی action استفاده می‌شود. در این مورد، متدی که در قالب فراخوانی می‌شود هیچ نتیجه‌ای بر نمی‌گرداند.

وقتی دکمه [Agenda] کلیک می‌شود،

  • مقادیر ارسال می‌شوند: مقداری که از لیست کشویی پزشکان انتخاب شده است در فیلد idMedecin فرم ذخیره می‌شود، و روزی که در فیلد 'day' انتخاب شده است
  • روش getAgenda قالب فراخوانی می‌شود.

روش getAgenda به شرح زیر است:


  // بیان برنامه
  @Inject
  private Application application;

  // قالب
  private Long idMedecin;
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean form3Rendered = false;
  private Boolean erreurRendered = false;
  private String form2Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Medecin medecin;
  private List<Erreur> erreurs;

  //دفترچه خاطرات
  public void getAgenda() {
    try {
      // بازیابی پزشک
      medecin = application.gethMedecins().get(idMedecin);
      // عنوان فرم ۲
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      //دفترچه یادداشت پزشک برای یک روز معین
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      //نمایش فرم ۲
      setForms(false, true, false, false);
    } catch (Throwable th) {
      // نمایش خطا
      prepareVueErreur(th);
    }
  }

  //آماده‌سازی vueErreur
  private void prepareVueErreur(Throwable th) {
    // فهرست خطاها را ایجاد می‌کند
    erreurs = new ArrayList<Erreur>();
    erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    while (th.getCause() != null) {
      th = th.getCause();
      erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    }
//نمای خطا نمایش داده می‌شود
    setForms(false, false, false, true);
}

بیایید به یاد بیاوریم که متد getAgenda قرار است چه چیزی را نمایش دهد:

  • خط ۲۱: پزشک انتخاب‌شده از فرهنگ لغت پزشکان بازیابی می‌شود؛ این در bean application ذخیره شده است. برای این کار، از شناسه پزشک استفاده می‌کنیم که به idMedecin ارسال شده بود،
  • خط ۲۳: عنوان صفحه [form2.xhtml] که باید نمایش داده شود، آماده می‌شود. این پیام از فایل پیام‌ها گرفته شده است تا قابل بین‌المللی‌سازی باشد. این تکنیک در بخش ۲.۸.۵.۷، صفحه ۱۳۵ توضیح داده شده است.
  • خط ۲۵: از لایه [métier] خواسته می‌شود تا برنامه دکتر منتخب را برای روز انتخاب‌شده محاسبه کند،
  • خط ۲۷: [form2.xhtml] نمایش داده می‌شود،
  • خط ۲۸: اگر استثنائی رخ دهد، یک لیست خطا تولید می‌شود (خطوط ۳۷–۴۲) و صفحه [erreur.xhtml] نمایش داده می‌شود (خط ۴۴).

3.6.6.2. نمایش برنامهٔ کاری پزشک

صفحه [form2.xhtml] مربوط به نمای زیر است:

کد صفحه [form2.xhtml] به شرح زیر است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:c="http://java.sun.com/jsp/jstl/core">

  <body>
    <h2><h:outputText value="#{form.form2Titre}"/></h2>
    <h:commandButton value="#{msg['form2.accueil']}" action="#{form.accueil}" />
    <h:dataTable value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour" headerClass="reservationsHeaders" columnClasses="creneau,client,action">
      <h:column>  
        <f:facet name="header">  
          <h:outputText value="#{msg['form2.creneauHoraire']}"/> 
        </f:facet>  
        <h:outputText value="#{creneauMedecinJour.creneau.hdebut}:#{creneauMedecinJour.creneau.mdebut} - #{creneauMedecinJour.creneau.hfin}:#{creneauMedecinJour.creneau.mfin}" />  
      </h:column>  
      <h:column>  
        <f:facet name="header">  
          <h:outputText value="#{msg['form2.client']}"/>  
        </f:facet>  
        <c:if test="#{creneauMedecinJour.rv==null}">
          <h:outputText value=""/>
          <c:otherwise>
            <h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}"/>
          </c:otherwise>
        </c:if>
      </h:column>  
      <h:column>  
        <f:facet name="header"/>
        <h:commandLink action="#{form.action()}" value="#{creneauMedecinJour.rv==null ? msg['form2.reserver'] : msg['form2.supprimer']}">
          <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneau}"/>
        </h:commandLink>
      </h:column>  
    </h:dataTable>
  </body>
</html>

به یاد داشته باشید که متد getAgenda دو فیلد را در مدل مقداردهی اولیه کرده است:


// قالب
  private String form2Titre;
private AgendaMedecinJour agendaMedecinJour;

این دو فیلد صفحه [form2.xhtml] را پر می‌کنند:

  • خط ۱۰، عنوان صفحه،
  • خط ۱۲: دفترچه یادداشت پزشک با استفاده از یک تگ سه‌ستونی <h:dataTable> نمایش داده می‌شود،
  • خطوط ۱۳–۱۸: ستون اول زمان‌بندی‌ها را نمایش می‌دهد،
  • رده‌های ۱۹–۳۰: ستون دوم نام مراجعی را که ممکن است آن بازه زمانی را رزرو کرده باشد نمایش می‌دهد، و در غیر این صورت هیچ چیزی نمایش نمی‌دهد. برای انجام این انتخاب، از تگ‌های کتابخانه اصلی JSTL که در ردیف ۷ به آنها ارجاع داده شده است، استفاده می‌شود،
  • خطوط ۳۰–۳۵: ستون سوم در صورتی که بازه زمانی آزاد باشد، لینک [Réserver] و در صورتی که رزرو شده باشد، لینک [Supprimer] را نمایش می‌دهد.

لینک‌های ستون سوم به قالب زیر متصل هستند:


// قالب
  private Long idCreneau;

  // اقدام بر روی RV
  public void action() {
    ...
}
  • متد action زمانی فراخوانی می‌شود که کاربر روی لینک Book / Delete (خط ۳۲) کلیک می‌کند. توجه داشته باشید که در اینجا از ویژگی action استفاده شده است. متدی که این ویژگی به آن اشاره می‌کند باید امضای String action() را داشته باشد، زیرا این متد باید یک کلید ناوبری را بازگرداند. با این حال، در اینجا `void action()` است. این کار باعث ایجاد خطا نشد و می‌توانیم فرض کنیم که در این مورد هیچ مسیریابی وجود ندارد. این رفتار مورد نظر بود. تنظیم `actionListener` به جای `action` باعث اختلال در عملکرد شد،
  • میدان idCreneau در خط ۲، شناسه بازه زمانی لینک کلیک‌شده (خط ۳۳ صفحه) را بازیابی خواهد کرد.

3.6.6.3. حذف یک قرار ملاقات

بیایید کدی را که حذف یک قرار ملاقات را مدیریت می‌کند، بررسی کنیم. این کد با توالی نماهای زیر مطابقت دارد:

کد مربوط به این عملیات به شرح زیر است:


// بیان برنامه
  @Inject
  private Application application;

  // قالب
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean form3Rendered = false;
  private Boolean erreurRendered = false;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneau;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;

  // اقدام روی RV
  public void action() {
    //در حال جستجوی یک شکاف در دفترچه یادداشت
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneau) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // آیا یکی پیدا شده است؟
    if (!trouvé) {
      // این عجیب است – فرم2 دوباره نمایش داده می‌شود
      setForms(false, true, false, false);
      return;
    }
    // پیدا شد
    creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    // بسته به اقدام مورد نظر
    if (creneauChoisi.getRv() == null) {
      reserver();
    } else {
      supprimer();
    }
  }
  //رزرو

  public void reserver() {
    ...
  }

  public void supprimer() {
    try {
      // حذف یک قرار ملاقات
      application.getMetier().supprimerRv(creneauChoisi.getRv());
      //تقویم به‌روزرسانی می‌شود
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      // نمایش فرم2
      setForms(false, true, false, false);
    } catch (Throwable th) {
      // نمایش خطا
      prepareVueErreur(th);
    }
  }
  • خط ۱۶: هنگامی که متد action شروع می‌شود، شناسه (ID) بازه زمانی انتخاب‌شده به idCreneau (خط ۱۱) ارسال شده است،
  • خطوط ۱۸–۲۶: سیستم تلاش می‌کند تا بازه زمانی را از id (خط ۲۱) بازیابی کند. ما آن را در تقویم جاری، agendaMedecinJour از خط ۱۰، جستجو می‌کنیم. به طور معمول، باید آن را پیدا کنیم. اگر نه، کاری انجام نمی‌دهیم (خطوط ۲۸–۳۲)،
  • خط ۳۴: اگر بازه زمانی مورد نظر پیدا شد، یک مرجع برای آن بازیابی کرده و در خط ۱۲ ذخیره می‌کنیم،
  • خط ۳۶: بررسی می‌کنیم که آیا بازه زمانی انتخاب‌شده قرار ملاقاتی داشته است یا خیر. در صورت وجود، آن را حذف می‌کنیم (خط ۳۹)؛ در غیر این صورت، یک قرار ملاقات رزرو می‌کنیم (خط ۳۷)،
  • خط ۵۱: قرار ملاقات در اسلات انتخاب‌شده حذف می‌شود. این کار توسط لایه [métier] انجام می‌شود،
  • خط ۵۳: ما دفترچه یادداشت به‌روزرسانی‌شده دکتر را از لایه [métier] درخواست می‌کنیم. البته، در آنجا یک قرار ملاقات کمتر خواهیم دید. با این حال، از آنجایی که این برنامه چندکاربره است، ممکن است تغییراتی را که توسط کاربران دیگر ایجاد شده است، مشاهده کنیم،
  • خط ۵۵: صفحه [form2.xhtml] مجدداً نمایش داده می‌شود،
  • خط ۵۸: از آنجایی که لایه [métier] فراخوانی شده است، ممکن است استثناهایی رخ دهد. در این صورت، استک استثناها در لیست خطاها در خط ۱۳ ذخیره شده و با استفاده از نما [erreur.xhtml] نمایش داده می‌شود.

3.6.6.4. رزرو وقت ملاقات

رزرو وقت ملاقات طبق این توالی انجام می‌شود:

قالب درگیر در این اقدام به شرح زیر است:


// قالب
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean form3Rendered = false;
  private Boolean erreurRendered = false;
  private String form3Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Medecin medecin;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;

  // اقدام روی RV
  public void action() {
...
    // یافت شد
    creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    // بسته به اقدام مورد نظر
    if (creneauChoisi.getRv() == null) {
      reserver();
    } else {
      supprimer();
    }
  }
  //رزرو

    public void reserver() {
    try {
      // عنوان فرم ۳
      form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
                creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
      //مشتری از منوی کشویی انتخاب شده است
      idClient=null;
      //فرم ۳ نمایش داده می‌شود
      setForms(false, false, true, false);
    } catch (Throwable th) {
      // نمایش خطا
      prepareVueErreur(th);
    }
  }
  • خط ۱۴: اگر بازه زمانی انتخاب‌شده هیچ قرار ملاقاتی نداشته باشد، در این صورت یک رزرو جدید است،
  • خط ۳۰: عنوان صفحه [form3.xhtml] با استفاده از همان تکنیکی که برای عنوان صفحه [form2.xhtml] به کار رفته است، تولید می‌شود،
  • خط ۳۴: در این فرم، یک لیست کشویی وجود دارد که مقدار آن توسط idClient پر می‌شود. ما مقدار این فیلد را روی null تنظیم می‌کنیم تا هیچ‌کس انتخاب نشده باشد،
  • خط ۳۶: صفحه [form3.xhtml] را نمایش دهید،
  • خط ۳۹: یا صفحهٔ خطا در صورت رخ دادن یک استثنا.

صفحه [form3.xhtml] به شرح زیر است:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <body>
    <h2><h:outputText value="#{form.form3Titre}"/></h2>
    <h:panelGrid columns="2">
      <h:outputText value="#{msg['form3.client']}"/>
      <h:selectOneMenu value="#{form.idClient}">
        <f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
      </h:selectOneMenu>
      <h:panelGroup>
        <h:commandButton value="#{msg['form3.valider']}" actionListener="#{form.validerRv}" />
        <h:commandButton value="#{msg['form3.annuler']}" actionListener="#{form.annulerRv}"/>
      </h:panelGroup>
    </h:panelGrid>
  </body>
</html>

این صفحه توسط قالب زیر تولید می‌شود:


// بیان برنامه
  @Inject
  private Application application;

  // قالب
  private Long idClient;

  // فهرست مشتریان
  public List<Client> getClients() {
    return application.getClients();
  }
  • خط ۶: شماره مشتری ویژگی value لیست کشویی مشتریان در خط ۱۲ صفحه را پر می‌کند. این ویژگی آیتم انتخاب‌شده در لیست کشویی را تعیین می‌کند،
  • خطوط ۹–۱۱: متد getClients محتوای لیست کشویی (خط ۱۳) را پر می‌کند. برچسب (itemLabel) برای هر گزینه، [Titre Prénom Nom] برای مشتری است و مقدار مرتبط (itemValue) شناسه مشتری می‌باشد. بنابراین این مقدار ارسال خواهد شد.

3.6.6.5. تأیید قرار ملاقات

تأیید قرار ملاقات شامل توالی زیر است:

و معادل کلیک کردن روی دکمه [Valider] است:


        <h:commandButton value="#{msg['form3.valider']}" actionListener="#{form.validerRv}" />

بنابراین متد [Form].validerRv این رویداد را مدیریت خواهد کرد. کد آن به شرح زیر است:


  // بیان برنامه
  @Inject
  private Application application;
  
  // قالب
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean form3Rendered = false;
  private Boolean erreurRendered = false;
  private Long idCreneau;
  private Long idClient;
  private List<Erreur> erreurs;

  //اعتبارسنجی قرار ملاقات
  public void validerRv() {
    try {
      //بازیابی یک نمونه از بازه زمانی انتخاب‌شده
      Creneau creneau = application.getMetier().getCreneauById(idCreneau);
      // افزودن قرار ملاقات
      application.getMetier().ajouterRv(jour, creneau, application.gethClients().get(idClient));
      // به‌روزرسانی تقویم
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      //نمایش فرم 2
      setForms(false, true, false, false);
    } catch (Throwable th) {
      // بررسی خطاها
      prepareVueErreur(th);
    }
}
  • خط ۱۲: قبل از اجرای متد validerRv، فیلد idClient با شناسه مشتری انتخاب‌شده توسط کاربر مقداردهی شده است،
  • خط ۱۹: با استفاده از شناسه بازه زمانی که در مرحله قبلی ذخیره شده است (bean دارای دامنه جلسه است)، از لایه [métier] خواسته می‌شود تا مرجعی به خودِ بازه زمانی ارائه دهد،
  • خط ۲۱: از لایه [métier] خواسته می‌شود تا یک قرار ملاقات برای روز انتخاب‌شده (day)، بازه زمانی انتخاب‌شده (slot) و مشتری انتخاب‌شده (idClient) اضافه کند،
  • خط ۲۳: به لایه [métier] دستور داده می‌شود که دفترچه یادداشت پزشک را تازه کند. قرار ملاقات اضافه شده، به همراه هرگونه تغییری که ممکن است سایر کاربران اپلیکیشن ایجاد کرده باشند، قابل مشاهده خواهد بود،
  • خط ۲۵: تقویم [form2.xhtml] مجدداً نمایش داده می‌شود،
  • خط ۲۸: در صورت رخ دادن خطا، صفحه خطا نمایش داده می‌شود.

3.6.6.6. لغو قرار ملاقات

این با توالی زیر مطابقت دارد:

دکمه [Annuler] در صفحه [form3.xhtml] به شرح زیر است:


        <h:commandButton value="#{msg['form3.annuler']}" actionListener="#{form.annulerRv}"/>

بنابراین متد [Form].annulerRv فراخوانی می‌شود:


  // قرار ملاقات را لغو می‌کند
  public void annulerRv() {
    // نمایش فرم2
    setForms(false, true, false, false);
}

3.6.6.7. بازگشت به صفحه اصلی

یک اقدام دیگر برای بررسی وجود دارد، که مربوط به دنباله زیر است:

کد دکمه [Accueil] در صفحه [form2.xhtml] به شرح زیر است:


    <h:commandButton value="#{msg['form2.accueil']}" action="#{form.accueil}" />

متد [Form].accueil به شرح زیر است:


  public void accueil() {
    //نمایش صفحهٔ اصلی
    setForms(true, false, false, false);
}

3.7. Conclusion

ما اپلیکیشن زیر را ساخته‌ایم:

ما بر عملکرد برنامه تمرکز کرده‌ایم تا رابط کاربری آن. رابط کاربری از طریق استفاده از کتابخانهٔ کامپوننت PrimeFaces بهبود خواهد یافت. ما یک برنامهٔ پایه‌ای ساخته‌ایم که با این حال معماری لایه‌بندی‌شدهٔ جاوا EE را با استفاده از EJB به نمایش می‌گذارد. این برنامه را می‌توان به روش‌های مختلف بهبود بخشید:

  • احراز هویت لازم است. همه مجاز به افزودن یا حذف قرارها نیستند؛
  • باید امکان پیمایش به جلو و عقب در تقویم هنگام جستجوی روزی که نوبت‌های خالی دارد، وجود داشته باشد،
  • باید بتوان فهرستی از روزهایی را درخواست کرد که یک پزشک در آن‌ها نوبت خالی دارد. در واقع، اگر پزشک چشم‌پزشک باشد، نوبت‌های وی معمولاً شش ماه زودتر رزرو می‌شوند،
  • ...

3.8. آزمایش با اکلیپس

3.8.1. لایه [DAO]

  • در [1]، پروژه EJB به همراه مشتری آن از لایه [DAO] وارد می‌شود،
  • در [2]، پروژه EJB را از لایه [DAO] انتخاب کرده و آن را در [3] اجرا کنید،
  • در [4]، آن را روی یک سرور اجرا کنید،
  • در [5]، تنها سرور GlassFish ارائه می‌شود زیرا تنها سرور دارای کانtejner EJB است،
  • در [6]، ماژول EJB مستقر شده است،
  • در [7]، لاگ‌ها نمایش داده می‌شوند:
1
2
3
4
5
6
7
8
Infos: Hibernate Validator 4.2.0.Final

Infos: Created EjbThreadPoolExecutor with thread-core-pool-size 16 thread-max-pool-size 32 thread-keep-alive-seconds 60 thread-queue-capacity 2147483647 allow-core-thread-timeout false 
...

Infos: EJB5181:Portable JNDI names for EJB DaoJpa: [java:global/mv-rdvmedecins-ejb-dao-jpa/DaoJpa!rdvmedecins.dao.IDaoRemote, java:global/mv-rdvmedecins-ejb-dao-jpa/DaoJpa!rdvmedecins.dao.IDaoLocal]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB DaoJpa: [rdvmedecins.dao#rdvmedecins.dao.IDaoRemote, rdvmedecins.dao]
Infos: mv-rdvmedecins-ejb-dao-jpa a été déployé en 5 523 ms.

این‌ها همان‌هایی هستند که با NetBeans داشتیم.

  • در [7A] و [7B]، تست کلاینت JUnit را اجرا می‌کنیم،
  • در [8]، تست با موفقیت اجرا می‌شود،
  • در [9]، کنسول لاگ می‌کند.

در [10]، برنامه EJB تخلیه می‌شود.

3.8.2. لایه [métier]

  • در [1]، چهار پروژه Maven را از لایه [métier] وارد کنید،
  • در [2]، پروژه سازمانی انتخاب شده و در [3]، روی سرور GlassFish [4] [5]، اجرا می‌شود،
  • در [6]، پروژه تجاری روی GlassFish مستقر شده است،
  • در [7]، ما لاگ‌های GlassFish را بررسی می‌کنیم،
1
2
3
4
Infos: EJB5181:Portable JNDI names for EJB DaoJpa: [java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-dao-jpa-1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoLocal, java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-dao-jpa-1.0-SNAPSHOT/DaoJpa!rdvmedecins.dao.IDaoRemote]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB DaoJpa: [rdvmedecins.dao#rdvmedecins.dao.IDaoRemote, rdvmedecins.dao]
Infos: EJB5181:Portable JNDI names for EJB Metier: [java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierLocal, java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote]
Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB Metier: [rdvmedecins.metier.service.IMetierRemote#rdvmedecins.metier.service.IMetierRemote, rdvmedecins.metier.service.IMetierRemote]

در خط ۳، نام قابل حمل EJB [Metier] را یادداشت کرده و آن را در کنسول کلاینت برای این EJB می‌چسبانیم:


public class ClientRdvMedecinsMetier {

  //نام رابط راه دور برای EJB [Metier]
  private static String IDaoRemoteName = "java:global/mv-rdvmedecins-metier-dao-ear/mv-rdvmedecins-ejb-metier-1.0-SNAPSHOT/Metier!rdvmedecins.metier.service.IMetierRemote";
  //تاریخ امروز
private static Date jour = new Date();
  • در [8]، کلاینت کنسول را اجرا کنید،
  • در [9]، لاگ‌های آن.
  • در [10]، برنامه سازمانی تخلیه می‌شود؛

3.8.3. لایه [web]

  • به [1]، سه پروژه Maven از لایه [web] وارد می‌شوند. پروژه‌ای که پسوند «ear» دارد، پروژه سازمانی است که باید روی GlassFish مستقر شود،
  • در [2]، آن را اجرا می‌کنیم،
  • روی سرور GlassFish [3]،
  • در [4]، برنامهٔ سازمانی با موفقیت مستقر شده است،
  • در [5]، ما URL برنامه را در مرورگر داخلی Eclipse درخواست می‌کنیم.