پیاده سازی جستجو و شاخص گذاری

نویسنده: جناب آقای محمد خدایی‌مهر

 

    عملیات جستجو و شاخص گذاری در Liferay با استفاده از Apache Lucene، که یک کتابخانه مبتنی بر جاوا است، انجام می شود. به منظور پیاده سازی عملیات جستجو و شاخص گذاری برای هر موجودیت، لازم است سه مرحله زیر را بپیمایید:

  1. کلاس *Indexer در پورتلت پروژه ایجاد کنید و این کلاس را در فایل liferay-portlet.xml ثبت کنید.
  2. لایه سرویس موجودیت ها را به روز رسانی کنید تا index در هنگام ایجاد، تغییر یا حذف یک موجودیت به روز شود.
  3. مکانیزمی فراهم کنید تا جستجو انجام شود. به عنوان مثال، می توانید یک JSP در پورتلت پروژه ایجاد کنید تا پرس و جو های جستجو را در آن وارد کنید و یک JSP دیگر برای نمایش نتایج جستجو ایجاد کنید. یا به راحتی می توانید پورتلت جستجو Liferay را پیکربندی کنید تا بتوانید موجودیت ها را جستجو کنید.

 

قبل از ادامه کار مطمئن شوید با اصطلاحات مورد استفاده در جستجو و شاخص گذاری که در زیر آورده شده اند آشنایی دارید:

  • شاخص جستجو شامل مجموعه ای از اسناد است که در حقیقت اشیاء جاوا می باشند و نماینده موجودیت های ذخیره شده در پایگاه داده هستند
  • سند(document) شامل مجموعه ای از field ها و مقادیر آن ها است. field های یک سند نشان دهنده metadata درباره هر سند است. برخی از field های معمول عبارتند از title، content، description، create date، tags و غیره. field های سند لزوماً به ویژگی های موجودیت مرتبط نمی باشند.
  • یک field می تواند دارای یک یا چند مقدار باشد. field یک مقداره می تواند تنها یک term داشته باشد. field چند مقداره می تواند دارای چندین term باشد. term یک مقدار مجزا و غیر تهی است که می تواند مورد جستجو قرار گیرد.
  • Phrase دنباله ای از term های جدا از هم به وسیله space است. تنها راه استفاده از یک phrase به عنوان term در جستجو، قرار دادن در بین (“) است.
  • نتیجه جستجو مجموعه ای از hit ها می باشد. Hit ها اشاره گر هایی به اسناد منطبق با پرس و جو هستند.

ایجاد و ثبت یک کلاس شاخص

کلاس های شاخص مسئول ایجاد اسناد Lucene هستند که نشان دهنده موجودیت ها می باشند. هنگامی که کلاس شاخص ایجاد می کنید، باید تصمیم بگیرید اسناد شما شامل کدام field ها باشند و آن ها چگونه باید پر شوند. توصیه می شود کلاس شاخص کلاس com.liferay.portal.kernel.search.BaseIndexer را extend کند یا حداقل کلاس com.liferay.portal.portal.kernel.search.Indexer را implement کند. این کار امکان تجمیع موجودیت های شما با موجودیت های پورتال(assets) را می دهد و به موجودیت های شما اجازه می دهد تا از چارچوب های پورتال مانند faceted search استفاده کنند. اگر قصد دارید موجودیت های خود را asset-enable کنید، ساخت یک تولید کننده شاخص برای آن ها ضروری است. هنگام ایجاد کلاس تولید کننده شاخص، می توان از کلاس های Indexer که مطابق asset های Liferay هستند استفاده کنید. این موارد شامل BlogIndexer، JournalArticleIndexer، WikiPageIndexer و غیره هستند.

اگر کلاس تولید کننده شاخص کلاس com.liferay.portal.kernel.search.BaseIndexer را extend می کند، باید متدهای زیر را implement یا override کنید:

  • Public String [] getClassNames ()
  • Public String getPortletId ()
  • Protected void doDelete (Object obj)
  • Protected Document doGetDocument(Object obj)
  • Protected Summary doGetSummary (Document document, Locale locale, String snippet, PortletURL portletURL)
  • Protected void doReindex (Object obj)
  • Protected void doReindex (String className, long classPK)
  • Protected void doReindex (String [] ids)
  • Protected String getPortletId (SearchContext searchContext)

همچنین توصیه می شود که ثابت های public static final String[] CLASS_NAMES و public static final String PORTLET_ID را تعریف کنید. این ثابت ها باید شامل نام کلاس های موجودیت و نام پورتلت مرتبط با آن نیز باشند. کلاس های *Indexer در Liferay از این قانون استفاده می کنند. استفاده از این ثابت ها در پلاگین ها، این اطمینان را می دهد که شما همواره از نام های کلاس و portlet ID صحیح استفاده می کنید.

به محض اینکه کلاس Indexer را برای موجودیت مورد نظرتان ایجاد کردی، باید این کلاس را در Liferay ثبت کنید. برای انجام این کار، فایل docroot/WEB_INF/liferay-portlet.xml را باز کنید و entry زیر را در آن وارد کنید:

به عنوان مثال:

اگر در فایل liferay-portlet.xml تازه تولید شده کار می کنید، entry باید دقیقاً زیر <icon> و داخل <portlet> قرار گیرد. پس از افزودن <indexer-class> به liferay-portlet.xml مجدداً پروژه را deploy کنید.

اگر می خواهید محتویات indexer پورتال خود را مرور کنید، می توانید اسکریپت Groovy زیر را از کنسول Liferay Script اجرا کنید. این اسکریپت از کلاس IndexerRegistryUtil در Liferay استفاده می کند تا لیستی از indexer های ثبت شده را بازیابی کند. سپس portlet ID و classnames برای هرکدام از indexer ها را چاپ می کند. برای دسترسی به کنسول اسکریپت به Admin -> Control Panel رفته و سپس روی Server Administration و پس از آن Script کلیک کنید. Groovy برای زبان را انتخاب کرده و سپس اسکریپت زیر را وارد کنید.

پیاده سازی شاخص گذاری در لایه Service

اگر نرم افزار داده محور ایجاد می کنید، احتمالاً برای اضافه کردن، به روز رسانی و حذف موجودیت ها کدی نوشته اید. اگر می خواهید اسناد مربوط به موجودیت ها در Liferay Lucene به درستی منعکس کننده خود این موجودیت ها باشند، لازم است مطمئن شوید تولید کننده شاخص را به نحوی ایجاد کنید تا هر موجودیتی که اضافه یا به روز رسانی شده را مجدداً شاخص گذاری کند. هنگامی که یک موجودیت حذف می شود، لازم است اسناد متناظر با آن را از شاخص حذف کنید. به منظور به دست آوردن نمونه ای از کلاس indexer، از کلاس IndexerRegistryUtil در Liferay استفاده کنید. این کلاس شامل متدهای getIndexer و nullSafeGetIndexer می باشد. هردو این متدها می توانند آرگومان های کلاس مانند MyEntity.class یا رشته ای که بیانگر نام کلاس باشد مانند My Entity را به عنوان ورودی بگیرند. اگر از getIndexer استفاده می کنید و هیچ مقداری مطابق آرگومان ورودی نباشد، مقدار null بازگشت داده می شود. با این حال اگر از nullSafeGetIndexer استفاده کنید و هیچ مقداری مطابق آرگومان نباشد، یک مقدار ساختگی بازگشت داده می شود. بازگشت یک مقدار ساختگی در مقایسه با بازگشت مقدار null از امنیت بیشتری برخوردار است زیرا مقدار null ممکن است باعث ایجاد exception شود.

هنگامی که تولید کننده شاخص مطابق موجودیت مورد نظرتان را به دست آوردید، لازم است عمل شاخص گذاری مناسبی را فراخوانی کنید. هر زمان که یک موجودیت جدید اضافه شود، سند مطابق با آن باید به index اضافه شود. هرگاه یک موجودیتی که در حال حاضر وجود دارد، به روز رسانی شد، سند مطابق با آن نیز باید به روز رسانی شود. هر دو این اعمال، شاخص گذاری و شاخص گذاری مجدد، با فراخوانی متد reindex مربوط به indexer قابل انجام هستند. این متد overload شده است. هنگامی که موجودیتی حذف می شود، سند مربوط به آن نیز باید از index حذف شود. برای انجام این کار می توانید از متد delete استفاده کنید.

اگر از service builder برای لایه سرویس پورتلت پروژه استفاده می کنید، باید منطق business سفارشی به کلاس [Entity] LocalServiceImpl اضافه شود. اگر نرم افزار خود را با سیستم permission در Liferay یکپارچه کرده اید، احتمالاً متدهای add[Entity]، update[Entity] و delete[Entity] را در دسترس دارید که متدهای مختلفی از resourceLocalService مانند add، update و delete منابع موجودیت را فراخوانی می کند. اگر موجودیت ها  را asset-enabled کرده باشید، از assetEntryLocalService و assetLinkLocalService برای اضافه کردن، به روز رسانی و حذف asset entry ها و  asset link ها می توان استفاده کرد. برای انجام این کار، خطوط زیر را به متدهای add[Entity] و update[Entity] اضافه کنید:

و خطوط زیر را در متد delete[Entity] اضافه کنید:

البته در هردو کد بالا به جای [Entity] نام موجودیت مورد نظرتان را باید وارد کنید. فایل [Entity]LocalServiceImpl.java را ذخیره کرده و پورتلت پروژه را مجدداً deploy کنید.

از پورتلت خود برای افزودن و به روز رسانی موجودیت های خود به منظور تست استفاده کنید. اگر به درستی عمل می کند، indexer باید اسناد را برای هر موجودیت که اضافه یه به روز رسانی شده است، در Liferay Index اضافه کند. به طور پیشفرض، داده Liferay Index در [Liferay Home]/data/lucene ذخیره می شود. این فولدر شامل زیر فولدرهایی متناظر با هرکدام از portal instance ها است. به عنوان مثال، اگر portal instance پیشفرض شما دارای company ID با مقدار ۱۰۱۵۴ باشد، باید فولدری با نام ۱۰۱۵۴ در [Liferay Home]/data/lucne داشته باشید. محتوای فولدر ۱۰۱۵۴ در قالب باینری است و توسط انسان قابل خواندن نیستند. با این حال، می توان از ابزارهای واسطه مانند Luke به منظور مرور شاخص های Lucene استفاده کرد.

Luke یک ابزار توسعه است که به شما امکان مرور و تغییر شاخص های Lucene موجود را می دهد. این ابزار به صورت رایگان و open-source در دسترس است. می توانید فایل .jar آن را از https://code.google.com/p/luke دانلود کنید. برای اجرای Luke خط فرمان یا ترمینال را باز کنید، به فولدر حاوی فایل .jar بروید و فرمان زیر را اجرا کنید:

هنگامی که Luke اجرا شد، می توان شاخص های Liferay Lucene را بررسی کرد تا اطمینان حاصل شود موجودیت ها شاخص گذاری شده اند. گزینه File -> Open Lucene Index را کلیک کرده و مسیر فولدر portal instance را وارد کنید. اگر Company Id مربوط به portal instance مورد نظر شما ۱۰۱۵۴ است، مسیر زیر را وارد کنید:

موارد زیر را رعایت کنید:

  • در حالت فقط خواندنی باز کنید
  • اگر lock شده بود از force unlock استفاده کنید

سپس ok را کلیک کنید. اگر tab اسناد Luke را کلیک کنید، می توانید تمام اسناد index شده با شماره سند را مرور کنید. اگر tab جستجو Luke را کلیک کنید، می تواند کوئری جستجو را وارد کنید. روی دکمه جستجو کلیک کنید و Luke لیستی از تمام اسناد مطابق را نمایش می دهد.

ایجاد مکانیزم جستجو

حال که indexer ثبت شده و index با اضافه شدن، به روز رسانی و حذف موجودیت به روز می شود، زمان آن فرا رسیده تا مکانیزمی برای جستجو ایجاد کنیم. راحت ترین راه برای دادن امکان جستجو به کاربران برای جستجو در موجودیت ها، از طریق پورتلت جستجو Liferay است. به طور  پیشفرض، پورتلت جستجو Liferay تنها اجازه جستجو در asset های out-of-the-box را می دهد که شامل موارد زیر هستند:

  • Users
  • Bookmarks
  • Bookmark folders
  • Blog posts
  • Documents and Media files
  • Documents and Media folders
  • Web Content files
  • Web Content folders
  • Message Board Messages
  • Wiki pages

پیکربندی پورتلت جستجو برای گونه های اضافی asset آسان است. برای این کار، پورتلت جستجو را به یک صفحه اضافه کنید و پنجره Configuration را باز کنید. در Setup tab، گزینه Advanced را کلیک کنید تا text area مربوط به Search Configuration نمایان شود. درمیان پیکربندی JSON جستجو کنید تا کد زیر را بیابید:

موجودیت خود را به انتهای آن اضافه کنید تا مطابق شکل زیر شود:

مسیر پکیج و نام موجودیت را با مسیر و نام موجودیت خود در کد بالا عوض کنید. سپس Save را کلیک کنید. با این روش می توانید هر تعداد از موجودیت ها را به پیکربندی پورتلت جستجو اضافه کنید.

با این حال، مجبور به استفاده از پورتلت جستجو Liferay نیستید. در این بخش خواهید آموخت چگونه از Liferay API استفاده کنید تا مکانیزم جستجو را در پورتلت خود ایجاد کنید. می توانید یک JSP برای وارد کردن کوئری جستجو و یک JSP دیگر برای نمایش نتایج جستجو ایجاد کنید. هنگامی که جستجو و شاخص گذاری را در یک پورتلت پیاده سازی می کنید، کلاس های Liferay زیر مهم هستند:

  • liferay. portal. kernel. search. SearchContext
  • liferay. portal. kernel. search. SearchContextFactory
  • liferay. portal. kernel. search. Indexer
  • liferay. portal. kernel. search. IndexerRegistryUtil
  • liferay. portal. kernel. search. BaseIndexer
  • liferay. portal. Kernel. search. SearchEngineUtil
  • liferay.portal.kernel. search. Hits
  • liferay. portal.kernel.search.Document

برای اجرای کوئری جستجو در Liferay، به شی SearchContext نیاز دارید. این شی جزئیاتی مانند نمونه company برای جستجو، کاربری که جستجو را فراخوانی می کند، locale، time zone و غیره در بر دارد.از آن جا که این کلاس دارای طیف گسترده ای از context properties می باشد، موثرترین روش برای به دست آوردن نمونه از آن، فراخوانی متد getInstance(HttpServletRequest request) از SearchContextFactory می باشد:

هنگامی که شی SearchContext را به دست آوردید، می توانید به آن مقادیر مختلفی مانند کلید واژه های جستجو، نوع صفحه بندی، مقادیر شروع و پایان و غیره را بدهید. به عنوان مثال می توانید از این مقادیر مانند کد زیر استفاده کنید:

از آن جا که کلید واژه بیانگر اصطلاح یا عبارت مورد جستجو است، مهمترین ویژگی می باشد.

پیشتر مشاهده کردید که چگونه از IndexerRegistryUtil برای به دست آوردن نمونه ای از indexer استفاده کنید. می توانید از هرکدام از ۴ متد زیر استفاده کنید:

  • getIndexer(Class<?> clazz)
  • getIndexer (String className)
  • nullSafeGetIndexer(Class<?> clazz)
  • nullSafeGetIndexer (String className)

به یاد داشته باشید هنگامی که plugin project خود را deploy کردید، Indexer شما توسط Liferay ثبت می شود. هنگامی که یکی از متدهای بالا را فراخوانی می کنید، Liferay نمونه ای از کلاس Indexer که مطابق آرگومان است را برمی گرداند. همچنین به یاد داشته باشید که کلاس Indexer باید کلاس com.liferay.portal.kernel.search.BaseIndexer را extend یا حداقل کلاس com.liferay.portal.kernel.search.Indexer را implement کند. اینترفیس Indexer متد search(SearchContext searchContext) را تعریف میکند که شی Hits را برمی گرداند. کلاس انتزاعی BaseIndexer پیاده سازی این متد را فراهم می کند که SearchEngineUtil را فراخوانی می کند.

در حالی که استفاده از یک Indexer مشخص به منظور جستجو امکان پذیر است، همچنین جستجو مستقیم با استفاده از SearchEngineUtil.search نیز امکان پذیر می باشد. SearchEngineUtil تمام پیچیدگی های پیاده سازی موتور جستجو را کنترل می کند. تمام ترافیک ها از موتور جستجو  و به موتور جستجو از طریق این کلاس عبور می کنند. اگر در حال debugging هستید، فعال کردن سطح debug برای logging این کلاس می تواند مفید واقع شود.

 

نتیجه فراخوانی search(SearchContext searchContext) یا SearchEngineUtil.search مربوط به یک Indexer، شی Hits می باشد. شی Hits شامل اسناد Lucene است که مطابق با کوئری جستجو هستند. اسناد Lucene که اشیاء Document هستند را می توان به صورت آرایه یا لیست از آن به دست آورد. به عنوان مثال، فرض کنید یک Indexer برای یک موجودیت به نام MyEntity دارید. فرض کنید دارای یک شی search context به نام searchContext هستید که حاوی رشته keywords است و این رشته عبارت مورد جستجوی شما می باشد. برای به دست آوردن شی Hits می توانید جستجو را به صورت زیر فراخوانی کنید:

اگر می خواهید از SearchEngineUtil.search به صورت مستقیم استفاده کنید، باید کوئری جستجو خود را ایجاد کنید و مطمئن شوید که search context به درستی پیکربندی شده است. Indexer ها در Liferay به موجودیت به خصوصی مرتبط هستند. هنگامی که از یک indexer برای جستجو استفاده می کنید، تنها موجودیت های مشخص شده جستجو می شوند. با استفاده از SearchEngineUtil.search می توانید این مورد را کنار بگذارید که به شما این امکان را می دهد تا موجودیت های مورد جستجو خود را با فراخوانی متد searchContext.setEntryClassNames مربوط به search context تعیین کنید. همچنین باید scope جستجو با فراخوانی متدهای searchContext.setCompanyId و searchContext.setGroupIds از search context را مشخص کنید.

SearchEngineUtil.search یک متد overloaded است. راحت ترین شکل این متد برای استفاده SearchEngineUtil.search(SearchContext searchContext , Query query) می باشد. برای استفاده از این متد باید کوئری خود را ایجاد کنید. Liferay اینترفیس پایه Query را داراست و علاوه بر آن چندین کلاس دیگر نیز آن را extend کرده اند و عبارتند از:

  • BooleanQuery
  • TermRangeQuery
  • TermQuery

علاوه بر این Liferay دارای چندین کلاس factory و پیاده سازی کوئری است. از کلاس های factory کوئری Liferay برای معرفی کلاس های پیاده سازی کوئری استفاده کنید. به عنوان مثال، فرض کنید می خواهید از موتور جستجوی Liferay برای جستجو اسناد شاخص دار و حاوی عبارت liferay در title استفاده کنید. برای انجام این کار می توانید از کد زیر استفاده کنید:

برای جستجو درمیان اسناد index شده با مقادیر field در یک محدوده خاص، می توان از کوئری های term range استفاده کرد. به عنوان مثال، فرض کنید می خواهید کوئری ای ایجاد کنید که اسناد شاخص گذاری شده را در یک روز خاص مانند ۴ دسامبر ۲۰۱۴  جستجو کند. برای ایجاد چنین کوئری می توانید از کد زیر استفاده کنید:

برای ایجاد کوئری های پیچیده تر به صورت برنامه ریزی شده، می توان از Boolean queries استفاده کرد. به عنوان مثال، فرض کنید می خواهید کوئری بنویسید که در میان اسناد شاخص دار جستجو کند که عبارت liferay به عنوان title باشد اما Lucene در قسمت description وجود نداشته باشد و در تاریخ ۴ دسامبر ۲۰۱۴ تغییر کرده باشد. به کمک کد زیر این کوئری را می نویسیم:

علاوه بر BooleanQuery، TermRangeQuery و TermQuery، Liferay کلاس StringQuery را نیز دارا می باشد. این کلاس به شما امکان ایجاد کوئری هایی با استفاده از Lucene query syntax را می دهد. پس از جستجو و به دست آوردن شی Hits، می توان نتایج را در قالب آرایه به شکل زیر بازیابی کرد:

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

برای نمایش نتایج جستجو، باید روی آرایه یا لیست از یک حلقه استفاده کنید. هر سند اساساً یک hash map از فیلدهای شاخص دار و مقادیر آن ها است. هنگامی که search query اجرا می شود، عبارت وارد شده توسط کاربر به عنوان keywords در نظر گرفته می شود. فایل JSP که نتایج جستجو را render می کند، حاوی کدی است که search context را ایجاد و پر می کند و یک Indexer را فراهم می کند و از یک Indexer برای جستجو استفاده می کند تا موجودیت هایی که مطابق Hits حاصل از جستجو هستند را بازیابی می کند. با این حال، این کد لازم نیست حتماً در JSP قرار گیرد. می توان آن را در متد action یا متد service یک پورتلت قرار داد.

در این آموزش نحوه ایجاد و ثبت Indexer برای موجودیتی در portlet project را آموختید. همچنین نحوه به روز رسانی لایه service را آموختید تا هرگاه اعمال add، update و delete بر روی یک موجودیت انجام گرفت، Indexer فراخوانی شود. به علاوه نحوه استفاده از Liferay Search API به منظور پیکربندی search context، انجام جستجو و به دست آوردن لیستی از نتایج جستجو را نیز آموختید. برای کشف امکانات بیشتری از Liferay Search API، لطفاً به آموزش مربوط به Faceted Search and Customized Search Filtering را مراجعه کنید.

    ارسال نظر

    نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

بالا