![Nginx](https://quera.org/qbox/view/tBWSXd1BII/nginx-logo.png)
اخیراً وبسایت شخصی نیما مورد حملات مشکوک قرار گرفته است. او در حال بررسی لاگهای وبسرور سایتش است تا دریابد این حملات از کدام سمت بودهاند، اما برای این کار از یک ویرایشگر متنی ساده استفاده میکند. او میخواهد بتواند روی لاگها فیلتر اعمال کند تا جستوجوی هدفمندی داشته باشد. از شما میخواهیم چنین برنامهای را برای او پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169409/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
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`:
```java
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
```
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.