Moved to Spring Ver: https://github.com/AppLoidx/helios-rest-api-spring
Arthur Kupriyanov
- VK: @apploidxxx
- Github: @AppLoidx
- Mail: [email protected]
API docs v1 : Веб-версия идентичная с README.md
Перед запуском необходимо настроить hiberante.cfg.xml
mvn install
java -jar target/fileName.jar
Этот API должен быть доступным для пользования всеми разработчиками, в том числе для создания собственных сервисов, интеграции с социальными сетями и тому подобным. Также следовательно он должен иметь методы аутентификации и идентификации пользователей через протокол OAuth.
Документацию по использованию API и примеры можно найти здесь
Реализован JAX-RS через Jersey и поднят на сервере GrizzlyHttpServer от Apache. Задеплоен в сервисе heroku
Сборка производится через maven с системой автоматической сборки на travis-ci
com.apploidxxx
:
api
- классы и методы отвечающие за обработку запросовexceptions
- пользовательские исключенияfilter
- фильтры запросовmodel
- POJO объекты
ds
- средства инициализации datasource (HibernateSession)entity
- хранимые объекты (persistence units)dao
- расположение всех DAO и их сервисов
test
:
api
- тесты по API (запросы через rest-assured)entity
- тесты по логике работы хранимых объектов
resources/static
:
external
- формы для авторизации\регистрации через OAuth
- Интерфейс для всех исключений выбрасываемых в работе с API и которые могут генерировать
Response
для отправки клиенту.
import javax.ws.rs.core.Response;
public interface IResponsibleException {
Response getResponse();
String getErrorMessage();
String getErrorDescription();
}
- Прослойка между интерфейсом
IResponsibleException
и самими реализациями исключений, которая наследуется отException
и является абстрактной:
public abstract class ResponsibleException extends Exception implements IResponsibleException { }
Наследование исключения от этого класса (его реализация IResponsibleException
) дает нам возможность использовать (вместе с try/catch
конструкциями) такого вида упрощения обработки ошибок:
Листинг 1.1 QueueApi
public Response getQueue( @Valid@NotNull@QueryParam("queue_name") String queueName){
try {
Queue q = QueueManager.getQueue(queueName);
return Response.ok(q).build();
} catch (InvalidQueueException e) {
return e.getResponse();
}
}
Таким образом, исключение умеет генерировать Response
, который можно возвращать пользователю. Вот его реализация:
Листинг 1.2 InvalidQueueException
public class InvalidQueueException extends ResponsibleException{
@Override
public Response getResponse() {
return Response
.status(Response.Status.NOT_FOUND)
.entity(new ErrorMessage(getErrorMessage(), getErrorDescription()))
.build();
}
@Override
public String getErrorMessage() {
return "invalid_queue";
}
@Override
public String getErrorDescription() {
return "queue not found";
}
}
Здесь представлены POJO объекты, которые по назначению должны сериализовываться в json. Все имена полей я буду приводить как они написаны в самом коде (то есть в верблюжем стиле), но все они при сериализации меняют свой проперти на стиль json, то есть this_is_my_value
Объект являющийся стандартным для отправки ответа запросам, которые вызывали ошибку или были некорректны. Содержит поля error
и errorDescription
Объект содержащий поле exist
. Обычно используется для CheckApi
, например, для проверки существования такого имени пользователя или очереди.
Класс-враппер для сущности User. Вот его поля:
private User user;
private List<String[]> queues;
@JsonProperty("queues_member")
private List<String[]> queuesMember;
@JsonProperty("swap_requests")
private List<Map<String, String>> swapRequests;
- queues - коллекция из массива очередей, содержащих два значения - короткое имя (ссылка) и полное имя очереди. Например,
{["shortLink","My Queue Fullname" ]}
. Этот список содержит очереди, в которых пользователь находится как участник или администратор. - queuesMember - такой же список как
queues
, но содержит очереди, в которых пользователь является лишь участником (не входят очереди, в которых он администратор) - user - данные о пользователе, кроме приватных данных, как например, пароль
- swapRequests - список мапов описывающих заявки на обмен местами (пока только исходящих). Метод добавляющий значение в
swapRequest
:
Листинг 2.1 UserInfoprivate void addSwapRequest(Queue queue, User user){ SwapContainer sc = queue.getSwapContainer(); User target = sc.getSwapRequest(user); if (target != null) { Map<String, String> hashMap = new HashMap<>(); hashMap.put("queue_name", queue.getName()); hashMap.put("queue_fullname", queue.getFullname()); hashMap.put("target_username", target.getUsername()); hashMap.put("target_firstname", target.getFirstName()); hashMap.put("target_lastname", target.getLastName()); swapRequests.add(hashMap); } }
Реализация паттерна фабрики - имеет статические методы возвращающие объекты Response
с тем или иным кодом ошибки и как говорилось ранее, объектом ErrorMessage
в качестве ответа пользователю:
Листинг 2.2 ErrorResponseFactory
public abstract class ErrorResponseFactory {
public static Response getInvalidParamErrorResponse(String description){
return Response
.status(Response.Status.BAD_REQUEST)
.entity(new ErrorMessage("invalid_param", description))
.build();
}
public static Response getForbiddenErrorResponse(String description){
return Response
.status(Response.Status.FORBIDDEN)
.entity(new ErrorMessage("insufficient_rights", description))
.build();
}
public static Response getForbiddenErrorResponse(){
return getForbiddenErrorResponse("You don't have enough rights");
}
...
}
Класс для проверки и валидации паролей. Использует MD5 хэширование. Имеет два публичных метода:
Листинг 2.3 Password
public static String hash(String password){
return Md5Crypt.md5Crypt((password + salt).getBytes());
}
public static boolean isEqual(String rawPassword, String hashedPassword){
return hashedPassword.equals(hash(rawPassword, hashedPassword));
}
Собственно, hash
хэширует пароль для хранения в БД, а isEqual
проверяет совпадение паролей.
Это обертки над QueueService
и UserService
, которые работают с сущностями БД. Они имеют методы, которые могут выкидывать специфичные исключение, как например, InvalidQueueException
или UserNotFoundException
.
Листинг 2.4 QueueManager
public class QueueManager {
public static Queue getQueue(String queueName) throws InvalidQueueException {
Queue q = QueueService.findQueue(queueName);
if (q == null){
throw new InvalidQueueException();
} else {
return q;
}
}
}
Проверяет строковые данные на уязвимости и наличие опасных значений, таких как <script>
. Имеет один метод checkWord
, который в случае опасности выбрасывает исключение VulnerabilityException
.
В этом пакете находятся DAO-объекты, при этом каждого такого объекта область видимости ограничивается лишь пакетом, а публичный доступ дается через сервисный класс (подробнее далее)
Класс который реализует простые операции с сущностями и БД, чтобы не писать много boilerplate-кода он был вынесен в отдельный класс и используется с обобщениями:
public class DAOBasicOperations<T> {
public void save(T obj) {
Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
Transaction tx1 = session.beginTransaction();
session.save(obj);
tx1.commit();
session.close();
}
public void update(T obj) {
Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
Transaction tx1 = session.beginTransaction();
session.update(obj);
tx1.commit();
session.close();
}
public void delete(T obj) {
Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
Transaction tx1 = session.beginTransaction();
session.delete(obj);
tx1.commit();
session.close();
}
}
Рассмотрим его использование на примере:
Листинг 3.1 MessageDAO
public class MessageDAO {
private DAOBasicOperations<Message> basicOperations = new DAOBasicOperations<>();
Message findById(Long id) {
return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Message.class, id);
}
void save(Message user) {
basicOperations.save(user);
}
void update(Message user) {
basicOperations.update(user);
}
void delete(Message user) {
basicOperations.delete(user);
}
List<Message> findAll() {
try (Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession()){
return session.createQuery("from Message", Message.class).list();
}
}
}
Сущность | Назначение |
---|---|
User | Данные о пользователе |
ContactDetails | Дополнительные данные о пользователе (email, vk id ...) |
Chat | Чат очереди (one-to-one Queue ) |
Message | Сообщение в чате (one-to-one Chat ) |
Queue | Информация об очереди |
Session | Сессия с пользователем (accessToken, refreshToken) |