اخیراً وبسایت شخصی نیما مورد حملات مشکوک قرار گرفته است. او در حال بررسی لاگهای وبسرور سایتش است تا دریابد این حملات از کدام سمت بودهاند، اما برای این کار از یک ویرایشگر متنی ساده استفاده میکند. او میخواهد بتواند روی لاگها فیلتر اعمال کند تا جستوجوی هدفمندی داشته باشد. از شما میخواهیم چنین برنامهای را برای او پیادهسازی کنید.
جزئیات پروژه
پروژهی اولیه را از این لینک دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
nginx-log-parser
├── exceptions
│ ├── LineDoesNotMatchException.java
│ ├── PropertyNotFoundException.java
│ └── TransformerNotFoundException.java
├── Condition.java
├── Log.java
├── LogList.java
├── LogParser.java
├── LogParserSampleTest.java
└── LogPropertyTransformerRepository.java
بستهی exceptions
این بسته شامل استثناهای موردنیاز برنامه است. همهی کلاسهای موجود در این بسته از کلاس RuntimeException
ارثبری میکنند.
کلاس Log
هر نمونه از این کلاس بیانگر لاگ یک درخواست است. هر لاگ شامل یک یا چند ویژگی است. این کلاس از قبل بهطور کامل پیادهسازی شده و شامل متدهای زیر است:
- متد
setProperty(String name, String value)
: این متد با دریافت نام و مقدار یک ویژگی، آن ویژگی را به ویژگیهای لاگ اضافه میکند. - متد
getProperty(String name)
: این متد با دریافت نام یک ویژگی، مقدار آن ویژگی را برمیگرداند. در صورتی که ویژگیای با نام واردشده موجود نباشد، یک استثنا از نوعPropertyNotFoundException
پرتاب میشود.
کلاس LogPropertyTransformerRepository
بعضی از ویژگیهای لاگها (مانند کد وضعیت پاسخ و زمان ارسال درخواست) ماهیت عددی دارند. این کلاس، مجموعهای از Function<String, Long>
را در خود نگهداری میکند تا با استفاده از آنها، بتوان مقدار عددی معادل بعضی از ویژگیهای لاگها را دریافت کرد. این کلاس از قبل بهطور کامل پیادهسازی شده و شامل متدهای زیر است:
- متد
addTransformer(String propertyName, Function<String, Long> transformer)
: این متد با دریافت نام یک ویژگی و یک تابع برای تبدیل مقدار رشتهای ویژگی به مقدار عددی، آن تابع را درMap
موجود در کلاس ذخیره میکند. - متد
getTransformer(String propertyName)
: این متد با دریافت نام یک ویژگی، تابع مبدل مقدار آن ویژگی به مقدار عددی را برمیگرداند. در صورتی که تابعی برای ویژگی موردنظر تعریف نشده باشد، یک استثنا از نوعTransformerNotFoundException
پرتاب میشود.
کلاس Condition
هر نمونه از این کلاس، بیانگر یک شرط (ساده یا مرکب) برای فیلتر کردن لاگها است. پراپرتیهای موردنیاز خود را در این کلاس تعریف کرده و متدهای زیر را در آن پیادهسازی کنید:
- متد استاتیک
equal(String propertyName, String value)
را طوری پیادهسازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با مقدار موردنظر در آن موجود باشد. - متد استاتیک
notEqual(String propertyName, String value)
را طوری پیادهسازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با مقدار موردنظر در آن موجود باشد. - متد استاتیک
in(String propertyName, String... values)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با حداقل یکی از مقادیر واردشده در آن موجود باشد. - متد استاتیک
in(String propertyName, String... values)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با هیچ یک از مقادیر واردشده در آن موجود باشد. - متد استاتیک
lessThan(String propertyName, Long value)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط کوچکتر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاسLogPropertyTransformerRepository
استفاده کنید. - متد استاتیک
lessOrEqual(String propertyName, Long value)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط کوچکتر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاسLogPropertyTransformerRepository
استفاده کنید. - متد استاتیک
greaterThan(String propertyName, Long value)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط بزرگتر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاسLogPropertyTransformerRepository
استفاده کنید. - متد استاتیک
greaterOrEqual(String propertyName, Long value)
را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاسCondition
برگرداند که اطلاعات مربوط به شرط بزرگتر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاسLogPropertyTransformerRepository
استفاده کنید. - متد
and(Condition condition)
را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاسCondition
برگرداند که شرط آن برابر با AND منطقی شرط فعلی و شرط واردشده باشد. - متد
or(Condition condition)
را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاسCondition
برگرداند که شرط آن برابر با OR منطقی شرط فعلی و شرط واردشده باشد. - متد استاتیک
not(Condition condition)
را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاسCondition
برگرداند که شرط آن برابر با NOT منطقی شرط فعلی و شرط واردشده باشد. - متد
test(Log log)
را طوری پیادهسازی کنید که با دریافت یک لاگ، در صورتی که لاگ در شرایط فعلی صدق کند، مقدارtrue
و در غیر اینصورت، مقدارfalse
را برگرداند.
نکته: متدهای and
، or
و not
را باید بهگونهای پیادهسازی کنید که نمونههای جدیدی از کلاس Condition
ایجاد شوند. در واقع، نمونههای کلاس Condition
باید immutable باشند.
کلاس LogList
این کلاس، یک List
از لاگها را در خود نگهداری میکند.
- متد
all
این کلاس را طوری پیادهسازی کنید که یکLogList
شامل یکList
جدید که لاگهای موجود در آبجکت فعلی در آن قرار گرفتهاند ایجاد کرده و برگرداند. - متد
where
این کلاس را طوری پیادهسازی کنید که با دریافت یکCondition
، یکLogList
شامل یکList
جدید که لاگهای موجود در آبجکت فعلی در آن قرار گرفتهاند و در شرط واردشده صدق میکنند ایجاد کرده و برگرداند. ترتیب لاگهای موجود در لیست را نباید تغییر دهید.
کلاس LogParser
وظیفهی این کلاس، پارس کردن رشتهی لاگها است. لاگها یک schema مشخص دارند که در قالب یک رشته به کانستراکتور این کلاس داده میشود. schema شامل تعدادی متغیر نیز هست که با $
آغاز میشوند. مثالی از یک schema:
$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
در اینصورت، لاگهای پارسشده باید شامل ویژگیهای remote_addr
، remote_user
و... باشند.
نمونهای از یک لاگ که طبق فرمت schema بالا است:
127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] "GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1" 200 43 "http://test.local/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"
- متد
parseLog(String line)
این کلاس را طوری پیادهسازی کنید که با دریافت یک رشتهی یکخطی، یکLog
متناظر با لاگ واردشده برگرداند. در صورتی که لاگ واردشده با schema دادهشده مطابقت نداشت، یک استثنا از نوعLineDoesNotMatchException
پرتاب کنید. ترتیب ویژگیهایLog
خروجی باید مطابق ترتیب متغیرهای موجود در schema باشد. - متد
parseLogs(String logs)
را طوری پیادهسازی کنید که با دریافت یک رشتهی یک یا چند خطی، یکLogList
متناظر با لاگهای واردشده برگرداند. برای پیادهسازی این متد، از متدparseLog(String line)
استفاده کنید. - متد
parseLogs(File file)
را طوری پیادهسازی کنید که با دریافت یک فایل، یکLogList
متناظر با لاگهای موجود در آن برگرداند. برای پیادهسازی این متد، از متدparseLogs(String logs)
استفاده کنید.
مثال
با اجرای متد main
موجود در کلاس LogParser
:
public static void main(String[] args) {
LogPropertyTransformerRepository.addTransformer("status", Long::parseLong);
String schema = "$remote_addr - $remote_user [$time_local] \"$request\" $status $bytes_sent \"$http_referer\" \"$http_user_agent\"";
LogParser parser = new LogParser(schema);
LogList logs = parser.parseLogs("127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] \"GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1\" 200 43 \"http://test.local/\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0\"");
System.out.println(logs.where(Condition.greaterOrEqual("status", 200L)));
}
خروجی باید بهصورت زیر باشد:
[Log[remote_addr=127.0.0.1, remote_user=-, time_local=07/Jul/2018:17:37:28 +0200, request=GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1, status=200, bytes_sent=43, http_referer=http://test.local/, http_user_agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0]]
نکته: تستهای نمونهی سؤال در کلاس LogParserSampleTest
موجود هستند. با افزودن JUnit به classpath پروژه، میتوانید آنها را اجرا کنید.
آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، یک فایل زیپ آپلود کنید که وقتی آن را باز میکنیم، با فایلهای زیر مواجه شویم:
Condition.java
LogList.java
LogParser.java
ارسال پاسخ برای این سؤال