Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions README.MD

This file was deleted.

88 changes: 70 additions & 18 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.javarush.khmelov</groupId>
<artifactId>project-ledzeppelin</artifactId>
<groupId>com.javarush.chebotarev</groupId>
<artifactId>project-pantera</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ProjectLedzeppelin</name>
<name>ProjectPantera</name>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<junit.version>5.10.2</junit.version>
</properties>

<parent>
Expand Down Expand Up @@ -50,11 +49,6 @@
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand All @@ -65,6 +59,22 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>

</dependencies>

Expand All @@ -75,20 +85,62 @@
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<!-- Surefire Plugin for running unit tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<excludes>
<exclude>**/*IT.java</exclude>
</excludes>
</configuration>
</plugin>
<!-- Failsafe Plugin for integration tests in verify phase -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.5</version>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
</plugin>
<!-- Jacoco plugin for generate report in verify phase -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
package com.javarush.khmelov.cmd;
package com.javarush.chebotarev.cmd;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;

import java.util.stream.Collectors;
import java.util.stream.Stream;

public interface Command {
public abstract class Command {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Методы doGet и doPost возвращают строку с путем. Было бы более наглядно использовать перечисления (Enum) для навигации. [INFO]


default String doGet(HttpServletRequest request) {
public String doGet(HttpServletRequest req, HttpServlet servlet) {
return getView();
}

default String doPost(HttpServletRequest request) {
public String doPost(HttpServletRequest req) {
return getView();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Логика преобразования CamelCase в kebab-style может быть упрощена с использованием регулярных выражений Java 21. [INFO]


default String getView() {
protected String getView() {
String simpleName = this.getClass().getSimpleName();
return convertCamelCaseToKebabStyle(simpleName);
return "/" + convertCamelCaseToKebabStyle(simpleName);
}

private static String convertCamelCaseToKebabStyle(String string) {
Expand All @@ -32,6 +33,4 @@ private static String convertCamelCaseToKebabStyle(String string) {
? snakeName.substring(1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вспомогательный метод преобразования строк стоит вынести в отдельный утилитный класс, чтобы не загромождать базовый абстрактный класс. [INFO]

: snakeName;
}


}
32 changes: 32 additions & 0 deletions src/main/java/com/javarush/chebotarev/cmd/ContinueQuest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.javarush.chebotarev.cmd;

import com.javarush.chebotarev.component.Attribute;
import com.javarush.chebotarev.component.Go;
import com.javarush.chebotarev.component.Utils;
import com.javarush.chebotarev.quest.CurrentQuest;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

@SuppressWarnings("unused")
public class ContinueQuest extends Command {

@Override
public String doGet(HttpServletRequest req, HttpServlet servlet) {
HttpSession currentSession = req.getSession();
CurrentQuest currentQuest = Utils.extractAttribute(
currentSession,
Attribute.CURRENT_QUEST,
CurrentQuest.class
);
String view;
if (!currentQuest.isStarted()) {
view = Go.NEW_QUEST;
} else if (!currentQuest.isDone()) {
view = Go.QUEST;
} else {
view = Go.RESULT;
}
return view;
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/javarush/chebotarev/cmd/Editor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.javarush.chebotarev.cmd;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.javarush.chebotarev.component.Go;
import com.javarush.chebotarev.component.ObjectRepository;
import com.javarush.chebotarev.component.QuestService;
import com.javarush.chebotarev.quest.Quest;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;

@SuppressWarnings("unused")
public class Editor extends Command {

@Override
public String doGet(HttpServletRequest req, HttpServlet servlet) {
return Go.EDITOR;
}

@Override
public String doPost(HttpServletRequest req) {
try {
req.setCharacterEncoding("UTF-8");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кодировка UTF-8 жестко захардкожена. Рекомендуется использовать StandardCharsets.UTF_8.name(). [INFO]

ObjectMapper mapper = ObjectRepository.find(ObjectMapper.class);
Quest quest = mapper.readValue(req.getReader(), Quest.class);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чтение всего тела запроса через getReader() и маппинг в объект может упасть с ошибкой памяти при очень больших файлах квестов. [WARNING]

QuestService questService = ObjectRepository.find(QuestService.class);
questService.saveQuest(quest);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Результат сохранения квеста игнорируется. Если saveQuest возвращает путь к файлу, возможно, его стоит использовать для логирования или информирования пользователя. [INFO]

} catch (IOException e) {
throw new RuntimeException(e);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оборачивание IOException в RuntimeException без контекста затрудняет отладку. Рекомендуется использовать пользовательские исключения или информативные сообщения. [WARNING]

}
return Go.ROOT;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/javarush/chebotarev/cmd/MainMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.javarush.chebotarev.cmd;

import com.javarush.chebotarev.component.*;
import com.javarush.chebotarev.quest.QuestMetadata;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

import java.util.List;

@SuppressWarnings("unused")
public class MainMenu extends Command {

@Override
public String doGet(HttpServletRequest req, HttpServlet servlet) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод не содержит логирования. Для Senior уровня важно отслеживать действия пользователя (например, вход в главное меню). [INFO]

HttpSession currentSession = req.getSession();
QuestService questService = ObjectRepository.find(QuestService.class);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поиск сервиса через ObjectRepository в каждом вызове метода doGet создает избыточную нагрузку. Рекомендуется использовать Dependency Injection. [WARNING]

List<QuestMetadata> availableQuests
= questService.obtainAvailableQuests(servlet.getServletContext());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод obtainAvailableQuests может быть тяжелым (чтение с диска). Его стоит кэшировать или выполнять асинхронно. [WARNING]

currentSession.setAttribute(Attribute.AVAILABLE_QUESTS, availableQuests);
Statistics statistics = Utils.tryExtractAttribute(
currentSession,
Attribute.STATISTICS,
Statistics.class
);
if (statistics == null) {
statistics = new Statistics();
currentSession.setAttribute(Attribute.STATISTICS, statistics);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Инициализация нового объекта статистики в контроллере нарушает принцип единственной ответственности (Single Responsibility). [WARNING]

}
return getView();
}
}
42 changes: 42 additions & 0 deletions src/main/java/com/javarush/chebotarev/cmd/NewQuest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.javarush.chebotarev.cmd;

import com.javarush.chebotarev.component.*;
import com.javarush.chebotarev.quest.CurrentQuest;
import com.javarush.chebotarev.quest.Quest;
import com.javarush.chebotarev.quest.QuestMetadata;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("unused")
public class NewQuest extends Command {

@Override
@SuppressWarnings("unchecked")
public String doGet(HttpServletRequest req, HttpServlet servlet) {
HttpSession currentSession = req.getSession();
int selectedQuestIndex = getSelectedQuestIndex(req);
List<QuestMetadata> availableQuests = Utils.extractAttribute(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Список доступных квестов извлекается из сессии. Если сессия истекла или атрибут не установлен, возникнет ошибка при получении по индексу. [ERROR]

currentSession,
Attribute.AVAILABLE_QUESTS,
ArrayList.class
);
QuestService questService = ObjectRepository.find(QuestService.class);
QuestMetadata selectedQuest = availableQuests.get(selectedQuestIndex);
Quest quest = questService.loadQuest(
selectedQuest,
servlet.getServletContext()
);
CurrentQuest currentQuest = new CurrentQuest(quest);
currentSession.setAttribute(Attribute.CURRENT_QUEST, currentQuest);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Объект currentQuest сохраняется в сессию под ключом CURRENT_QUEST, перезаписывая старый прогресс без предупреждения пользователя. [WARNING]

return getView();
}

private int getSelectedQuestIndex(HttpServletRequest req) {
String questIndexString = req.getParameter(Parameter.QUEST_INDEX);
return Integer.parseInt(questIndexString);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод Integer.parseInt может выбросить NumberFormatException, если параметр отсутствует или не является числом. Следует добавить валидацию входных данных. [ERROR]

}
}
45 changes: 45 additions & 0 deletions src/main/java/com/javarush/chebotarev/cmd/NextStage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.javarush.chebotarev.cmd;

import com.javarush.chebotarev.component.*;
import com.javarush.chebotarev.quest.CurrentQuest;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

@SuppressWarnings("unused")
public class NextStage extends Command {

@Override
public String doGet(HttpServletRequest req, HttpServlet servlet) {
HttpSession currentSession = req.getSession();
CurrentQuest currentQuest = Utils.extractAttribute(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переменная currentQuest извлекается из сессии, но не проверяется на null перед использованием. [ERROR]

currentSession,
Attribute.CURRENT_QUEST,
CurrentQuest.class
);
int nextNodeId = getSelectedNextNodeId(req);
currentQuest.nextStage(nextNodeId);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вызов currentQuest.nextStage(nextNodeId) изменяет состояние объекта. В многопоточной среде (сервлеты) это может привести к race condition, если объект в сессии не синхронизирован. [ERROR]

String view;
if (currentQuest.isDone()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод isDone() используется для определения перехода на страницу результатов. Логика завершения квеста должна быть инкапсулирована внутри CurrentQuest. [INFO]

view = Go.RESULT;
Statistics statistics = Utils.extractAttribute(
currentSession,
Attribute.STATISTICS,
Statistics.class
);
if (currentQuest.isVictory()) {
statistics.incVictories();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обновление статистики напрямую в сессионном объекте. Рекомендуется выделить логику обновления статистики в отдельный сервис. [WARNING]

} else {
statistics.incDefeats();
}
} else {
view = Go.QUEST;
}
return view;
}

private int getSelectedNextNodeId(HttpServletRequest req) {
String nextNodeIdString = req.getParameter(Parameter.NEXT_NODE_ID);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Имя параметра NEXT_NODE_ID лучше вынести в константу класса Parameter для исключения опечаток. [INFO]

return Integer.parseInt(nextNodeIdString);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Прямое приведение строки из параметров запроса к числу без проверки на null или формат является потенциальным источником RuntimeException. [ERROR]

}
}
Loading