Използване на Java Reflection API за мързелива “документация” на response кодове

В единия от страничните проекти използваме REST за комуникация между фронтенда (Vue) и бекенда (Spring Boot). Приложението е сравнително просто и пиша бекенда сам и за документация на REST-а използвам Swagger. Това значително услеснява имплементацията на фронтенда откъм информация за endpoint-и и кой какво може да очаква като заявка/отговор.

Тъй като в процеса на работа постоянно изникват нови възможни отговори за грешка, които касаят само клиента, счетох за ненужно (i.e. домързя ме) да създавам нови Exception-и за всяка.

Механизмът, на който се спрях за връщане на грешките към потребителския интерфейс, е сравнително прост и не е нищо ново – при грешка в бекенда към клиента се връща DTO с цифров код и пояснителен текст. Пояснителният текст служи само за улеснение на пишещия фронтенда. Локализацията на грешките, ако се наложи да има такава, ще се случва във Vue приложението според цифровия код.

В Spring това се имплементира изключително лесно. При неправилно действие (няма достъп, липсва задължително поле и т.н.) се хвърля Exception (в случая – ErrorResponseException), който се прихваща от @ExceptionHandler и към клиента се връща DTO-то с нужния код и текст. За имплементацията на този механизъм са нужни няколко основни неща:

  • Номенклатура на кодовете на грешки и клас, описващ формата на отговора, който очаква Vue приложението,
  • Някой да прихване Exception-а,
  • Exception за прихващане.

Номенклатурата е обикновено POJO със статични методи за всяка грешка:

import lombok.Data;

@Data
@AllArgsConstructor
public class ErrorResponseDTO {

    private Integer code;
    private String message;

    // 500
    public static ErrorResponseDTO generalError(Throwable t) {
        return new ErrorResponseDTO(500, "I can't even: " + t.getMessage());
    }

    // 1401 - JWT token expired
    public static ErrorResponseDTO tokenExpired() {
        return new ErrorResponseDTO(1401, "Token expired");
    }

    // 1402 - Invalid JWT token
    public static ErrorResponseDTO invalidToken() {
        return new ErrorResponseDTO(1402, "Invalid token");
    }

    // ...
 
    // 1409 - User is not active
    public static ErrorResponseDTO userIsNotActive() {
        return new ErrorResponseDTO(1409, "User is not active");
    }

    // ...
    // Още много статични методи, описващи останалите грешки
    // ...
}

Горното може да се реализира по много начини. Засега това ме устройва. Не смятам, че проектът ще стане достатъчно сложен, за да оправдае разделянето на тази номенклатура от DTO-то или категоризиране на грешките в отделни класове, пакети, Exception-и и т.н.

Прихващането на Exception-а става с @ControllerAdvice и @ExceptionHandler:

@ControllerAdvice
public class RestExceptionHandler {
    // ...
    @ExceptionHandler(ErrorResponseException.class)
    @ResponseStatus(HttpStatus.PRECONDITION_FAILED)
    @ResponseBody
    public ErrorResponseDTO processErrorResponseException(ErrorResponseException 
e) {
        return e.getErrorResponseDTO();
    }
    // ...
    // Други handler-и за други exception-и
    // ...
}

Exception-ът е обикновен наследник на RuntimeException, който съдържа в себе си DTO-то:

public class ErrorResponseException extends RuntimeException {

    private ErrorResponseDTO errorResponseDTO;

    public ErrorResponseException(ErrorResponseDTO errorResponseDTO) {
        this.errorResponseDTO = errorResponseDTO;
    }

    public ErrorResponseDTO getErrorResponseDTO() {
        return errorResponseDTO;
    }
}

С описаното дотук връщането на грешка става просто. Например, в Service-а за генериране на JWT токен има простата проверка дали конкретният потребител е активен. Ако не е, няма смисъл да му генерирам токен:

if (userData.getUserStatus() != UserStatus.ACTIVE) {
    throw new ErrorResponseException(ErrorResponseDTO.userIsNotActive());
}

Така при неактивен потребител към извикващия клиент се връща този JSON:

{
    "code": 1409,
    "message": "User is not active"
}

Логиката за потребителския интерфейс е изцяло в клиентското приложението. Дали ще се покаже директно върнатият текст или според кода на грешката ще се случи нещо, няма значение. Целта е пълно отделяне на логиката по обработване на грешката (ако така се превежда decoupling). Няма значение дали клиентът е Vue, Android или Swing приложение – отговорността е негова.

Всичко е хубаво до момента, в който не се наложи друг човек да работи с това API и се започне едно питане “Ама тоя код какво означава”. Най-добрият вариант, разбира се, е да има подробна документация с всички кодове и условията, при които се връщат, но в конкретния случай това не е оправдано. А и ако не бях мързел, нямаше да съм програмист.

Можеше вместо рефлексия да използвам анотациите, предоставени от Swagger за този случай (@ApiResponses и @ApiResponse) върху всеки метод на всеки контролер в приложението.

Понеже рефлексията ми е интересна, се спрях на вариант с endpoint, който връща JSON с всички възможни грешки, които могат да се върнат към REST клиента. Тъй като всички грешки са дефинирани като статични методи на класа, който ги описва, алгоритъмът е тривиален:

  1. Вземам всички статични методи, декларирани в този клас,
  2. Викам ги един по един,
  3. Записвам резултатите в списък,
  4. Връщам списъка.
@RestController
@Slf4j
public class ErrorListController {

    @GetMapping(path = "/errors-list")
    public List<ErrorResponseDTO> getErrorsList() {
        List<ErrorResponseDTO> result = new ArrayList<>();
        
        // Взимаме всички методи
        Method[] methods = ErrorResponseDTO.class.getMethods();
        for (Method method : methods) {
            try {
                // Искаме само статичните методи
                if (Modifier.isStatic(method.getModifiers())) {
                    Method declaredMethod = ErrorResponseDTO.class.getDeclaredMethod(method.getName());

                    // Викаме метода
                    ErrorResponseDTO invoke = (ErrorResponseDTO) declaredMethod.invoke(null);

                    // Добавяме резултата към отговора
                    result.add(invoke);
                }
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                log.debug("Ignoring exception \"{}\": {}", e.getMessage(), e);
            }
        }

        return result;
    }
}

Адресът на този контролер (/errors-list) е разрешен в WebSecurityConfigurerAdapter-а само при определени условия и не се вижда в “продукционната” среда. Отговорът изглежда долу-горе така:

[
  {
    "code": 1401,
    "message": "Token expired"
  },
  {
    "code": 1402,
    "message": "Invalid token"
  },
  {
    "code": ...,
    "message": "..."
  },
  {
    "code": 1409,
    "message": "User is not active"
  }
]

Така има винаги актуален списък с цялата номенклатура на възможните кодове на грешки. Смятам, че с малко повече заигравка ще мога да докарам и информация откъде може да се върне даден код за грешка, но към момента това ми върши работа.

Програма за решаване на IIОЗГ за телефон

Преди време писах програма за решаване на втора основна задача в геодезията. Оказа се, че, макар и проста, е доста полезна за някои неща. Бая хора я натеглиха. Не знам колко са успели да я подкарат, което ме навежда на мисълта, че може би трябва да я пренапиша на PHP или C++ (което едва ли ще стане скоро).

Всичкото хубаво, ама като ми трябва и на училище, какво да правя? Да си я напиша и за телефона, разбира се. За има-няма половин час беше готова първата версия. За първи път работя с J2ME с такава цел и разбрах, че е доста скопена платформа. Няма DecimalFormat, заради което числата са толкова дълги. Ще се налага сами да си ги закръгляте. Не ми харесват възможностите за закръгляне, а да си играя да мисля алгоритми за тов, само забавя нещата. В този си вид ми е достатъчна. Освен DecimalFormat няма и Math.atan(), без което не може. Него се наложи да си го пиша сам.

За да тръгне, телефонът трябва да поддържа MIDP-2.0. Пробвана е на SEK500i, Nokia6120, LGKE970 и емулатора на J2ME, все без грешка.
С две думи, работи на почти всичко от 2004 досега:)
Ако някой пък види зор и не може да я подкара, да пише, никакъв проблем не е да се прекомпилира за друг модел.

Download .jar (~10KB) // .jad

Как се пуска това?
Качвате .jar-a на телефона (и .jad, ако трябва), инсталирате, ако е нужно и това е.

Как да си пусна java програма?

Е, писна ми разни хора да ме питат как да си подкарат разни java програми. Ето го обяснението на нереално сложния процес на подкарване на програма, писана на java: Continue reading “Как да си пусна java програма?”

Програма за решаване на втора основна задача в геодезията [обновена + версия за Android]

GeoStuff е програма за решаване на основни геодезически задачи за Windows и Android.

Windows



Текущата версия на програмата за Windows е 1.1.0 (от 19.03.2016) и може да се свали от тук (.zip, ~45KB)

Програмата използва .NET Framework 4.5 и би трябвало да тръгне на всеки нормален компютър с Windows 7 и нагоре. Ако не тръгне, трябва да се изтегли .NET Runtime от сайта на Microsoft.

Версия 1.0, използваща Java и Swing, все още може да се свали от тук (.rar, ~50KB). Тази версия работи на всички компютри с инсталирана Java Runtime Environment (JRE) без значение от операционната система.

Android

Програмата има и версия за Android, която дава възможност за решаване на двете основни задачи и права засечка.

Get it on Google Play

 

 

 

Повече информация и последни версии ще има на страницата на приложението: тук

Безполезна програма за генериране на честоти с java

messingfrequencies

Ето това е труда на 5-минутно умуване и игра с java-та. “Програмата генерира честота със зададено цяло число. Времетраенето е от 0.000… до 1 секунда (някога може да си поиграя да го направя и повече). Няма ограничение за честотата. И докато сме на темата за честотите, веднага заявявам, че не поемам никаква отговорност за повреди, нанесени от използването на “програмата”. Като бонус чертае и почти вярна графика на сигнала, който излъчва. Това може да намери приложение като програма за тестване на големи високоговорители (особено интересно е при 2 херца), за досаждане на съседите и др. Download

padnote-0.6b

Ето пак…в последно време имам много свободно време и го използвам за писане на безсмислени/безполезни програми на java…

padnote…защо точно padnote? Защото се мъча да напиша клонинг на notepad. Защо, след като има хиляди такива и аз да пиша? Ами…Защото мога! Ето я и новата 0.6b версия…цък

padnote-06b

CHANGELOG:

  • сложих “overwrite it?” диалог
  • оправих NullPointerException-а, който се появаваше като се опитам да запазя празен файл
  • добавих font chooser
  • добавих word wrap
  • добавихcopy/paste/select all menus
  • добавих шорткъти към менютата

TODO:

  • Да пренапиша кода, да го подредя и да го пусна в нетя да се чете
  • Да му пусна един javadoc
  • Да пренапиша FontChooser-а, защото кода му не е 100% мой
  • Да сложа search/replace
  • Да я регистрирам под GPL
  • Да я регистрирам в sourceforge и/или някой друг подобен сайт…

Е, това е за сега, ако някога съм в добро java настроение, ще я допиша и може би ще и сложа български.

padNote-0.1

Q:Какво правят хората на св. Валентин?
A: Обичат се…

Q: Какво правя аз на св. Валентин?
A: Пиша java…

padnote-01Днешната “програма” е notepad clone без никакви функции освен отваряне, редактиране и записване на текст във файлове. Ако някога имам повече време, може би ще я допиша и ще изкарам и втора версия…както и да е…може да се свали от , а сега мога само да си напиша TODO листа…

да добавя опции (шрифт и някакви други ненужни работи)
да сложа about/help работите
да пренапиша целия код, да го подредя
да се наспя

Java writing is fun…

Java Certificate

Днес случайно намерих един сайт, в който след като се направи тест по даден език получаваш сертификат или нещо подобно 🙂

Та си викам: “Що да не го направя и аз?” И така…направих го…
Оказа се, че само аз и още някой сме правили този тест и аз съм се справил по добре хъх 8)

Ето какво има в профила, който си създадох…

:-)