Archiwa tagu: Java

Wyrażenia regularne w Javie – figle i psikusy

To trzeci (i na jakiś czas ostatni) z serii wpisów na temat wyrażeń regularnych w Javie, po O wyrażeniach regularnych. Podstępna różnica pomiędzy find i matches oraz Wyrażenia regularne dla nieprogramistów. Historycznie ten jest najwcześniejszy – to zaktualizowana wersja tekstu z mojego poprzedniego bloga.

Poruszam tu bardzo podstawowe kwestie, które potrafią jednak dać się we znaki. Rozwiązanie tych paru niewinnych problemików kosztowało mnie niemało czasu i nerwów. Liczę, że kiedy jakaś zbłąkana dusza znajdzie się w tej samej sytuacji, Google zaprowadzi ją prosto w moje troskliwe ramiona.

Artykuł powstał podczas mojej pracy nad narzędziem do przekształcania metadanych.

Psikus 1: znaki ucieczki w wyrażeniach wczytywanych z zewnętrznego pliku

Załóżmy, że wyrażenie (które chcemy wczytać z zewnętrznego pliku) w zwykłym kodzie Javy wygląda tak:

Linia przedstawia zakres wieków, do którego dopasuje się na przykład linia:

Proste, prawda?

Prawda – ale do czasu. Problemy pojawiły się, kiedy zaczęłam wczytywać wyrażenia regularne zapisane w zewnętrznym pliku. Wczytywałam między innymi następujący fragment:

Wszystko przestało działać. Łańcuchy znaków, które bez najmniejszych wątpliwości powinny były dopasować się do wyrażenia, przechodziły niezauważone. Po godzinie analiz niebezpiecznie zbliżałam się do stanu, w którym myślałam, że oszalał albo świat dokoła mnie. Wtedy na szczęście nadeszła pora lunchu. Opowiedziałam o problemie nad talerzem naleśników, a jeden z kolegów zadał oczywiste w sumie pytanie – czy na pewno dobrze wyeskejpowałam (przepraszam!!!) wszystkie znaki specjalne. I wtedy wreszcie nadeszło olśnienie: nie, nie zrobiłam tego dobrze. Przeeskejpowałam je.

Znaki specjalne, takie jak d, w wyrażeniu regularnym oznaczające cyfrę, należy poprzedzić tzw. symbolem ucieczki, czyli w tym wypadku backslashem (ukośnikiem wstecznym). W łańcuchach znaków w kodzie Javy konieczne jest wprowadzenie dodatkowego backslasha, gdyż musimy jeszcze odebrać specjalne znaczenie samemu backslashowi (musimy poprzedzić znak ucieczki znakiem ucieczki…). Tyle razy widziałam te dwa ukośniki w parze, że zupełnie zapomniałam o tym, że w zewnętrznym pliku należy użyć tylko jednego!

Psikus 2: flagi

To bardzo proste. Załóżmy, że wyrażenie nie ma brać pod uwagę wielkość liter. Normalnie oznaczamy to tak:

Świetnie, tylko jak przekazać tę flagę, jeśli wyrażenie jest wczytywane z zewnątrz? Wychodzi na to, że flagę, poprzedzoną znakiem zapytania, należy umieścić w nawiasie na początku wyrażenia. Ignorowanie wielkości liter (przy okazji, poznałam ostatnio nowe słowo – kasztowość) to literka i, zatem dodajemy (?i). Ostatecznie, w kodzie wyrażenie wygląda tak:

a poza kodem, z pojedynczymi ukośnikami, tak:

Psikus 3: String.replaceAll

W pewnym brzegowym przypadku mój kod, w wyniku wczytania wyrażeń regularnych z pliku, wykonywał operację, którą można w uproszczeniu zapisać tak:

Po wykonaniu się tego kodu spodziewałam się, że treść zostanie całkowicie zastąpiona, czyli wartością s będzie:

skoro * jest zachłannym kwantyfikatorem, to .* powinno dopasować się do całego napisu niezależnie od okoliczności.

Wyobraźcie sobie moje zaskoczenie (czy raczej przerażenie), gdy okazało się, że s przyjęło wartość:

Próbowałam użyć jeszcze bardziej zaborczego kwantyfikatora *+, ale efekt był ten sam. Byłabym mniej zaskoczona, gdyby .* zostało dopasowane do każdej litery w łańcuchu. Jakim cudem dopasowało się dokładnie dwa razy?

Dalsze śledztwo wykazało, że przebieg akcji jest następujący:

  1. Cały łańcuch dopasowuje się do .* i jest zamieniany na łańcuch „nowa treść”.
  2. Po dopasowaniu z oryginalnego łańcucha znaków nie zostaje nic, a raczej zostaje łańcuch "". Metoda replaceAll jeszcze raz sprawdza możliwość dopasowania i okazuje się, że "" także pasuje do .*, zatem pusty łańcuch również zostaje wymieniony.
  3. Zasadniczo można by kontynuować i w nieskończoność dodawać na końcu "nowa zawartość", jednak na szczęście (?) dana pozycja w łańcuchu znaków jest traktowana jako sprawdzona i wykonanie metody kończy się.

Pozdrawiam siostry i braci w cierpieniu.

O wyrażeniach regularnych. Podstępna różnica pomiędzy find i matches

Wyrażenia regularne to jedno z zagadnień dzielących programistów. Są tacy, którzy je uwielbiają i ci, którzy szczerze ich nienawidzą.

Sama zaliczam się do pierwszej grupy, być może z powodu upodobań lingwistycznych (wyrażenia regularne ≈  języki regularne ≈ automaty skończone). Wyrażenia regularne pozwalają na bardzo zwięzły i precyzyjny zapis warunków wyszukiwania, które – gdyby ograniczyć się do tradycyjnych konstrukcji zawartych w danym języku programowania – mogłyby zająć wiele linii. Po stronie minusów należy zapisać trudność zrozumienia dłuższych wyrażeń regularnych, zwłaszcza, jeśli są dziełem kogoś innego.

W tym wpisie chcę podzielić się swoją niedawną przygodą optymalizacyjną. W najbliższym czasie (czyli przed końcem roku) wrzucę jeszcze stary tekst o pułapkach, w które można wpaść pisząc wyrażenia regularne w Javie oraz nowy o tym, jak regeksy mogą przydać się nieprogramistom w ich codziennych zadaniach.

Do rzeczy.

Napisałam ostatnio kod analizujący treść stron internetowych. Mniejsza z tym, po co to robił. Stanowił część większej całości (moduł mavenowy) i podobnie jak reszta kodu korzystał m.in. z Javy 8 i biblioteki JSoup. Napisałam masę testów jednostkowych, testowałam na kilkuset stronach… Jednak prawdziwa próba ognia miała nadejść kilka dni po oddaniu przeze mnie projektu, podczas uruchomienia pełnego łańcucha przetwarzania na kilkudziesięciu (kilkuset?) tysiącach stron.

W pięciu przypadkach mój kod zawiesił się na ponad dobę.

Naprawa, kiedy już do niej zasiadłam zajęła mi niecałą godzinę. Co się okazało? W kilku miejscach w moim kodzie użyłam metody Element::getElementsByAttributeValueMatching i dałam się zwieść nazwie. Na swoją obronę mam to, że Javadoc też wprowadza w błąd. Otóż wyobraziłam sobie, że gdzieś w głębinach metoda wywołuje metodę Matcher::matches, dlatego szukany przeze mnie fragment otoczyłam znakami ".*" przed i po właściwym wzorcu.

Skoro już wszystko się zapętliło, zajrzałam do środka, a tam:

Podstawowa różnica pomiędzy wspomnianą już Matcher::matches a Matcher::find jest taka, że matches szuka pełnych dopasowań (cały łańcuch znaków musi pasować do wzorca), a find zadowoli się dopasowaniem w środku łańcucha znaków (i może takich dopasowań zwrócić wiele).

Czyli jeśli nasz wzorzec ma postać "[abc]e", to find dopasuje go do łańcucha "cel", w przeciwieństwie do metody matches:

Jeśli dodam na początku i na końcu wzorca uniwersalne symbole ".*" (uzyskując ".*[abc]e.*") to również metoda matches uzna, że mamy dopasowanie:

Jednak jednak użyjemy metody find przy odpowiednio złożonym i zagnieżdzonym wyrażeniu regularnym z opcjonalnymi elementami i przy odpowiednio długim tekście, pojawi się tyle możliwości dopasowania wyrażenia do łańcucha znaków, że wejdziemy na obszar tzw. Catastrophic Backtracking (katastrofalne nawracanie?).

Moja poprawka sprowadziła się, oczywiście, do usunięcia czterech znaków: ".*" z początku i końca wyrażenia przekazanego jako argument metodzie Element::getElementsByAttributeValueMatching.

Jaka z tego nauczka? Rutynowo zaglądać w cudzy kod, niestety.

PS. Polecam krzyżówki regeksowe: Regex Crossword.

Maven ➝ Pivotal. Automatyczne wdrożenie w chmurze

Pisałam już o tym, jak uruchomić aplikację w chmurze Pivotal oraz jak podłączyć do niej bazę danych. W tym odcinku pokażę, jak ułatwić sobie życie i bez wysiłku wdrażać nowe wersje aplikacji zaraz po ich poprawnym zbudowaniu. Wykorzystam do tego plugin Cloud Foundry Maven (Cloud Foundry ≈ Pivotal).

Cel

Wdrożenie nowej wersji w chmurze po wydaniu jednego polecenia. Spoiler – w tym wypadku będzie to:

Realizacja celu

Plugin w pom.xml

Pierwszy tutorial, jak znalazłam w archiwach Cloud Foundry, twierdził, że pluginu nie ma w głównym repozytorium Mavena – konieczne miało być dodanie wpisu w sekcji pluginRepositories pliku pom.xml. Od tego czasu sytuacja uległa zmianie (https://mvnrepository.com/artifact/org.cloudfoundry/cf-maven-plugin) i nie trzeba już zawracać sobie tym głowy.

Konfiguracja pluginu w pom.xml

W moim wypadku wygląda tak:

Czym jest mycloudfoundry-instance? O tym poniżej.

Dane logowania

Dane logowania można podać w konsoli (ale wtedy nie będzie to wdrożenie za pomocą jednego polecenia!), w treści pom.xml (ale wtedy trafią one do systemu kontroli wersji, być może publicznego!), albo zdefiniować je w przeznaczonym dla Mavena pliku settings.xml na własnym dysku. Więcej o samym pliku settings.xml można poczytać tutaj. Szukać go należy (lub utworzyć) na jednej z dwóch ścieżek:

  • ${maven.home}/conf/settings.xml (dla całej instalacji Mavena)
  • ${user.home}/.m2/settings.xml (per użytkownik)

U mnie wpis wygląda tak:

id to nadana przeze mnie nazwa, za pomocą której mogę odwołać się do tej konfiguracji w pliku pom.xml (linia 6 w listingu z konfiguracją).

Więcej informacji

Więcej informacji na temat korzystania z pluginu można znaleźć na stronie https://docs.run.pivotal.io/buildpacks/java/build-tool-int.html.

Napotkane problemy

Wcześniej musiałam tylko raz wyklikać powiązanie pomiędzy aplikacją a usługą MongoDB (mLab). Przy wdrożeniach przez konsolę cf konfiguracja ta była zachowywana. Dlatego początkowo pominęłam definicję serwisów w konfiguracji pluginu (linie 13-21). Błąd! – baza danych była przez to „odczepiana” od aplikacji przy wdrożeniu.

Kod

Jak zwykle w GitHub.

PS.

Wiedziałam, że po zakończeniu konkursu Daj się poznać wrócę do swojego projektu, ale nie byłam pewna, czy chcę na ten temat blogować. Co mnie przekonało? To, że kiedy zapomniałam, jakie czynności należy wykonać w celu podłączenia bazy danych do aplikacji w chmurze, nie musiałam szukać daleko. Zerknęłam jedynie we własne wcześniejsze teksty! W ten sposób zostałam swoim najwdzięczniejszym czytelnikiem.

Strumienie w Javie 8

Odkrywam na poważnie Javę 8. Jestem tak zachwycona strumieniami, że aż się muszę podzielić!

Przykład (Java 8)

Zacznę od przykładu. Napisałam ostatnio taki kod:

Co robi ten fragment? Zwraca tablicę zawierającą te klucze z mapy, których wartości są większe od 0.

Dokładniej:

  1. Odwołuje się do obiektu mapy (map).
  2. Pobiera zbiór par klucz-wartość przechowywany w tej mapie (entrySet()).
  3. Zamienia ten zbiór na strumień, albo, poprawniej, odczytuje ten zbiór poprzez strumień (stream()).
  4. Filtruje strumień, pozostawiając w nim (a właściwie w nowym strumieniu) tylko te pary klucz-wartość, w których wartość jest większa od zera. Do filtrowania używana jest lambda, czyli definiowana w miejscu anonimowa funkcja zwracająca wartość typu boolean, przekazana jako parametr (.filter(entry -> entry.getValue() > 0)).
  5. Na wszystkich elementach (typu Map.Entry) pozostałych w strumieniu wykonywana jest funkcja zwracająca klucz (.map(entry -> entry.getKey())).
  6. Na samym końcu wywoływana jest metoda toArray z interfejsu Stream, do której przekazujemy generator (toArray(String[]::new)), czyli funkcję, dzięki której strumień wie, ile pamięci zaalokować. Istnieje bezargumentowa wersja tej metody, tyle że przy jej użyciu otrzymamy tablicę obiektów typu Object.

Przykład (Java 7 i starsze)

Po napisaniu tych paru linijek (ciągle mam problemy z formatowaniem takiego kodu i liczeniem linii) zaczęłam się zastanawiać, jak wyglądałby ten kod w wersji przedstrumieniowej. Musiałoby to być coś takiego:

Porównanie

Kilka różnic:

Java 7 Java 8
Definicja zadania Algorytm / opis procedury Strumień przetwarzania / deklaracja oczekiwanego wyniku
Kolejność przetwarzania Sekwencyjna Nieokreślona – może zostać poddana optymalizacji
Typy danych W niektórych miejscach, jak pętla for, trzeba je powtórzyć Kompilator jest w stanie je wywnioskować, nie trzeba ich powtarzać (np. w definicjach lambd)
Układ kodu Kod z blokami i wcięciami Łańcuch wywołań (oczywiście w przypadku bardziej złożonych lambd również trzeba stosować bloki)

Uczciwie dodam, że po stronie minusów stosowania strumieni muszę na razie dopisać debugowanie. W przypadku wystąpienia problemu, odrobinę trudniej jest mi się zorientować, co dokładnie poszło nie tak.

Czy naprawdę nie było innego wolnego słowa?

Strumieni z Javy 8 (z pakietu java.util.stream) nie należy mylić ze strumieniami służącymi do obsługi wejścia i wyjścia (z pakietu java.io). Co za geniusz wymyślił tę nazwę – nie wiem. Być może chodziło i uniknięcie jeszcze gorszego słowa „monada”.

Jeśli chcesz wyszukać w Google informacji na temat „nowych” strumieni, używaj nazwy „Java 8 Streams”.

Co się dzieje w tle?

Strumień nie przechowuje własnych danych – można uznać go za „widok” na dane pochodzące z kolekcji lub innego źródła. Podczas potokowego przetwarzania danych na przykład za pomocą kilku kolejnych operacji map, strumień nie jest modyfikowany, tylko zwracany jest wtedy nowy strumień, oferujący inne dane.

Wniosek

Wygodne. Używać.

Kiedy po raz pierwszy, w ramach prezentacji na Poznań JUG, zobaczyłam strumienie, nie byłam zachwycona. Pomyślałam sobie, że to kolejny wynalazek, który świetnie działa na wymyślonych, zabawkowych przykładach, a który okazuje się bezużyteczny w prawdziwym świecie. Nic bardziej mylnego, w pracy korzystam z nich teraz regularnie.

PS. Sklejanie Stringów

Od lat jeden z najbardziej irytujących braków w bibliotece standardowej Javy to sklejacz łańcuchów znaków. Teraz, jeśli chcesz za pomocą np. średników połączyć słowa przechowywane w kolekcji, nie już musisz pisać specjalnej funkcji ani załączać zewnętrznych bibliotek. Można tak:

Aktywacja konta poprzez email

Poprzedni wpis poświęciłam kwestii wysyłania maili przez aplikację napisaną w Javie.

Kontynuując, teraz chcę upewnić się, że świeżo zarejestrowany użytkownik to człowiek (a nie bot) i że podał poprawny adres email. Poniżej opisuję kroki prowadzącego do tego celu.

Krok 1: nieaktywny użytkownik z wygenerowanym kodem

Po wypełnieniu formularza rejestracyjnego obiekt reprezentujący użytkownika jest zapisywany w bazie danych jako nieaktywny. Razem z nim zapisywany jest losowo wygenerowany kod do aktywacji konta. Na tym etapie użytkownik nie może się logować, nie zna też przypisanego mu kodu.

Krok 2: email z linkiem aktywacyjnym

Aplikacja wysyła na adres podany przez użytkownika wiadomość zawierającą link aktywacyjny. Kod wygenerowany losowo w poprzednim kroku jest doklejony do adresu jako parametr URL.

Krok 3: obsługa linku aktywacyjnego

Wejście pod adres podany w mailu powoduje uruchomienie servletu weryfikacyjnego. Aplikacja szuka w bazie danych obiektu użytkownika, któremu przypisano kod podany jako parametr. Jeśli taki użytkownik istnieje, jest oznaczany jako aktywny. Od tej chwili może się już logować, ponieważ potwierdzono (czy raczej zwiększono prawdopodobieństwo), że to rzeczywista osoba.

Krok 4: usunięcie nieaktywnych kont

Należałoby usunąć zbyt długo przechowywane nieaktywne konta. Ale jeszcze się za to nie zabrałam 🙂

Ciekawostka: wyszukanie użytkownika w bazie

Oto ilość pracy, którą włożyłam w wyszukanie użytkownika w bazie danych MongoDB po wartości confirmationId:

Resztę robi za mnie Spring Data, parsując nazwę metody!

Więcej informacji na ten temat można znaleźć tutaj.

PS.

To ostatni techniczny wpis w ramach konkursu Daj się poznać 2016. Jutro postaram się napisać krótkie podsumowanie. O ile wieczorem na spotkaniu młodych mam nie poleje się za dużo wina.

 

Jak wysłać maila z aplikacji webowej w Javie?

Po co mi maile?

Warunkiem utworzenia konta użytkownika jest podanie poprawnego adresu email, do którego dany użytkownik rzeczywiście ma dostęp.

W kolejnym wpisie omówię dokładniej proces weryfikacji. Na teraz istotne jest to, że moja aplikacja musi być w stanie wysłać do użytkownika maila z linkiem do uaktywnienia konta.

Jak to zrobić?

Korzystam z frameworka Spring Boot (czyli właściwie z nakładki ułatwiającej korzystanie z frameworka Spring zgodnie z zasadą „konwencja ponad konfiguracją”).

Kroki do realizacji mojego celu są następujące:

1. Dodaję zależność w pliku pom.xml:

2. Piszę kod tworzący i wysyłający wiadomości:

3. Ustawiam właściwości aplikacji

Co może pójść nie tak?

com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first.

Koniecznie jest nadanie wartości true właściwości spring.mail.properties.mail.smtp.starttls.enable, a najlepiej całej trójce kończącej powyższy listing.

javax.mail.AuthenticationFailedException: 534-5.7.9 Application-specific password required.

To akurat sprawa typowa dla serwisu GMail. Jeśli chcesz, żeby aplikacja łączyła się z kontem email i w Twoim imieniu wysyłała maile, musisz stworzyć specjalne hasło aplikacji, różne od hasła prywatnego. W każdej chwili możesz podejrzeć listę aplikacji, które korzystają z takiego hasła (na stronie https://security.google.com/settings/security/apppasswords), a także dowolną z nich zablokować.

Google uprzejmie zwraca uwagę, że nadal nie jest to najbezpieczniejsze rozwiązanie.

com.sun.mail.util.MailConnectException: Couldn’t connect to host.

Ten błąd pojawił się, kiedy przełączyłam konto z GMaila na adres email na mojej wirtualnej instancji obsługującej kilka domen. Początkowo myślałam, że Java Mail nie chce pracować z „wirtualnymi” adresami obsługiwanymi przez serwer SMTP w innej domenie. Ostatecznie okazało się jednak, że przyczyną błędu był nadmiarowy biały znak w pliku application.properties 🙂

Ciekawostka: tymczasowe maile

Wiele aplikacji wymusza na użytkowniku podanie działającego adresu email. Główne powody to:

  • zwiększenie prawdopodobieństwa, że mamy do czynienia z człowiekiem, a nie robotem,
  • umożliwienie spamowania 🙂
  • zabezpieczenie użytkownika przed literówką w adresie email, która mogłaby doprowadzić nawet do utraty konta w aplikacji (brak możliwości zresetowania hasła bez działającego adresu).

Wiele osób tworzy sobie dodatkowe konto email, które używane jest do rejestracji w mniej istotnych serwisach (sama mam osobne konto do zakupów internetowych). Zdarza się jednak, że adresu email żąda strona lub aplikacja, z którą na pewno już nigdy nie będziesz mieć nic wspólnego. Co wtedy zrobić?

Możesz założyć tzw. jednorazowe (ang. disposable) konto email, np. w serwisie Guerilla Mail. Poczta, która tam trafia, jest przechowywana przez godzinę niezależnie od tego, czy ktoś ja odczyta, czy nie.

Ocenę moralną pozostawiam Czytelnikowi.

Zapisuję dane użytkowników w bazie i robię głupie błędy podczas walidacji formularza

Zapis danych użytkowników w MongoDB

O MongoDB pisałam już w dwóch wcześniejszych postach: Jak zacząć z MongoDB? i MongoDB + Spring Data.

Dzisiaj postanowiłam zaimplementować wreszcie zapis danych z formularza użytkownika w mojej nierelacyjnej (dokumentowej) bazie danych.

W poprzednim tekście pokazałam, jakich adnotacji należy użyć w klasie User, żeby jej instancje zostały poprawnie zapisane w bazie danych. Żeby faktycznie zapisać dane z formularza, musiałam tylko dopisać kilka linii w kodzie kontrolera:

Co zrobić z hasłami?

Zależało mi na spełnieniu trzech warunków:

  1. Użytkownik podaje hasło dwa razy, żeby zmniejszyć ryzyko literówki.
  2. Wpisywane hasło jest niewidoczne.
  3. Hasło w bazie danych nie jest przechowywane w postaci jawnej.

Dwa hasła

Dodałam następujące pola do klasy reprezentującej użytkownika:

Specjalnie zaznaczyłam adnotację @Transient. Dzięki jej użyciu wskazane pola (w tym wypadku hasła zapisane jawnym tekstem) nie zostaną zapisane w bazie danych.

Ukrycie literek

Pola musiałam również dopisać do formularza. Użyłam pola input w wersji password, żeby zamiast liter wyświetlić gwiazdki (czy tam kropki).

Funkcja hashująca

Wreszcie, hasła. Nie powinny być przechowywane w bazie danych w postaci jawnej. Powodów jest wiele. Na przykład taki: ludzie często używają tych samych haseł w wielu miejscach. Gdyby administrator bazy danych miał wgląd w hasła do aplikacji, mógłby podjąć próbę zalogowania się na konta użytkowników w innych serwisach (np. gmail) przy użyciu tego samego hasła i adresu email.

Oprócz pokazanych już dwóch pól do wprowadzenia hasła roboczego, klasa User przechowuje jeszcze następujące dwie wartości:

passwordSalt to losowa wartość doklejana do hasła przed jego „zaszyfrowaniem” (dzięki niej w bazie danych nie będzie widać, że dwa użytkownicy przypadkowo użyli tego samego hasła), passwordEncrypted to wynik szyfrowania.

Losowanie i szyfrowanie realizuje następujący pomocniczy serwis, wywoływany z kontrolera dzięki adnotacji @Autowired:

Kontroler wygląda tak:

Błąd 1: strona błędu zamiast komunikatu o błędzie w formularzu

Podanie niepoprawnych danych w formularzu z nieznanego mi powodu zaczęło przenosić mnie na moją własną stronę błędu (zamiast wyświetlenia komunikatu przy polu formularza, które spowodowało problem).

Co się okazało?

W pewnym momencie zmieniłam sygnaturę metody kontrolera z:

na:

ponieważ chciałam dopisać informacje do modelu.

Okazuje się, że parametr reprezentujący BindingResult musi następować bezpośrednio po parametrze, przez który przekazywany jest dotyczący go obiekt (co ma sens, gdy jeden formularz obsługuje wiele obiektów).

Formularz zaczął działać poprawnie po zamianie kolejności parametrów:

Błąd 2: Error during execution of processor ‚org.thymeleaf.spring4.processor.attr.SpringErrorsAttrProcessor’

Kolejny błąd zaskoczył mnie, kiedy testowałam sytuację, w której użytkownik wpisze dwa różne hasła. Okazało się, że poczyniłam złe założenia na temat parametrów metody rejectValue, za pomocą której chciałam przekazać do formularza informację o wykryciu problemu. Napisałam:

zakładając, że drugi parametr to komunikat o błędzie. Tymczasem – to kod błędu. Komunikat można przekazać dopiero w trzecim parametrze (poprawny kod widać na listingu z metodą processPasswords). Błąd wynikał zatem z braku komunikatu o błędzie, który był wymagany w szablonie.

Wreszcie działa

Kod jest dostępny w moim repozytorium GitHub.

Komunikat o niepasujących do siebie hasłach
Komunikat o niepasujących do siebie hasłach
Wyświetlenie informacji o pierwszym lepszym użytkowniku z bazy
Wyświetlenie informacji o pierwszym lepszym użytkowniku z bazy

 

Jak zabezpieczyć strony generowane przez Spring Boot Actuator?

W mojej aplikacji webowej korzystam z biblioteki Actuator, która daje dostęp do masy pożytecznych informacji na temat działania aplikacji (w czasie rzeczywistym).

Wystarczy dodać w pom.xml następujący wpis:

Po uruchomieniu aplikacji uzyskuję dostęp do wielu stron ze szczegółami działania aplikacji. Przykładowo, pod adresem /health znajdę podstawowe informacje o aktualnym statusie aplikacji:

Raport na temat stanu zdrowia aplikacji
Raport na temat stanu zdrowia 🙂

Wszystko pięknie, tyle tylko, że nie chcę dzielić się takimi danymi z przypadkowymi (lub, co gorsza, nieprzypadkowymi) użytkownikami Internetu.

Pokazywałam już (we wpisie Spring Boot: Bezpieczeństwo 101) jak oznaczyć strony, które mają być wyświetlane tylko użytkownikom o statusie administratora. Problem z Actuatorem polega na tym, że generowane przez tę bibliotekę adresy nie mają wspólnego rdzenia. Mamy np.: /env, /metrics, /trace. Ich wymienianie jest nieco upierdliwe!

Wtem! Wyczytałam (w Spring Boot in Action), że można dodać tym stronom wspólny przedrostek za pomocą wpisu w pliku application.properties:

A następnie zabezpieczyć dostęp do stron na tej ścieżce:

I voilà! Po zalogowaniu się jako administrator widzę:

Raport o stanie zdrowia wyświetlam teraz pod innym, wspólnym adresem
Raport o stanie zdrowia wyświetlam teraz pod innym, wspólnym adresem

 

MongoDB + Spring Data

W poprzednim wpisie pokazałam, jak dodawać dokumenty i kolekcje do nierelacyjnej bazy MongoDB oraz jak połączyć się z tą bazą z kodu javowego.

W tym odcinku pokażę, jak łatwo utrwalić w MongoDB obiekty przetwarzane w aplikacji webowej.

Wpis w pom.xml

Kolejny raz korzystam ze „startera” Spring Boot, który grupuje w sobie kilka podstawowych bibliotek realizujących spójny cel – w tym wypadku mapowanie, zapis i odczyt danych z MongoDB:

Adnotacje w obiekcie, który ma zostać utrwalony w bazie danych

Do obiektu reprezentującego użytkownika bazy danych dodałam następujące adnotacje:

  • @Document oznacza, że mamy do czynienia z obiektem zapisywanym w bazie danych. Jest to odpowiednik @Entity w wersji Spring Data dla relacyjnych baz danych.
  • @Id to automatycznie generowany, obowiązkowy identyfikator dokumentu w ramach kolekcji w MongoDB.
  • Pola nieopisane zostaną bezpośrednio odwzorowane w dokumencie w bazie danych.
  • @DBRef oznacza odwołanie do innej kolekcji w bazie. Uwaga! Nie wolno tworzyć odwołań obustronnych, MongoDB brzydko się przez nie zapętli. Zamiast tego należy po jednej stronie jawnie zapisać identyfikator dokumentu.

Interfejs do obsługi repozytorium

Muszę tylko zasygnalizować, że będę chciała pobierać z bazy obiekty danego typu:

Użycie w kodzie: kontroler i widok

Kontroler

W celu przetestowania konfiguracji dodałam odwołanie do kolekcji użytkowników do mojego powitalnego kontrolera GreetingController:

Nazwa pierwszego napotkanego użytkownika zostanie dodana do modelu.

Widok

W powitalnym szablonie Thymeleaf dopisałam kod, który wyświetli nazwę odnalezionego w bazie danych użytkownika, o ile taki użytkownik istnieje:

Efekt

Wyświetlona informacja pobrana z bazy danych
Wyświetlenie informacji pobranej z bazy danych. Przecinek od czapy.

Zapis informacji w bazie

Mogę skorzystać ze sposobu pokazanego w poprzednim wpisie. Mogę też przygotować kod, który będzie wywoływany przy każdym uruchomieniu aplikacji:

Konfiguracja połączenia z bazą danych

Tym razem oparłam się na wartościach domyślnych. Nie wpisałam w application.properties żadnych danych na temat połączenia z bazą danych. Oczywiście musiałam uruchomić mój serwer MongoDB.

Wszystko zadziałało.

Mogę podejrzeć zastosowane wartości właściwości związanych z MongoDB dzięki dołączonej w pom.xml bibliotece Spring Boot Actuator. Pod adresem http://localhost:8080/configprops widzę m.in. takie dane:

Test to baza danych, którą utworzyłam na potrzeby poprzedniego wpisu.

Troubleshooting: co zrobiłam nie tak przy pierwszym podejściu?

  • Zapomniałam o wpisaniu @Autowired w kontrolerze w punkcie 4 (komunikat CrudRepository ... is no accessor method!)
  • Nie włączyłam MongoDB (Timed out after 10000 ms while waiting for a server that matches AnyServerSelector{} ... caused by {java.net.ConnectException: Connection refused: connect}})

Jak zacząć z MongoDB?

Wprowadzenie

Najwyższa pora dodać do mojej konkursowej aplikacji jakąś bazę danych. Od początku zakładałam, że z powodów edukacyjnych użyję bazy nierelacyjnej. Zdecydowałam się w końcu na MongoDB. Jest to jedna z popularnych baz nierelacyjnych obsługiwanych przez mojego dostawcę PaaS (Platform as a Service), tj. Pivotal Web Services.

Źródła

Swoją działalność w tym obszarze opieram na następujących źródłach:

Pierwsze kroki

Po instalacji serwera bazodanowego wykonuję następujące kroki:

  1. Uruchamiam proces serwera MongoDB:
  2. Przygotowuję rekord reprezentujący użytkownika mojego systemu (plik user.json). Rekordy w MongoDB mają postać dokumentów w standardzie JSON (w przybliżeniu). Wartościami pól mogą być również inne dokumenty oraz tablice (w tym tablice dokumentów). Dokumenty są przechowywane w ramach kolekcji. Kolekcja przypomina tablicę w relacyjnej bazie danych, tyle że zawarte w niej dokumenty wcale nie muszą mieć tej samej struktury. Dokumenty w kolekcji muszą mieć klucz główny o nazwie _id:
  3. Importuję rekord użytkownika do bazy danych:
  4. Dodaję sterownik MongoDB jako zależność w mavenowym pliku pom.xml.
  5. Łączę się z bazą danych z kodu w Javie:
  6. Wyszukuję użytkowników:
  7. Zmieniam wartość jednego z pól użytkownika (dokument d pełni tu rolę wzorca, według którego odnajdywany jest rekord z bazy):
  8. Nakładam indeks na nazwy użytkowników:

No i działa 🙂

Cały plik jest dostępny w GitHub.

Co dalej?

  1. Na serio dodać to do aplikacji 🙂
  2. Zastanowić się nad użyciem Hibernate.
  3. Zastanowić się, jak przechowywać zdjęcia.