Wydział Matematyki i Informatyki Uniwersytetu Łódzkiego www.math.uni.lodz.pl/ radmat radmat@math.uni.lodz.pl
JSTL JavaServer Pages Standard Library (JSTL) jest pewnym rozszerzeniem JSP. Z technicznego punktu widzenia JSTL można traktować jako ustandaryzowany zbiór dodatkowych funkcjonalności JSP. JSTL to z punktu widzenia Jave Enterpise Edition zbiór bibliotek, które można importować do stron JSP za pomocą dyrektywy taglib. Każda taka biblioteka zawiera zbiór znaczników, które udostępniają dodatkową funkcjonalność. Funkcjonalność ta nie jest dostępna w obrębie standardowych akcji, dyrektyw i innych wyrażeń, używanych w standardowej technologii JSP. Biblioteki, które wchodzą w skład JSTL najczęściej są rozróżniane za pomocą prefiksów, czyli oznaczeń używanych w kodzie JSP do odróżnienia znaczników pochodzących z różnych przestrzeni nazw XML. Np. za pomocą prefiksu JSP odróżniamy znacznik akcji JSP < jsp : include > od znacznika języka HTML < a >.
Prefiksy bibliotek: c (core - korzeń) - biblioteka o największym znaczeniu, zawierająca największą ilość istotnych akcji fmt (format) - biblioteka zawiera akcje, które pozwalają na formatowanie dat, liczb, walut, itp. sql - biblioteka pozwala na wykonywanie zapytań do bazy danych xml - biblioteka pozwala na przetwarzanie danych w formacie XML fn (functions) - biblioteka zawiera funkcje operujące m.in. na ciągach znaków, które mogą być wykorzystywane w wyrażeniach EL lub znacznikach
Akcja c:out Akcja c:out odpowiedzialna jest za wyświetlanie wartości wyrażeń. Na pierwszy rzut oka wydaje się, że akcja ta jest zupełnie zbędna, gdyż do wyświetlenia dowolnych wyrażeń wystarczy po prostu umieścić je na stronie. Jak się okazuje, twórcy biblioteki JSTL uwzględnili kilka istotnych aspektów, które uzasadniają potrzebę istnienia akcji c:out: <c:out value="${osoba.pesel}" default="brak numeru PESEL" />
Zauważmy, że jeśli wartość wyrażenia znajdującego się w atrybucie value przyjmie wartość null, to wówczas wyświetlona zostanie wartość domyślna. Oczywiście walidację numeru PESEL powinniśmy przeprowadzić już na etapie wprowadzania danych. Jeśli jednak dopuścimy możliwość niewprowadzania tego numeru, dobrze jest posłużyć się akcją c:out. Wartość domyślną można podać także wewnątrz znacznika c <c:out value="${osoba.pesel}"> Brak numeru PESEL </c:out>
Drugi atrybut znacznika c, który odróżnia go od zwykłego wyświetlania tekstu ma bardzo istotne znaczenie dla bezpieczeństwa aplikacji. Jednym z bardziej popularnych rodzajów ataków związanych z aplikacjami webowymi są ataki typu XSS - Cross-Site Scripting, czyli skrypty międzystronicowe. Atak taki polega na wprowadzeniu do formularza na danej stronie mieszanki kodu HTML i JavaScript, a następnie zapisaniu go do bazy danych. Przy późniejszym wyświetleniu tego kodu, może on wykorzystywać skrypty z innych stron, co w bardzo dużym stopniu ułatwia działania crackerów. Zabezpieczenie przed tego typu atakami jest proste i polega na uniemożliwieniu bezpośredniego wyświetlania niepożądanego kodu przez przeglądarkę.
Istnieje kilka możliwości rozwiązania tego problemu. Jednym z nich jest usuwanie znaczników lub zamiana ich na encje jeszcze przed zapisaniem do bazy. Istnieje pięć znaków, które dla języka XHTML mają specjalne znaczenie i które powinny być zamieniane na encje. Są to: Znak Encja < < > > & & ' " Przeglądarka internetowa po napotkaniu któregoś z wyżej wymienionych znaków jedynie go wyświetla i w żaden sposób nie interpretuje. Mechanizm zamiany znaków na encje spowoduje, że wpisanie złośliwego kodu nie spowoduje jego wykonania. Zostanie on jedynie wyświetlony przez przeglądarkę.
Znacznik c:set Jak pamiętamy akcja jsp:setproperty umożliwiała ustawianie właściwości ziaren Javy. Jednak nie zawsze atrybuty zasięgów (strony, żądania, sesji, czy kontekstu) są ziarnami. Z tego właśnie względu twórcy biblioteki JSTL dodali znacznik, który umożliwia ustawienie wartości typów prymitywnych (łańcuchów znaków), jak też własności ziaren. Ze względu na różnicę w działaniu znacznik c:set udostępnia dwa warianty obsługi. Wspólnym elementem są atrybuty: value - za pomocą którego przekazujemy wartość dla elementu scope - za pomocą którego określamy zasięg ustawianego atrybutu
Jeśli chcemy ustawić wartość prymitywną, musimy określić jedynie atrybut var, w którym przekazujemy nazwę atrybutu, który chcemy ustawić. Na przykład: <c:set var="preferowanyjezyk" scope="session" value="${param.jezyk}" /> W ten sposób wartośc formularza o nazwie jezyk zostanie skopiowana do atrybutu sesji o nazwie preferowanyjezyk. Atrybut ten jest oczywiście zwykłą zmienną typu String.
Akcję c:set można wykorzystać także do modyfikacji bardziej złożonych obiektów, takich jak ziarna, czy mapy. Składnia akcji jest następująca: <c:set target="${sessionscope.osoba}" property="pesel" value="${param.pesel}" /> W tej akcji zmieniamy właściwość pesel przechowywanego w sesji obiektu osoba. Wykorzystujemy tutaj pole formularza o nazwie pesel. Zauważmy, że przy określaniu obiektu, który chcemy modyfikować, przekazujemy bezpośrednio ten obiekt, a nie jego nazwę.
Akcja c:remove Akcja c:remove wykorzystywana jest do usunięcia elementu. Teoretycznie do usunięcia elementu można użyć akcję c:set ustawiając wartość null. Jednak akcja c:set - zgodnie z logiką - powinna służyć jedynie do ustawiania wartości. Składnia akcji c:remove jest następująca: <c:remove var="osoba" scope="session" /> Atrybut scope można oczywiście pominąć. Wówczas usuwanie odbędzie się zgodnie z kolejnością przeszukiwania zasięgów.
Instrukcje warunkowe i pętle Do tej pory wszystkie tworzone przez nas aplikacje webowe wyświetlały informacje od początku do końca. Dzięki JSTL w bardzo prosty sposób można np. uzależniać wyświetlanie danego znacznika w zależności od wartości wyrażenia.
Akcja c:if Akcja c:if stanowi bezpośredni odpowiednik instrukcji warunkowej if, znanej z języka Java. Przyjmuje tylko jeden obowiązkowy atrybut - test, który powinien zawierać wyrażenie EL. Zwraca oczywiście prawdę, bądź fałsz. Akcja c:if nie udostępnia klauzuli else! W obrębie akcji c:if istnieją dwa atrybuty: var i scope, które pozwalają na zapisanie wyniku warunku (pochodzącego z atrybutu set) do określonego atrybutu var, w określonym zasięgu scope. Wówczas: <c:if test="${sessionscope.zalogowany!= null}"> Witaj ${sessionscope.zalogowany.login} </c:if>
Akcja c:choose W przeciwieństwie do akcji c:if, która nie odzwierciedlała całkowicie możliwości tradycyjnej instrukcji warunkowej, akcja c:choose jest kompletnym odpowiednikiem instrukcji switch. Co więcej, ta instrukcja realizuje tradycyjną funkcjonalność instrukcji if...else, a dzięki różnorodności wyrażeń EL, również poszczególne przypadki akcji c:choose mogą być bardziej rozbudowane, niż w przypadku instrukcji switch. Składnia jest następująca:
<c:choose> <c:when test="${sessionscope.koszyk == null}"> Koszyk nie istnieje. </c:when> <c:when test="${empty sessionscope.koszyk}"> Koszyk jest pusty. </c:when> <c:when test="${!(empty sessionscope.koszyk)}"> Koszyk nie jest pusty. </c:when> </c:choose>
Wewnątrz akcji c:choose (która sama nie przyjmuje żadnych wartości) można umieszczać tylko dwa rodzaje akcji - c:when i c:otherwise. Znacznik otherwise nie przyjmuje żadnych atrybutów. Umieszczona w nim treść zostanie wykonana tylko w przypadku, gdy żaden z warunków umieszczonych w akcjach c:when nie zostanie spełniony. Jest to więc odpowiednik słowa kluczowego default. W przeciwieństwie do default, akcja c:otherwise musi być umieszczana na samym końcu akcji c:choose: <c:choose> <c:when test="${!(empty sessionscope.osoba.pesel)}"> Twój PESEL to: ${sessionscope.osoba.pesel} </c:when> <c:otherwise> Nie podałeś numeru PESEL. </c:otherwise> </c:choose>
Akcja c:foreach Na poziomie strony JSP nie ma znaczenia, czy dane pochodzą z bazy danych, czy pliku XML. W każdym przypadku mamy do czynienia z jakąś kolekcją (tablicą, listą, mapą). Istotne jest m.in. w jaki sposób te dane zostaną wyświetlone. Akcja c:foreach stanowi bezpośredni odpowiednik pętli for (Object o:kolekcja). Akcja ta przyjmuje dowolny rodzaj kolekcji, a następnie wykonuje kod tyle razy, ile elementów znajduje się w kolekcji. Rozważmy przykład abstrakcyjnego koszyka. Ów koszyk jest listą obiektów typu Produkt. Produkt jest z kolei klasą-ziarnem, która zawiera właściwości takie jak nazwa, opis i cena.
<c:foreach var="prod" items="${sessionscope.koszyk}"> <p>${prod.nazwa}</p> <p>${prod.opis}</p> <p>${prod.cena}</p> </c:foreach> Dwa kluczowe atrybuty to: var, który określa nazwę zmiennej, która będzie używana wewnątrz akcji c:foreach items, w którym umieszcza się wyrażenie EL, które zwraca kolekcję danych
Nieco inaczej wygląda sytuacja w przypadku map. Często bowiem zdarza się, że klucze danej mapy są tak samo ważne, jak ich wartości. Z tego względu iteracja po elementach mapy wygląda następująco: <c:foreach var="element" items="${mapa}"> ${element.key}:${element.value} </c:foreach> W ten sposób wyświetlimy klucze i odpowiadające im wartości w dowolnej mapie.
Często przy wyświetlaniu elementów list istnieje potrzeba ich ponumerowania. Do tego celu służy atrybut status. <table> <c:foreach var="element" items="${sessionscope.koszyk}" status="licznik"> <tr> <td>${licznik.count}.</td><td>${element.nazwa}</td> </tr> </c:foreach> </table>
Czasami nie ma potrzeby iterowania po jakiejś kolekcji, lecz jedynie po liczbach. Składnia wówczas jest następująca: <c:foreach var="numer" begin="1" end="10" step="1"> ${numer} </c:foreach>
Akcja c:url Jak wiadomo, adresy URL w obrębie aplikacji są relatywne, tzn. adres /sklep/strony/strona.jsp jest tłumaczony na http://serwer.pl/sklep/strony/strona.jsp. Problem może pojawić się w przypadku, gdy klient zechce zmienić ścieżkę kontekstu ze /sklep np. na /MojSklep. Wówczas istniałaby konieczność zamiany adresów występujących w całej aplikacji. Zamiast podawać adresy URL w znacznikach a, img i innych, można skorzystać z następującego rozwiązania: <c:url value="/strony/strona.jsp" var="adresglownej" /> <a href= <c:out value="${adresglownej}" /> >Główna</a>
Akcja c:url pozwala m.in. na podawanie adresów relatywnych bez ścieżki kontekstu. Ścieżka ta jest dynamicznie pobierana z ustawień aplikacji i dołączana do powstałego adresu URL. Po wygenerowaniu, adres strony głównej jest umieszczny w atrybucie strony adresglownej. Wówczas wystarczy umieścić adres w atrybucie href znacznika a. W atrybucie href korzystamy z cudzysłowów pojedynczych, ze względu na konieczność stosowania cudzysłowów podwójnych w akcji c:out.
Jednym z największych problemów występujących w przypadku tworzenia adresów URL, jest umieszczanie parametrów żądania. Są one umieszczne za właściwą częścią adresu URL, która zawiera ścieżkę do zasobu internetowego, np.: http://serwer.pl/sklep/strony/strona.jsp?imie=jan&nazwisko=kowalski Problemy mogą się pojawić w momencie, gdy wartości parametrów zawierają nietypowe znaki (np. niealfanumeryczne). Akcja c:url udostępnia mechanizm eleganckiego deklarowania parametrów: <c:url value="/strony/strona.jsp" var="adresglownej" > <c:param name="imie" value="jan" /> <c:param name="nazwisko" value="kowalski" /> </c:url> <a href= <c:out value="${adresglownej}" /> >Głowna</a>
Akcja c:import Akcja c:import pozwala na umieszczanie na stronie JSP teści, które pochodzą z zewnętrznych źródeł w podobny sposób, jak miało to miejsce w przypadku akcji jsp:include. Najprostsza składnia jest następująca: <c:import url="strona.jsp" /> Akcja c:import pozwala na importowanie zarówno elementów wchodzących w skład tej samej aplikacji webowej, jak i dowolnych elementów, które są dostępne za pomocą adresu URL (czego nie da się zrealizować za pomocą akcji jsp:include). Zatem poprawne jest odwołanie <c:import url="http://innyserwer/strona.jsp" />
Akcja c:import umożliwia także przekazywanie parametrów: <c:import url="strona.jsp" /> <c:param name="login" value="login" /> <c:param name="haslo" value="haslo" /> </c:import>
Czytniki i łańcuchy Czytniki i łańcuchy pozwalają na tymczasowe przechowywanie treści pochodzących z zewnętrznych, zaimportowanych źródeł. Wariant łańcuchowy polega na zapisaniu treści do atrybutu o podanej nazwie i zasięgu: <c:import url="http://innyserwer/waluty.txt" var="kursy walut" scope="page" /> W powyższy sposób można zapisać informacje w atrybucie - nie tylko strony, czy żądania, ale także sesji, a nawet kontekstu.
Czytnik jest obiektem klasy pochodzącej od klasy Reader, czyli strumieniem znaków: <c:import url="http://innyserwer/waluty.txt" varreader="czytnik" /> <... input="${czytnik}" /> </c:import>
Funkcja fn:indexof Funkcja fn:indexof zwraca indeks wystąpienia jednego łańcucha znaków w innym. Jeśli podłańcuch nie wystąpi w łańcuchu, to zwracana jest wartość zero. W przeciwnym przypadku zwracany jest indeks, który określa pierwszy znak podłańcucha.
Funkcja fn:join Funkcja fn:join łączy elementy tablicy tekstów w jeden ciąg z wykorzystaniem separatorów, np.: Zalogowany: ${fn:join(sessionscope,";")}
Funkcja fn:replace Funkcja fn:replace zamienia jeden tekst innym w wejściowym łańcuchu znaków: ${fn:replace("kotekot","kot","pies")} Wyświetlony zostanie ciąg piesepies.
Funkcja fn:substring Funkcja fn:substring zwraca wybrany fragment wejściowego łańcucha znaków: ${fn:substring("serwery aplikacji",0,4)} Wyświetlony zostanie ciąg serw.
Uwaga Podstawową różnicą między funkcjami, a akcjami JSTL jest sposób ich umieszczania. Akcje wstawiane są jako zwykłe znaczniki, zgodne ze standardem XML. Funkcje wykorzystują wyrażenia EL.