dareks_

  • Dokumenty2 821
  • Odsłony706 435
  • Obserwuję402
  • Rozmiar dokumentów32.8 GB
  • Ilość pobrań345 635

Software Developers Journal 2014 01

Dodano: 5 lata temu

Informacje o dokumencie

Dodano: 5 lata temu
Rozmiar :7.6 MB
Rozszerzenie:pdf

Software Developers Journal 2014 01.pdf

dareks_ CZASOPISMA
Użytkownik dareks_ wgrał ten materiał 5 lata temu. Od tego czasu zobaczyło go już 28 osób, 28 z nich pobrało dokument.

Komentarze i opinie (0)

Transkrypt ( 25 z dostępnych 40 stron)

Java SE 8 MARCIN MICHALSKI Java SE 8 jest w środowisku programistów Java najbardziej oczekiwaną wersją platformy od 2004 roku. Otrzymamy nie tylko nowe funkcjonalności i poprawki API, ale wprowadzone zostaną również nowe elementy składni języka Java. Przyjrzyjmy się bliżej nowej wersji Java SE 8! Injection po raz kolejny… Marek Puchalski Open Web Application Security Project (OWASP) jest społecznością skupiającą osoby, instytucje i organizacje zainteresowane poprawą bezpieczeństwa aplikacji webowych.W ramach realizowanych przez OWASP projektów tworzone są narzędzia, dokumenty i procesy wspomagające pracę przy tworzeniu systemów. Dobrze o tym pamiętać, aby w różnych sytuacjach nie wynajdywać koła na nowo, tym bardziej że oferowane zasoby wspomagają pracę nie tylko deweloperów i architektów, lecz również testerów i menadżerów projektów. Strefy czasowe w Javie i bazach danych? Piotr Wirkus Jak pamiętamy ze szkoły podstawowej, Ziemia obraca się wokół własnej osi, co powoduje występowanie po sobie pory dnia i pory nocy. Raz na dobę ustawienie promieni słonecznych jest w zenicie (najwyższym punkcie względem horyzontu), co potocznie nazywamy „południem”. Wehikuł czasu w komputerze, czyli o systemach kontroli wersji na przykładzie Subversion Sławomir Andrzejewski Artykuł przybliża Czytelnikowi koncepcje i strategie ozwoju oprogramowania przy wykorzystaniu systemów kontroli wersji. Oprócz zagadnień teoretycznych zaprezentowano podstawowe scenariusze pracy z Subversion. Sposób na cyberprzestępców czyli jak się wyszkolić z bezpieczeństwa informatycznego Kamil Brzeziński Najsłabszym ogniwem bezpieczeństwa jest paradoksalnie człowiek. Nawet najbardziej zaawansowany system informatycznych zabezpieczeń nie sprawdzi się w walce z hakerami, jeśli jego użytkownik sam nie zadba o poufność przetwarzanych danych. 18 12 4 Software Developer’s Journal jest wydawany przez NPRACA Sp. z o.o. Redakcja: sdjpl@sdjournal.pl Kierownik produkcji: Andrzej Kuca Adres korespondencyjny: NPRACA Spółka z o.o. ul. 3 Maja 46, 72-200 Nowogard, Polska Oddział w Warszawie: ul. Postępu 17 D, 02-676 Warszawa, Polska www.sdjournal.pl Facebook: https://www.facebook.com/SoftwareDevelopersJournal Redakcja dokłada wszelkich starań, by publikowane w piśmie informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain. Wszystkie znaki firmowe zawarte w piśmie są własności odpowiednich firm. Zostały użyte wyłącznie w celach informacyjnych. Osoby zainteresowane wspópracą prosimy o kontakt: sdjpl@sdjournal.pl Skład i łamanie / projekt okładki: Digital Concept www.digitalconcept.pl admin@digitalconcept.pl 26 38 REKLAMAJeżeli jesteś zainteresowany/a reklamą swoich produktów w naszym magazynie Jeżeli jesteś zainteresowany/a wypromowaniem swojej marki wśród specjalistów IT Jeżeli szukasz klientów lub pracowników Zareklamuj się u nas! Posiadamy bazę 90 tysięcy specjalistów z różnych działów IT! W celu uzyskania oferty reklamowej skontaktuj się z nami: sdjpl@sdjournal.pl tel. 799 46 34 76 www.sdjournal.pl

1/20144 W 2004 roku światło dzienne ujrzała Java SE 5. Otrzymaliśmy tak bardzo oczekiwane ty- py generyczne, enumeracje, pętlę foreach, autoboxing, możliwość definiowania metod ze zmien- ną liczbą parametrów... Śmiało można powiedzieć, że mieliśmy do czynienia z rewolucją w programowaniu w języku Java, jakości kodu oraz jakości tworzonych przez nas aplikacji. Następne rewizje przynosiły raczej kosmetyczne zmiany - poprawki w API, bugfixy, imple- mentacje nowych funkcji języka dyskutowanych w ra- mach Java Community Process, jednak sama składnia ję- zyka nie zmieniła się prawie wcale. W 2010 roku pojawiły się pierwsze oficjalne doku- menty opisujące szczegóły wprowadzenia domknięć (closure) dla języka Java w ramach Lambda Project – dłu- go oczekiwanych przez programistów. Po rezygnacji z wprowadzenia domknięc w siódmej wersji platformy Java, Java SE 8 nareszcie wprowadza Project Lambda pod strzechy! Project Lambda Project Lambda powstał w celu rozwiązania „problemu wertykalnego” w Java. Zarówno Java API jak i wiele bi- bliotek i frameworków Java wykorzystuje technikę wy- wołań zwrotnych realizowanych poprzez interfejsy za- wierające tylko jedną metodę (SAMType – Single Abstract MethodType). Implementacja takiego interfejsu często realizowana jest poprzez anonimowe klasy wewnętrzne: button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { long when = e.getWhen(); Date whenDate = new Date(when); System.out.println(″Action prformmed at:″ + whenDate.toString()); } }); Przykład 1 Implementacja właściwej logiki wymaga dodatkowego, powtarzalnego kodu niepotrzebnie wydłużającego kod proste wywołania metod. Wykorzystując nowe wyrażenia lambda powyższa im- plementacja wyglądać będzie następująco: button.addActionListener(event -> { long when = event.getWhen(); Date whenDate = new Date(when); System.out.println(″Action performmed at:″ + whenDate.toString()); }); Przykład 2 Stosując wyrażenia lambda możemy pozbyć się nad- miarowego kodu i skupić na implementacji logiki sys- temu. Kod staje się zdecydowanie bardziej czytelny. Przyjrzyjmy się szczegółom implementacji i zobaczmy jak to działa! Wyrażenia lambda są ściśle związane z interfejsami funkcyjnymi – nowym pojęciem wprowadzonym w Ja- va SE 8. Interfejs funkcyjny jest to interfejs definiują- cy dokładnie jedną metodę abstrakcyjną. Przykładem Java SE 8 Java SE 8 jest w środowisku programistów Java najbardziej oczekiwaną wersją platformy od 2004 roku. Otrzymamy nie tylko nowe funkcjonalności i poprawki API, ale wprowadzone zostaną również nowe elementy składni języka Java. Przyjrzyjmy się bliżej nowej wersji Java SE 8! Dowiesz się Czym są wyrażenia lambda, poznasz nowe ele- menty języka Java oraz nowe możliwości wielowąt- kowego przetwarzania danych. Powinieneś wiedzieć Czym jest dziedziczenie i typy generyczne. Powi- nieneś posiadać praktyczną wiedzę z zakresu ko- lekcji w języku Java oraz znać zagadnienie prze- twarzania wielowątkowego.

www.sdjournal.pl 5 JAVA SE 8 (String a, String b) -> {System.out.println(a+b)}; (String a, String b) -> {return a+b}; Pierwsze wyrażenie przyjmuje 3 argumenty typu String i zwraca ich konkatenację. Drugie wyrażenie nie przyjmuje żadnych argumentów i zwraca typ int Trzecie wyrażenie przyjmuje 2 obiekty typu String i wyświetla ich złożenie w konsoli. Ostanie wyrażenie przyjmuje 2 obiekty typu String i zwraca ich konkatenację. Warto zauważyć, że w składni wyrażenia lambda nie deklarujemy typu interfejsu funkcyjnego, a skoro język Java jest językiem silnie typowanym, powstaje pytanie jaki typ reprezentuje dane wyrażenie. Otóż typ wyra- żenia wnioskowany jest z kontekstu. Oczywiście to sa- mo wyrażenie lambda może być reprezentowane przez różne typy, przykład Callable callable = c -> ″Kilroy was here!″; PrivilegedAction action = c -> ″Kilroy was here!″; Kompilator sprawdza czy wyrażenie lambda może być reprezentowane przez dany typ następująco: • typ musi być interfejsem funkcyjnym • wyrażenie lambda posiada tę samą ilość parame- trów co metoda interfejsu funkcyjnego i są one tego samego typu • typ zwracany przez wyrażenie lambda jest tego sa- mego typu co typ zwracany przez metode interfejsu funkcyjnego • wyjątki rzucane w ciele wyrażenia lambda zdeklaro- wane są w metodzie interfejsu funkcyjnego Zatem jeśli chcemy uprościć naszą implementację i wy- korzystywać wyrażenia lambda – najpierw musimy po- siadać interfejs funkcyjny. Niestety nie mamy możliwości zdefiniowania gene- rycznego typu, który przechowywałby po prostu refe- rencję do funkcji. Kolejną konsekwejcją jest to, że wyrażenia lambda możemy deklarować tylko w przypadkach gdy istnieje odpowiedni kontekst dla określenia typu interfejsu funk- cyjnego, czyli w następujących sytuacjach: • deklaracje zmiennych • przypisanie wartości • operacje return • inicjalizacje tablic • argumenty metod i konstruktorów • w ciele wyrażeń lambda • operator warunkowy ? : • rzutowanie takiego interfejsu jest wykorzystany w przykładzie 1 java.awt.event.ActionListener. Przy czym słowo de- finiujący nie oznacza, że interfejs nie może posiadać większej ilości metod. Interfejs może definiować do- wolną ilość metod, jednak pozostałe metody abstrak- cyjne muszą być nadpisywane przez klasę java.lang. Object. Mówiąc prościej – interfejs funkcyjny może posiadać tylko jedną metodę, która nie została zadeklarowana w klasie java.lang.Object @FunctionalInterface public interface Comparator { int compare(T o1, T o2); boolean equals(Object obj); } Przykład 3 Kompilator automatycznie rozpoznaje interfejsy funk- cyjne, jednak dla celów informacyjnych została wpro- wadzona adnotacja @FunctionalInterface Java SE 8 wprowadza nowy pakiet java.util.function zawierający podstawowy zestaw interfejsów funkcyjnych reprezentujących podstawowe funkcje: • Predicate – testowanie własności obiektu – dla obiektu zwracana jest wartość boolean • Consumer - wykonanie akcji na obiekcie bez zwra- cania wartości • Function - funkcja zwracająca obiekt typu R na podstawie obiektu T • Supplier - konstruktor, fabryka obiektów typu T • UnaryOperator - funkcja zwracająca obiekt typu T – obpowiednik Function• BinaryOperator - funkcja zwracająca obiekt typu T, przyjmująca dwa argumenty typu T Dodatkowo dostępne są specjalizacje powyższych in- terfejsów, np. specjalizacje interfejsów dla typów pry- mitywnych int, long oraz double – pozostałe typy moż- na uzyskać poprzez rzutowanie. Zobaczmy jak teraz zbudowana jest deklaracja wyraże- nia lambda. Składa się z następujących elementów: 1. Deklaracja argumentów wejściowych metody inter- fejsu funkcyjnego oddzielonych przecinkami 2. Operatora ->3. Ciała wyrażenia zawierającego jedną komendę lub blok kodu zawartego w nawiasach klamrowych { }. Zastosowania wyrażeń prezentują poniższe przykłady (String a, String b, String c) -> ″Hello ″ + a + ″ ″ + b + ″ ″ + c; () -> 13;

1/20146 Na początku wspomnieliśmy o tym, że wyraże- nia lambda pozwalają zastąpić anonimowe klasy we- wnętrzne. Warto zatem przypomnieć problem za- kresu zmiennych i przesłaniania metod w klasach we- wnętrznych, który często trapił programistów. Wyra- żenia lambda nie są reprezentowane przez klasy we- wnętrzne, nie dziedziczą żadnych metod czy zmien- nych z typów nadrzędnych. Dostępna przestrzeń nazw definiuje tylko moment deklaracji. Rzućmy okiem na następujący przykład: public class LambdaTest { Runnable printThis = () -> { System.out. println(this); }; Runnable printToString = () -> { System.out. println(toString()); }; Runnable printInnerClass = new Runnable() { @Override public void run() { System.out.println(toString()); } }; public String toString() { return «Kilroy was here!»; } public static void main(String... args) { new LambdaTest().printThis.run(); new LambdaTest().printToString.run(); new LambdaTest().printInnerClass.run(); } } Dzięki deklaracji wyrażeń lambda wewnątrz obiek- tu LambdaTest, staje się on przestrzenią nazw dla wy- rażeń lambda. Po uruchomieniu aplikacji wywoła- nie printThis oraz printToString wyświetli na kon- soli tekst ”Kilroy was hare!”, natomiast wywołanie printInnerClass wyświetli nam coś na kształt Lambda- Test$1@17c264. Skoro ograniczna nas tylko moment deklaracji – co ze zmiennymi lokalnymi? Do tej pory dla klas wewnętrznych dostęp ograniczony był do finalnych zmiennych lokalnych. Java SE 8 wprowadza nowe po- jęcie effective final local variable. Zmienną możemy określić jako effective final, jeśli jej wartość nie zmie- nia się od momentu deklaracji. W Java SE 8 dostęp do takich zmiennych lokalnych możliwy jest zarówno z poziomu wyrażeń lambda jak i anonimowych klas we- wnętrznych! Powyższy przykład pokazuje, że przejście z klas we- wnętrznych na wyrażenia lambda może doprowadzić do trudnych do wykrycia błędów. Jeśli nasz kod nie jest odpowiednio obłożony testami, warto o to zadbać przed zmianą implementacji – unikniemy nieprzyjem- nych konsekwencji. Podsumowując - wyrażenia lambda oszczędzają czas programisty dając możliwość zastąpienia anonimo- wych klas wewnętrznych. I na taką rewolucję przyszło nam czekać 10 lat? Na pewno zmiany w składni języka upraszczają implementację, na pewno kod aplikacji bę- dzie bardziej czytelny, jednak musimy nauczyć się korzy- stać z wyrażeń lambda i konsekwentnie wykorzystywać dostępne nowe interfejsy funkcyjne. Efekty przyjdą z cza- sem... Jednak to nie wszystko - zobaczmy jakie inne zmia- ny zostały wprowadzone w Java API i jak współgrają z nową filozofią wyrażeń lambda. Metody domyślne Rzućmy okiem na implementację interfejsu Iterablepublic interface Iterable { Iterator iterator(); default void forEach(Consumer action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } } Wzrok nas nie myli – interfejs zawiera implementację metody forEach. Stało się to możliwe, dzięki wprowa- dzeniu nowego elementu języka – metodami domyśl- nymi - default methods, określanymi również jako virtual extension methods lub defender methods. Jak zauważyliśmy wcześniej samo wprowadzenie wyrażeń lambda nie przyniesie rewolucji w sposobie programowania w języku Java. Konieczne jest umoż- liwienie wykorzystania nowych możliwości w istnieją- cych już elementach języka. Jednak zmiana istniejących interfejsów pociągnęłaby za sobą konieczność zmia- ny implementacji, co praktycznie uniemożliwiałoby przejście ze starszych wersji języka. Metody domyślne umożliwiają rozszerzenie interfejsów bez konieczno- ści zmian klas implementujących je implementacji. A jakie daje nam to możliwości? Zamiast dobrze znanej pętli foreach for (String string : list) { System.out.println(string); } Mamy możliwość wykorzystania wyrażeń lambda list.forEach(c -> System.out.println(c)); Przeglądając implementację innych interfejsów – np. interfejs java.util.Comparator nasuwa się wniosek.

www.sdjournal.pl 7 JAVA SE 8 Zgodnie z określeniem defender method, metody do- myślne na daną chwilę największe zastosowanie znaj- dą przy refaktoryzacji pod kątem wyrażeń lamb- da i świetnie się w tym celu sprawdzają. W przypad- ku tworzenia nowej implementacji – bardziej przydat- ne będzie przemyślane i konsekwentne wykorzystanie interfejsów funkcyjnych. Interfejsy w języku Java rekompensowały brak dzie- dziczenia wielokrotnego – rekompensowały, ponieważ do tej pory interfejsy nie mogły zawierać implementa- cji metod. Czyżby Java SE 8 wprowadzała wsparcie dla wielokrotnego dziedziczenia? Nie do końca – dziedzi- czenie metod domyślnych odbywa się na tych samych zasadach co zwykłych metod, jednak z pewnymi wyjąt- kami: • Jeśli klasa dziedziczy więcej niż jeden interfejs defi- niujący metodę default o tym samym deskryptorze, występuje błąd kompilacji • Kolejność w jakiej interfejsy pojawiają się w deklara- cji implements lub extends nie mają wpływu na me- chanizm dziedziczenia • Metody zdefiniowane w klasach przesłaniają meto- dy domyślne – nawet jeśli są to metody abstrakcyj- ne, bez względu na kolejność w hierarchii dziedzi- czenia • Przeładowana metoda domyślna jest pomijana – tracimy do niej dostęp Przyjrzyjmy się następującemu przykładowi public class Mammal { public void speak() { System.out.println(″I can’t speak″); } } public interface Human { default void speak() { System.out.println(″I am a human″); } } public interface Male { default void speak() { System.out.println(″I am a male″); } } public class Person extends Mammal implements Human, Male { } Zgodnie z zasadami dziedziczenia dla klas domyślnych wywołanie metody speak() dla obiektu Person spo- woduje wyświetlenie I can’t speak, ponieważ zwykłe metody przesłaniają metody domyślne. Jeśli klasa Person nie będzie rozszerzać klasy Mammal, tylko implementować interfejsy Human i Male, otrzymamy błąd kompilacji. Czyli dziedziczenie wie- lokrotne nie jest możliwe... jednak w takiej sytuacji istnieje możliwość wyboru, z której klasy metodę chcemy dziedziczyć, poprzez przeładowanie meto- dy speak() public class Person implements Human, Male { public void speak() { Male.super.speak(); } } Zatem Java nadal stricte nie wspiera dziedziczenia wie- lokrotnego. Jednak tak długo jak deskryptory metod domyślnych będą różne, możemy zamiast rozszerzać mozolnie kolejne klasy, zaimplementować kilka interfej- sów z metodami domyślnymi – przy czym będzie to ra- czej zła praktyka. public interface Human { default void speak() { System.out.println(″I am a human″); } } public interface Male { default void sing() { System.out.println(″I am singing″); } } public class Person implements Human, Male { } Referencjonowanie metod Kolejną nowością w składni języka Java jest możliwość stworzenia referencji do metody. Tworzenie referencji do metody odbywa się na takich samych zasadach jak tworzenie wyrażeń lambda – referencja do metody to instancja interfejsu funkcyjnego. Rzućmy okiem na inter- fejs Comparator. Możemy przygotować instancję klasy comparator za pomocą wyrażeń lambda. Comparator comp = (a ,b) -> a.getName(). compareTo(b.getName()); Aby jeszcze uprościć deklarację, wykorzystajmy meto- dę domyślną Copmarator.comparing Comparator comp = Comparator.comparing(p ->p.getName()); Wykorzystujące referencje do metody deklaracja wy- glądać będzie następująco

1/20148 Comparator comp = Comparator. comparing(Person::getName()); Jak widać różnica nie jest duża, jednak kod staje się bardziej czytelny – widzimy, że do porównania wyko- rzystujemy metodę getName z klasy Person. Możemy tworzyć referencje do następujących metod: • metod statyczne - String::valueOf • metod instancji obiektu - person::getName • metod instancji obiektu określonego typu - Person::getName • konstruktorów – Person::new Parametry metody interfejsu statycznego stają się pa- rametrami metod referencjonowanych, dzięki czemu możemy je dowolnie rzutować i korzystać z varargs- ów, np.: Comparator comp = Comparator. comparing(Human::getAge()); Runnable r = Example::main - void main(String... args) Consumer cons = Male::speak; Referencjonowanie metod ułatwia wykorzystanie inst- niejących metod o deskryptorze identycznym z inter- fejsem funkcyjnym. public class PersonHelper { public static int getAgeDifference(Person a, Person b) { return a.getAge() - b.getAge(); } } Comparator byAge = PersonHelper::getAgeDifference; Comparator byAgeLambda = (a, b) ->PersonHelper.getAgeDifference(a, b); W takich przypadkach referencje metod są zdecydo- wanie bardziej czytelne niż wyrażenia lambda. Miejmy nadzieję, że nowy element języka przyczyni się do poprawienia jakości kodu aplikacji – do tej pory na- zbyt często prosty kod lądował bezpośrednio w ciele anonimowych klas wewnętrznych i powtarzany był w różnych miejscach w aplikacji. Referencje do metod, ja- ko nowy element języka Java zachęca do zaprzestania tej złej praktyki. Kolekcje w Java SE 8 Opisując metody domyślne wprowadzone w interfej- sach w Java SE 8 wspomniałem, że ich głównym zasto- sowaniem jest rozszerzenie istenijących interfejsów i dostosowanie ich implementacji pod kątem wyrażeń lambda. Implementacja kolekcji przeszła największą metamor- fozę za sprawą wprowadzenia wyrażeń lambda i metod domyślnych. Stream API Do tej pory najwygodniejszym sposobem przetwa- rzania elementów kolekcji w Java były obiekty Itera- tor oraz pętla foreach – wykorzystująca zresztą ite- ratory. Z założenia takie przetwarzanie było sekwencyjne – elementy pobierane były jeden po drugim i przetwarza- ne. Strumienie (Stream) wprowadzają nowy sposób ite- racji z wykorzystaniem operacji agregujących (aggre- gate operations). Ciekawostką jest to, że definiując ta- kie operacje nie wykorzystujemy metody next() do której tak bardzo przyzwyczaiły nas iteratory. Przyj- rzyjmy się przykładowi: List list = new ArrayList<>(); listOfPeople.stream() .filter(p -> p.getAge() > 18) .filter(p -> p.isUnemployed()) .forEach(p -> storePersonalData()); Powyższy kod z dostępnej listy obiektów typu Person wykona operację storePersonalData dla każdej bezro- botnej osoby powyżej 18 roku życia. Zauważmy, że korzystając ze strumieni i operacji agregujących nie definiujemy w jaki sposób mamy po- bierać kolejne elementy, ale jakie operacje mają zo- stać na nich wykonane. Wykorzystujemy do tego celu wyrażenia lambda i referencje do metod. Sposób ite- racji omówimy jednak za chwilę Zwróćmy uwagę na to, że cała logika została za- mknięta w jednej „linii” kodu, dzięki wprowadzeniu możliwości łączenia operacji agregujących w poto- ki (pipelines) – na wzór potoków znanych z systemu UNIX. Potok składa się z następujących elementów: • źródło – może nim być kolekcja, tablica, generator obiektów (np. obiekt Iterator), kanał wejścia/wyjścia (IO channel) – np. zwracany przez metodę Files. lines(Path file) • operacje pośrednie (intermediate operations)– opera- cje zwracające strumienie, np. operacja filter • operacja kończąca (terminal operation) – opera- cja kończąca potok, zwracająca obiekt nie będą- cy strumieniem – np. kolekcję, lub przetwarzające elementy strumienia bez zwracania żadnej warto- ści – np. operacja forEach

www.sdjournal.pl 9 JAVA SE 8 Dostępne są następujące operacje pośrednie: • filter(Predicate predicate) – opera- cja zwracająca strumień będący podzbiorem speł- niającym określone kryterium, np.. filter(p ->p.getAge() > 18) • map(Function mapper) - operacja zwracająca strumień skonwertowanych obiektów, np. map(p -> Unemployed::new) • flatMap(Function> mapper) - operacja mapująca relację jeden do wiele na stru- mień. Wyobraźmy sobie, że obiekt Person posiada metodę zwracającą kolekcję obiektów typu Child, np. Collection getChildren(). Możemy za- tem zwrócić strumień dal takiej kolekcji. W efekcie uzyskamy strumień wszystkich dzieci wejściowych obiektów Person, np. flatMap(p -> p.getChildren(). stream()) • sorted() - operacja zwracająca strumień posorto- wany za pomocą istniejącej metody compareTo • sorted(Comparator comparator) - ope- racja zwracająca strumień posortowany za pomocą określonego obiektu comparator • distinct() - operacja zwracająca strumień unikal- nych obiektów. Do wykrywania duplikatów wyko- rzystywana jest metoda equals Metody pośrednie mogą być dowlonie łączone, np. listOfPeople.stream() .filter(p -> p.getAge() > 18) .filter(p -> p.isUnemployed()) .distinct() .sorted() Co również bardzo istotne, metody pośredie są meto- dami lazy – będą wywołane dopiero w momencie wy- wołania metody końcowej. Możemy zatem stworzyć potok bez metody końcowej i uzyskany strumień prze- tworzyć w późniejszym czasie. Dostępne są następujące operacje końcowe: • void forEach(Consumer consumer) - operacja przetwarza elementy strumienia bez zwracania żadnej wartości, np. forEach(p ->storePersonalData()); • R collect(Collector collector) – operacja przetwarzająca strumień i zwracająca po- jedynczą wartość lub kolekcję obiektów. Możemy zwrócić listę posortowanych obiektów Person listOfPeople.stream() .filter(p -> p.getAge() > 18) .filter(p -> p.isUnemployed()) .distinct() .sorted() .collect(Collectors.toList()) • Optional reduce(T identity, BinaryOperatoraccumulator) - operacja przetwarzająca strumień i zwracająca pojedynczą wartość. Możemy np. poli- czyć sumę wszystkich miesięcznych świadczeń osób bezrobotnych: listOfPeople.stream() .filter(p -> p.getAge() > 18) .filter(p -> isUnemployed()) .mapToInt(p -> p.getMonthlyGrants()) .reduce(0, (a,b) -> a + b) • Iterator iterator() - operacja zwraca iterator obiektów pozostałych po przetworzeniu potoku Zarówno dla operacji collect jak i reduce istnie- ją gotowe implementacji ciekawych funkcji. Dla operacji reduce, w zależności od typu strumienia, mamy dostępne, np. dla Strean opera- cje arytmetyczne takie jak suma czy średnia. Ope- racje collect zawarte są w klasie Collectors. Do ciekawszych należą np. funkcje grouppingBy czy partitioning – zachęcam do lektury javadoc. Zobaczmy teraz w jaki sposóþ pobierane są ele- menty kolekcji. Zamiast jawnej iteracji (external ite- ration), mamy do czynienia z wewnętrzną iteracją (internal iteration) z wykorzystaniem obiektów Spli- terator. Obiekt spliterator – podobnie jak Iterator se- kwekncyjnie przetwarza elementy kolekcji, ale rów- nocześnie umożliwia podział elementów kolekcji na mniejsze porcje za pomocą metody trySplit(). Efektem takiej operacji jest stworzenie podrzęd- nej instancji Spliterator, przy czym główny Spliterator pominie elementy przetwarzane przez obiekty pod- rzędne. Oczywiście możliwy jest dalszy podział podrzęd- nych instancji Spliterator, tak aby uzyskać odpowied- nio małe porcje i przetwarzać je równolegle. Czyli przetwarzanie sekwencyjne się nie zmienia – Spliterator zastąpił Iterator. Natomiast przetwarzanie równoległe z podziałem Spliteratorów brzmi skom- plikowanie – pula obiektów spliterator, pula wątków lub ForkJoinPool, przydział obiektów do przetwo- rzenia, zebranie rezultatów cząstkowych... Nic po- dobnego! Z Java SE 8 równoległe przetwarzanie otrzymujemy za darmo! listOfPeople.parallelStream() .filter(p -> p.getAge() > 18) .filter(p -> p.isUnemployed()) .forEach(p -> storePersonalData());

1/201410 Każdy strumień może być przetworzony równole- gle, dzięki czemu w końcu w prosty sposób będzie- my mogli wykorzystać potencjał wielordzeniowych maszyn. Jeśli prawo Moorea ma się obronić – rdze- ni będzie coraz więcej, więc to bardzo dobra wia- domość. Jednak jeśli przetwarzanie nie wprowadzało prawie żadnych ograniczeń, tak w strumieniach rów- noległych (parallel stream) obowiązują już pewne re- guły: • strumień musi być statyczny – kolekcja nie mo- że być modyfikowana (elementy kolekcji nie mogą być dodawane, usuwane, przemieszczane) podczas przetwarzania. Operacje pośrednie również nie modyfikować kolekcji • po zakończeniu przetwarzania strumień nie może być ponownie wykorzystany • przetwarzanie strumieni z zachowaniem kolej- ności wymaga użycia specjalnej operacji końco- wej forEachOrdered gwarantującej przetworzenie rezultatu dokładnie w kolejności w której znaj- dował się na wejściu strumienia. W przypad- ku użycia zwykłej metody forEach podczas prze- twarzania równoległego – kolejność zostanie utracona • konieczność użycia metod collect odpowied- nich do przetwarzania równoległego. Każ- dy obiekt Collector opisany jest przez enume- rację Characteristics. Wymagane jest stoso- wanie obiektów Collector z charakterystyką CONCURRENT. Na pewno zmiany w Java Collections API zdecydo- wanie uproszczą kod naszych aplikacji – zwłaszcza tych wykorzystujących wielowątkowość. Funkcjo- nalności, które do tej pory wymagały implementa- cji kilku klas, będziemy mogli zamknąć nawet w kil- ku linijkach kodu. Jednak zanim zaczniemy wykorzystywać nowe API warto zagłębić się w szczegóły dokumentacji oraz przejrzeć implementację podstawowych obiektów Spliterator – jest duże pole do zwiększenia wydajno- ści naszego kodu. Nie należy ulegać pokusie i wyko- rzystywać równoległego przetwarzania dla wszyst- kich strumieni. Zanim nasz kod trafi na docelowe środowisko, warto przeprowadzić porównawcze te- sty wydajnościowe rozwiązań wielowątkowych i se- kwencyjnych. Powtarzalne adnotacje (Repeating annotations) Jednym z ciekawszych przykładów adnotacji w Java do tej pory była adnotacja @EJBs – jedyną jej rolą było gru- powanie adnotacji @EJB @EJBs({@EJB(name=″name1″,beanInterface=Type1.class), @EJB(name=″name2″,beanInterface=Type2.class)}) Java SE 8 rozwiązuje ostatecznie ten problem przez prowadzenie powtarzalnych adnotacji – możliwe bę- dzie uzycie jednej adnotacji wiele razy w tym samym miejscu. @EJB(name=″name1″,beanInterface=Type1.class) @EJB(name=″name2″,beanInterface=Type2.class) Mała rzecz, a cieszy. Adnotacje typów (Type Annotations) Java 8 SE wprowadza możliwość dodawania adnotacji dla typów, np. @NotNull String text = ″test″; @Readonly List users = ... public String getFullText(@Readonly Paper sheetOfPaper) Celem wprowadzenia tych adnotacji była poprawa ja- kości kodu przez umożliwienie bardziej szczegółowej jego kontroli za pomocą narzędzi analizujących kod. Spójrzmy na przykład: Deklaracja metody public class Paper { public String getText(@Readonly Paper sheetOfPaper) { ... Kod wywołujący metodę: @NotNull String text = sheetOfPaper.getText(); Zauważmy, że metoda getText może potencjalnie zwracać wartość null i przypisać ją do zmiennej zade- klarowanej jako @NotNull. Oczywiście kompilacja i wy- konanie programu będzie działać prawidłowo. Możliwe jest natomiast wychwycenie potencjalnego problemu przez narzędzia analizujące kod takie jak CheckerFrame- work, Javarifier czy inne. Możliwość dodawania adnotacji typów na pewno sprawdzi się w zaawansowanych projektach z bar- dzo restrykcyjnymi normami jakościowymi i popar- tymi dobra analizą. Na pewno wywrą wpływ już na etapie projektowania klas i interfejsów – koniecz- ne będzie nie tylko zaplanowanie nazw i struktur danych, ale także ich zachowania. Na pewno war- to przyjrzeć się dostępnym rozwiązaniom wspiera- jącym analizę kodu. Ciekawym testem byłby również pomiar czasu potrzebnego na analizę średniej wiel- kości projektu.

www.sdjournal.pl 11 JAVA SE 8 Java Date - Time API Na koniec, wisienka na torcie. Istniejąca wersja Date -Time API oparta na klasie java.util.Calendar odcho- dzi do lamusa. Dostaliśmy dokładnie to o co prosili- śmy od lat. Po pierwsze, Java SE 8 wprowadza pakiet java.time gdzie znjadziemy wszystkie potrzebne klasy. Po dru- gie API jest przejrzyste, model prosty i pozbawiony wprowadzających zamieszanie metod. Pierwszy z gó- ry przykład. LocalDateTime dt = LocalDateTime.now(); Calendar cal = Calendar.getInstance(); Metoda now niesie ze sobą jasny przekaz , a metoda getInstance hmm… singleton? A co ze strefami czasowymi? Spróbujmy obliczyć go- dzinę przylotu gdy poruszamy się po różnych strefach czasowych, gdzie lot trwa 8 godzin: LocalDateTime now = LocalDateTime.now(); ZonedDateTime sydneyTime = dt.atZone(ZoneId. of(″Australia/Sydney″)); ZonedDateTime tokioTime = sydneyZoneTime. withZoneSameInstant(dt. atZone(ZoneId.of(″Asia/Tokyo″))); ZonedDateTime tokioArrivalTime = tokioTime. plusHours(8); Wykorzystując java.util.Calendar sami musielibyśmy obliczać przesunięcie między strefami tworząc dodat- kowe obiekty TimeZone i przekazywać bieżącą datę w postaci wartości long, a wykonując operację przesunię- cia czasu musielibyśmy użyć metody cal.add(Calendar. HOUR, 8). Ijeszczejednadobrainformacja.KlasaSimpleDateFormat również doczekała się następcy i tym razem jedną instan- cję swobodnie możemy używać w wielu wątkach! DateTimeFormatter dtf = DateTimeFormatter. ofPattern(″MM-dd-yyyy HH:mm″); dtf.format(tokioArrivalTime); Geniusz tkwi w prostocie - po wielu latach w końcu doczekaliśmy się biblioteki wspierającej operacje na czasie! Podsumowanie Java SE 8 na pewno jest krokiem milowym w rozwo- ju języka java. Wyrażenia lambda zależne od interfej- sów funkcyjnych na pewno nie dają pełnej elastycz- ności, ale są użyteczne i powinny rozwiązać problem wewnętrznych klas anonimowych. Wprowadzenie metod domyślnych do interfejsów, może nie wyglą- da zbyt elegancko, ale świetnie realizuje swoje cele – czego przykładem są chociażby zmiany w Collec- tions API. Czas pokaże, które z nowości przyjmą się w środowisku Java, a które będą musiały zostać jeszcze dopracowane. Jedno jest pewne – mamy nowe i do- bre narzędzia, musimy nauczyć się z nich korzystać, czekamy na reakcję społeczności i na pewno czeka- my na więcej! MARCIN MICHALSKI Współtwórca i współwłaściciel marki Sagiton. Posiada 7 lat do- świadczenia w programowaniu. Obecnie realizuje się jako Sys- tem Architect w projektach wy- konywanych przez ArrowGroup Sp. z o.o., której jest współzałoży- cielem. Ukończył Informatykę na Wydziale Elektroniki na Politechnice Wrocław- skiej. Odbył kursy i szkolenia organizowane przez: Cisco Network Academy, Sun Microsy- tems, Oracle. Uzyskał certyfikaty eksperckie w zakresie sieci komputerowych, programowa- nia (Java/JEE) oraz baz danych. Jest autorem licznych publikacji naukowych, prowadzi blo- ga technicznego. Marka: www.sagiton.pl [1] Kontakt: marcin.michalski@sagiton.pl Blog: marcin-michalski.pl [2]

1/201412 D ziś chciałbym wspomnieć tylko o jednym - choć pewnie najbardziej znanym - projekcie ze stajni OWASP, mianowicie o OWASP Top 10. Lista OWASP Top 10 jest listą dziesięciu najpopularniejszych błędów bezpieczeństwa znajdowanych w aplikacjach webo- wych. Istnienie listy ma skupić uwagę zespołów programi- stów na te zagrożenia, których pojawienie w projekcie jest najbardziej prawdopodobne, edukować oraz być punktem startowym do korzystania z narzędzi i projektów OWASP. Lista Top 10 aktualizowana jest przeciętnie co trzy lata, a ostatnia odsłona listy miała miejsce w czerwcu tego roku. NajkrótszarecenzjazmiandokonanychnaliścieOWASP Top 10 2013 brzmiała by: “nic nowego”. Wszystkie roz- poznane przez OWASP w roku 2010 zagrożenia pozosta- ją nadal aktualne. Najnowsza odsłona listy bardzo dobrze pokazuje, że w pogoni za wypełnianiem wymagań funk- cjonalnych nadal zdarza nam się zapomnieć o bezpieczeń- stwie tworzonej aplikacji. W epoce automatycznych ska- nerów, przeczesujących internet w poszukiwaniu luk, nie jest to krzepiący wniosek. Listę OWASP Top 10 2013 po raz kolejny otwierają błędy typu injection (z najznamienit- szym ich reprezentantem - SQL injection (SQLi)) i to o nich chciałbym dzisiaj opowiedzieć. Czym są błędy typu injection? Najkrótsza, obrazowa (i nieoficjalna) definicja - błędy typu injection powstają wtedy, gdy oddajemy w ręce użytkow- nika aplikacji możliwość oprogramowania np. naszej bazy danych. Robimy to najczęściej nieświadomie poprzez kon- katenację elementów naszego kodu z tekstem wprowa- dzonym do systemu. Rozpatrzmy następujące przykłady. Przykład 1: Runtime runtime = Runtime.getRuntime(); Process proc = runtime.exec(„find . -name „ + args[0]); Z kodu naszej aplikacji próbujemy uruchomić komen- dę systemową dodatkową parametryzowaną przez użyt- kownika. Czego oczekujemy? Oczekujemy nazwy pliku lub katalogu, który ma zostać odnaleziony. Co może zrobić użytkownik? W zależności od fantazji może na przykład spróbować usunąć całą za- wartość dysku doklejając do polecenia tekst “foo; rm -rf /” i uzyskując przez to: find . -name foo; rm -rf / Nie uruchamiacie serwerów aplikacji z uprawnieniami administratora, prawda? Przykład 2 (ze strony OWASP https://www.owasp.org/in- dex.php/XPATH_Injection): Uwierzytelniamy użytkowników naszej aplikacji na podstawie danych poniższego XMLa:ArnoldBakerABakerSoSecretAdminPeterPanPPanNotTellingUserInjection po raz kolejny… Open Web Application Security Project (OWASP) jest społecznością skupiającą osoby, instytucje i organizacje zainteresowane poprawą bezpieczeństwa aplikacji webowych. W ramach realizowanych przez OWASP projektów tworzone są narzędzia, dokumenty i procesy wspomagające pracę przy tworzeniu systemów. Dobrze o tym pamiętać, aby w różnych sytuacjach nie wynajdywać koła na nowo, tym bardziej że oferowane zasoby wspomagają pracę nie tylko deweloperów i architektów, lecz również testerów i menadżerów projektów.

www.sdjournal.pl 13 INJECTION SQL injection Większość nietrywialnych systemów i aplikacji wymaga dostępu do bazy danych. Wszędzie tam, gdzie wystę- puje relacyjna baza danych, istnieje również zagroże- nie wystąpienia błędów SQL injection. Co może zrobić złośliwy użytkownik mogąc oprogramować naszą bazę? Pojawiają się tutaj trzy zagrożenia: Omijanie kryteriów klauzuli WHERE w zapytaniu SQL Klasyczny przykład zakłada uwierzytelnianie użytkowni- ków w systemie na podstawie wykonania poniższego za- pytania: select * from users where name = ‘’ and password = ‘’; Niebezpieczna parametryzacja miała by tu postać: name = ″’ or 1=1 or ′a′=′a″ password = ″anything″ Pozyskiwanie dodatkowych informacji z bazy Przy założeniu, że wykonywane w systemie zapytanie ma postać: select content from data where id = poniższa parametryzacja sprawi, że udostępnimy więcej informacji niż byśmy tego chcieli: id = ″21 UNION ALL SELECT password from users″ Przekazywanie dodatkowych poleceń do bazy I tu kolejny przykład. Mając zapytanie: select name from record where id = możemy się obawiać wstrzyknięcia parametryzacji ty- pu: id = ″1; DROP table record; DROP table address--″ budując parametryzowalne zapytanie XPath: “//Employee[UserName/text()=‘“ + name+ “’And Password/text()=‘“ + password + “‚]” Następująca parametryzacja pozwala całkowicie omi- nąć nasze mechanizmy kontroli: name: blah’ or 1=1 or ‚a’=’a password: foo Z powyższych przykładów wynika parę wniosków. Po pierwsze błędy typu injection nie są jedynie ce- chą aplikacji webowych i równie dobrze mogą wystę- pować w aplikacjach desktopowych oraz mobilnych. To czyni je jeszcze bardziej niebezpiecznymi. Po dru- gie powodem powstawania błędów jest konkatenacja elementów naszych zapytań z niefiltrowanymi para- metrami wprowadzonymi przez użytkownika. To bar- dzo istotne spostrzeżenie. Powinno uwrażliwić każ- dego czytającego kod na potencjalne błędy wynikające z faktu łączenia łańcuchów znaków celem parametry- zacji zapytań i poleceń. Czy jest się czego obawiać? Podstawowe pytania jakie należy sobie zadać myśląc o bezpieczeństwie budowanego systemu brzmią: Co ma- my do stracenia? Co stanowi o wartości naszej aplikacji? Chcemy chronić nasze algorytmy, czy też chcemy chronić nasze dane? Co się stanie, jeżeli utracimy całkowicie i jedno i drugie? Przedstawione powyżej przykłady są co prawda kry- tyczne z punktu widzenia potencjalnych zniszczeń lub innych konsekwencji w naszym systemie, jednak praw- dopodobieństwo ich wystąpienia jest znikome. (Kto z nas wywołuje w swojej aplikacji parametryzowalne po- lecenia systemowe? Kto z nas uwierzytelnia użytkow- ników na podstawie wpisów w pliku XML?) Co więc sprawiło, że wielcy tego świata (Nokia, Sony Music, Yahoo, FBI, NASA, itd.) padli ofiarą błędów typu in- jection? OWASP Top 10 2013 (https://www.owasp.org/index.php/Top_10_2013-Top_10) • Injection • Broken authentication and session management (Błędy uwierzytelniania i obsługi sesji) • Cross-Site Scripting (XSS) • Insecure direct object references (Niezabezpieczone, bezpośrednie linki do obiektów) • Security misconfiguration (Błędy konfiguracji) • Sensitive data exposure (Błędy obsługi poufnych danych) • Missing function level access control (Brak weryfikacji uprawnień dostępu) • Cross-Site Request Forgery (CSRF) • Using components with known voulnerabilities (Wykorzystanie komponentów z błędami bezpieczeństwa) • Unvalidated redirects and forwards (Niewalidowane przekierowania)

1/201414 Poza pomysłowością potencjalnego intruza, chcące- go wykorzystać błędy w implementacji naszego sys- temu, ograniczać będzie wykorzystana przez nas technologia oraz biblioteki. Na przykład ataki ma- jące na celu wykonanie w bazie dodatkowych po- leceń (punkt 3) są możliwe w niektórych frame- workach wykorzystywanych przez PHP, nie są na- tomiast możliwe i wspierane przez np. sterownik JDBC w Javie. Podsumowując powyższe przykłady trzeba jednak zauważyć, że konsekwencje błędów typu injection w aplikacji idą bardzo daleko i umożliwiają pozyskanie i nadpisanie wszystkich danych zawartych w bazie (przy jednoczesnym ominięciu mechanizmów kontroli dostę- pu do tych danych). Konsekwencje utraty informacji na taką skalę mogą doprowadzić niektóre przedsiębior- stwa nawet do bankructwa. Co nie pomaga? Rozwiązania wszelki problemów dzielą się na dwie kate- gorie: na te które działają i na całą resztę. Próbę odpo- wiedzi na pytanie, jak uniknąć problemów niechcianych wstrzyknięć obcego kodu w naszej aplikacji, zaczniemy od analizy tych drugich. Na pewno rozwiązaniem pro- blemu błędów typu injection nie jest ignorancja. Nie należy oczekiwać, że nikt nie odkryje problemów z for- mularzami na trzeciorzędnej stronie naszego portalu, gdyż są zbyt głęboko ukryte. Narzędzia do automaty- zacji procesu skanowania aplikacji webowych biorą na siebie ciężar takich poszukiwań. Rozwiązaniem naszych problemów nie będzie prawie nigdy blacklisting niechcianych wartości parametryzacji zapytań. Rozpatrzmy następujący przykład: W naszym zapytaniu do bazy select content from data where id = obawiamy się wstrzyknięcia 21 UNION ALL SELECT (...). Tworzymy więc wyrażenie regularne, które sprawdzi, czy elementem naszego parametru nie jest czasem wartość UNION. Jeżeli tak, aplikacja zwróci błąd. Złośliwy użytkownik będzie mógł obejść nasz filtr przekazując zamiast UNION wartość UNIO/**/N (dla bazy danych MySQL). Problem z blacklistingiem jest taki, że rzadko kiedy de- weloper zna wszystkie sztuczki możliwe do wykonania ze składnią zapytań do bazy danych. Stąd zabezpiecze- nia tego rodzaju dają najczęściej tylko złudne poczucie bezpieczeństwa. Co pomaga? Możliwe rozwiązania problemów wstrzyknięć obcego kodu do aplikacji można podzielić na trzy kategorie: • wykorzystanie wbudowanych mechanizmów szablo- nów • escaping • whitelisting Wykorzystanie wbudowanych mechanizmów szablonów - (na przykładzie sterownika JDBC dla Javy) popraw- ne wykorzystanie szablonów PreparedStatement nie tylko zwiększa wydajność bazy danych (plan zapyta- nia musi być policzony tylko raz dla szablonu), lecz również rozwiązuje problem SQL injection. W po- niższym przykładzie wartość zmiennej id nie jest w stanie przeformułować treści zapytania do bazy: String sql = „SELECT firstname, lastname, age FROM Employees where id=?”; PreparedStatement statement = connection. prepareStatement(sql); Statement.setString(1, id); ResultSet rs = statement.executeQuery(sql); Escaping - czyli unieszkodliwienie wszystkich znaków modyfikujących zapytanie, które przychodzą spoza systemu, poprzez wykorzystanie odpowiedniej biblio- teki (np. ESAPI). Często spotykanym przykładem jest escapowanie wszystkich znaków będących częścią ko- du HTML przy dynamicznym generowaniu stron in- ternetowych w aplikacji webowej. Whitelisting - w przeciwieństwie do blacklistingu, gdzie szukaliśmy wzorców, które należało wykluczyć jako nie- bezpieczne, przy whitelistingu sprawdzamy, czy przekaza- ne wartości parametrów spełniają określone wyrażenie do którego bezpieczeństwa jesteśmy pewni, a odrzucamy wszystkie wartości, które wyrażenia nie spełniają. Tabela 1. SQL-Injection Hall of Shame Lista wybranych błędów SQL Injection, które doprowadziły do kompromitacji systemów. Pełna lista dostępna na stronie http://codecurmudgeon.com/wp/sql- injection-hall-of-shame/ Firma/Instytucja Data Utracono Nasa 2013-11 Dane ponad 100.000 użytkowników FBI 2012-12 Ponad 1,6 mln adresów e-mail i haseł użytkowników Yahoo 2012-07 450.000 niezaszyfrowanych haseł LinkedIn 2012-06 6,5 mln haszy haseł Sony Pictures 2011-06 Dane ponad 1 mln użytkowników

www.sdjournal.pl 15 INJECTION Dodatkowe zabezpieczenia Przestrzeganie powyższych wskazówek daje stupro- centową ochronę przed błędami typu injection. Nadal jednak istnieć będzie ryzyko, że - wskutek błędnej im- plementacji - luki tego typu zaistnieją w rozwijanym sys- temie. Ochronę naszej aplikacji powinniśmy dlatego roz- wijać wielowarstwowo (tzw. defense in depth), a naszym celem powinno być z jednej strony minimalizowanie ry- zyka wystąpienia, a z drugiej - minimalizowanie strat w przypadku zaistnienia błędu. Co więc możemy zrobić, aby się lepiej zabezpieczyć? Edukacja - gdyby istniało szybkie i sprawne rozwiąza- nie problemów bezpieczeństwa, już dawno byśmy je stosowali. Droga do bezpiecznego systemu jest jednak bardziej wyboista. Po pierwsze nie możemy nigdy zapo- mnieć o tych, którzy te systemy tworzą. Brak doświad- czenia oraz szybkie tempo realizacji projektów wpływa- ją negatywnie na bezpieczeństwo aplikacji. Okresowe szkolenia poszerzają wiedzę, przypominają o prioryte- tach i redukują ryzyko wystąpienia błędów. Architektura - dobra architektura może reduko- wać straty powstałe w przypadku zaistnienia błę- dów. Często praktykowanym na poziomie bazy da- nych podejściem jest oddzielenie zadań administracji bazą od wykorzystywania bazy danych na potrzeby aplikacji. Cel taki można osiągnąć tworząc osobnego użytkownika (resource user) posiadającego upraw- nienia do zasobów takich jak tabele, widoki, pro- cedury, linki do innych baz danych, itd. Użytkownik ten udostępnia poprzez granty wybrane widoki użyt- kownikowi (connect user), przez którego realizowane są połączenia z aplikacji. Efektem takiej architektury jest ograniczenie uprawnień connect usera do nie- zbędnego minimum i uniemożliwia wykonanie opera- cji na zasobach administracyjnych bazy danych z jego poziomu. Kryptografia - dobrze wiedzieć, że istnieje również po- za stosem protokołów TLS i że można ją wykorzystać do ukrycia wrażliwych danych w systemie. W przypadku przechowywania haseł użytkowników dobrze pamiętać o jednokierunkowych funkcjach skrótu (PBKDF2, bcrypt, scrypt, koniecznie z unikalną solą, a najlepiej solą i pie- przem), a w przypadku numerów kart kredytowych - o szyfrowaniu symetrycznym w którym klucz szyfrowania przechowywany będzie poza bazą danych. Obsługa błędów - jeżeli podczas działania aplikacji wy- stąpi błąd krytyczny, serwerowi na środowisku produk- cyjnym wolno odpowiadać jedynie błędami HTTP (np. 500 - błąd serwera). Pokazywanie listingu stosu jest co prawda bardzo przydatne dla dewelopera, jednocześnie zdradza jednak szczegóły implementacyjne naszego sys- temu, co niepotrzebnie ułatwia sprawę osobie atakującej naszą aplikację. Monitoring - częste analizowanie stanu naszego syste- mu na środowisku produkcyjnym jest wskazane. Pro- ces ten można zautomatyzować integrując aplikację z systemem IDS/IPS, który odpowiednio szybko będzie w stanie zabić na alarm w momencie wykrycia ataku. Procesy - najlepsze systemy IDS/IPS niewiele dadzą, jeżeli ich działanie nie będzie przez nikogo monitoro- wane. Brak błędów bezpieczeństwa w danej wersji sys- temu nie gwarantuje, że nie pojawią się one w kolej- nej. Bezpieczeństwo to nie stan, to proces, stąd istotną rzeczą jest przedsięwzięcie odpowiednich kroków (qu- ality gates) celem ciągłego dbania o bezpieczeństwo ja- ko element jakości systemu. Dobrym pomysłem jest na przykład audyt krytycznych fragmentów kodu przepro- wadzany każdorazowo przez doświadczonych pracow- ników w ramach budowania kolejnej wersji systemu, lub uwzględnienie testów penetracyjnych w fazie testo- wania aplikacji. Marek Puchalski Od ponad 7 lat specyfikuje, imple- mentuje i wdraża systemy informa- tyczne dla największych graczy niemieckiego rynku motoryzacyj- nego. Pracownik firmy Capgemi- ni, tata siedemnastomiesięcznego Janka, gracz Go, fascynat wszel- kiej tematyki związanej z bezpie- czeństwem oprogramowania i aplikacji webo- wych.

Wrocławskie Capgemini Software Solutions Center, jako cen­trum rozwoju oprogramowania i usług in- formatycznych firmy Capgemini, specjalizuje się w rozwiązaniach IT dla znanych na całym świecie klien- tów. Centrum to współpra­cuje ściśle z niemieckimi oddziałami Capgemini. Obecnie Software Solutions Center zatrudnia niemal 600 specjalistów branży IT, którzy zajmują się głównie: · projektowaniem, tworzeniem, testowaniem oraz wdrażaniem indywidualnych rozwiązań IT w oparciu o nowoczesne technologie programistyczne, takie jak Java, .NET, platformy bazodanowe (np. Oracle), Business Intelligence oraz technologie mobilne (np. Android, iOS, Windows Phone); · projektowaniem, tworzeniem i wdrażaniem nowych rozwiązań informatycznych z obszaru SAP; · architekturą złożonych systemów IT - projektowanie zaawansowanych rozwiązań klasy enterprise oraz biznesowy konsulting dla wielu branż; · zarządzaniem projektami informatycznymi w oparciu o metodyki zwinne i klasyczne - stosowa- ne są metodyki stworzone na bazie międzynarodowych trendów (takich jak Scrum, RUP) wzbogacone o nasze doświadcze­nia; w zarządzaniu projektami opieramy się również na klasycznych standardach i dobrych praktykach (np. PMBOK, ITIL); · wsparciem funkcjonowania systemów indywidual­nych oraz SAP – wsparcie w zakresie monito- ringu systemów SAP; rozwiązywanie incydentów, problemów oraz realizacja zmian w oparciu o ITIL; wspieranie procesów w poszczególnych obszarach SAP; rozbudowywanie systemu SAP o dodatkowe funkcjonalności;

1/201418 Z e względu na wspomniany ruch wokół wła- snej osi, punkt południa przesuwa się z pręd- kością 1 stopnia na 4 minuty. Gdyby każdy człowiek na Ziemi chciał mieć na zegarku godzinę 12:00 (z dokładnością co do minuty) wtedy gdy słoń- ce jest u niego nad horyzontem w zenicie, to mieliby- śmy 1440 stref czasowych - tyle jest minut w czasie doby. Z oczywistych względów jest to niemożliwe, i w latach 1878 – 1884 zaproponowano i wprowadzo- no podział na 24 strefy czasowe oddzielone od siebie o 1h (z drobnymi wyjątkami). Ustalono że strefą „0” będzie czas dla południka „0” przebiegającym przez obecną dzielnicę Greenwich w Londynie i nazywa się go zamiennie: UT - Czas uniwersalny (ang. Univer- sal Time), GMT (ang. Greenwich Mean Time) lub UTC - uniwersalny czas koordynowany (ang. Coordinated Universal Time). Ze względów praktycznych strefy czasowe zosta- ły wyznaczone wg granic oceanów i poszczególnych państw tak, aby mniejsze z nich znalazły się w obrębie jednej strefy czasowej. Większe są położone w kilku strefach, np. Rosja (12), USA (7), Kanada (6), chociaż np. Chiny tylko w jednej. Żeby sprawa była jeszcze bardziej skomplikowana, to część krajów ma jeszcze tzw. czas letni, czyli okresowo wprowadzaną zmianę w czasie o 1h. Zostało to wpro- wadzone ze względów oszczędnościowych, żeby lepiej wykorzystać światło dzienne. Zmiany czasu letniego są organizacyjnie kłopotliwe i wiele krajów już się z niego wycofało. Strefy czasowe w Javie i bazach danych Jak pamiętamy ze szkoły podstawowej, Ziemia obraca się wokół własnej osi, co powoduje występowanie po sobie pory dnia i pory nocy. Raz na dobę ustawienie promieni słonecznych jest w zenicie (najwyższym punkcie względem horyzontu), co potocznie nazywamy „południem”. Rysunek. Strefy czasowe na świecie, źródło: Wikipedia.

www.sdjournal.pl 19 STREFY CZASOWE W JAVIE I BAZACH DANYCH lub zmienną środowiskową TZ=Europe/Warsaw. Możemy też ustawić lub zmienić aktualną strefę metodą setDefault: TimeZone.setDefault(TimeZone.getTimeZone(″Australia/ Darwin″)). Listę dostępnych identyfikatorów stref czasowych oraz ich nazwy można wyświetlić w prosty sposób: for(String tzId : TimeZone.getAvailableIDs()) { TimeZone tz = TimeZone.getTimeZone(tzId); System.out.println(String.format(″%35s -> %s″, tzId, tz.getDisplayName())); } Dalej musimy już pamiętać, że uruchomiony pro- gram ma przypisaną strefę czasową, z której korzy- stają standardowe klasy, takie jak SimpleDateFormat i Calendar. Przykład: jeżeli w naszym programie przy pomocy SimpleDateFormat wyświetlimy czas „0” to otrzymamy: System.out.println(″Strefa czasowa: " + TimeZone.getDefault().getID()); System.out.println(″Czas ′0’: ″ + new SimpleDateFormat().format(new Date(0))); ----------------------------------------------------- Strefa czasowa: Europe/Warsaw Czas ‚0’: 01.01.70 01:00 Otrzymaliśmy godzinę pierwszą („01”) a nie, jak można by się spodziewać, północ. Wynika to z tego, że metoda klasy SimpleDateFormat wykonała za nas przeliczenie do domyślnej stre- fy czasowej. Podobnie sytuacja wygląda z klasą Calendar: Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(0); System.out.println(″Godzina czasu ′0′: ″ + calendar.get(Calendar.HOUR_OF_DAY)); ----------------------------------------------------- Godzina czasu ′0′: 1 Wykonując operacje na czasie należy pamiętać, aby posługiwać się reprezentacją obiektową, np. klasą Calendar, a nie bezpośrednio reprezentacją milisekun- dową. Wykonanie poniższego kodu ilustruje problem Kiedy musimy martwić się strefami czasowymi W świecie „pozainformatycznym” z koniecznością prze- stawiania zegarków mamy do czynienia gdy podróżujemy, np. na niedaleką Ukrainę czy Litwę. Z kolei w świecie „in- formatycznym” musimy zainteresować się kwestią stref czasowych wtedy, gdy tworzymy aplikacje z których ko- rzystają klienci w więcej niż jednej strefie czasowej. W dalszej części tego artykułu opiszę sposób rozwią- zania tego problemu na realnym przykładzie. Jak działają strefy czasowe w systemie operacyjnym Współczesne systemy operacyjne, np. Linux, posiadają dane pozwalające na prezentowanie czasu zegara syste- mowego w strefie czasowej wybranej przez użytkowni- ka, najczęściej w czasie instalacji systemu operacyjnego, automatycznie uwzględniając w tym czas letni. $ date pią, 29 lis 2013, 16:09:42 CET „CET” oznacza czas środkowoeuropejski (ang. Central European Time). Ponadto możemy wyświetlić aktualny czas w innej strefie czasowej: $ TZ=″America/Chicago″ date pią, 29 lis 2013, 09:10:09 CST „CST” oznacza czas centralny (ang. Central Standard Time). Możemy również poprosić o wyświetlenie za- danej daty i w odpowiedzi otrzymamy informacje o obowiązującym wtedy czasie: $ date --date ″2013-09-01 00:00:00″ nie, 1 wrz 2013, 00:00:00 CEST „CEST” oznacza czas środkowoeuropejski letni (ang. Central European Summer Time). System operacyjny potrafi zwrócić też czas w mili- sekundach od 1970.1.1 00:00:00 UTC co dalej będzie istotne: $ date +%s%3N 1385738304763 Jak działają strefy czasowe w Javie W javie metoda System.currentTimeMillis() zwraca czas w milisekundach od 1970.1.1 00:00:00 UTC, iden- tycznie jak ostatnia komenda date +%s%3N systemu ope- racyjnego Linux. Uruchomiony program w JVM automatycznie otrzy- muje domyślną strefę czasową z systemu operacyjnego, np. „Europe/Warsaw”. Możemy ją zmienić przez argument przekazywany do JVM -Duser.timezone=Europe/Warsaw Tip Trzeba pamiętać, że na liście dostępnych stref cza- sowych są także takie z identyfikatorami trzylitero- wymi, ale istnieją one tylko dla zapewnienia zgod- ności wstecz i nie powinny być używane.

1/201420 jaki może się pojawić gdy korzystamy z reprezentacji milisekundowej. Timestamp ts = Timestamp.valueOf(″2013-10-26 12:00:00″); System.out.println(″Data początkowa: ″ + ts); Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(ts.getTime()); cal.add(Calendar.DAY_OF_MONTH, 1); System.out.println(″Dzień później (calendar): ″ + new Timestamp(cal.getTimeInMillis())); System.out.println(″Dzień później (millis): ″ + new Timestamp(ts.getTime() + 24 * 60 * 60 * 1000)); ----------------------------------------------------- Data początkowa: 2013-10-26 12:00:00.0 Dzień później (calendar): 2013-10-27 12:00:00.0 Dzień później (millis): 2013-10-27 11:00:00.0 Na tym przykładzie widać że „jeden dzień później” to nie zawsze to samo co „24 godziny później”, ponieważ w nocy z 26 na 27 października 2013 jest zmiana czasu letniego na zimowy i godziny 12:00 obydwu tych dni są oddalone od siebie o 13 a nie 12 godzin. Metody klasy Calendar skutecznie obsługują takie przypadki brzegowe. Strefy czasowe w bazach danych Każda baza danych potrafi przechowywać dane typu „data z czasem”, potocznie zwane timestamp’em. Więk- szość baz danych ma też różne typy pól „daty z cza- sem” w zależności od tego czy mają one obsługiwać strefy czasowe czy nie. Pola bez obsługi stref czasowych działają jak zwykłe napisy, tzn. jeżeli wstawimy do bazy danych wartość „1970-01-02 01:00:00” to niezależnie od tego w jakiej strefie czasowej będzie działać klient bazy danych to za- wsze otrzymamy tą samą wartość. Pola z obsługą stref czasowych działają w oparciu o in- formacje w jakiej strefie czasowej działa program klienc- ki podłączony to bazy danych, terminal konsolowy lub aplikacja Java. Gdy klient jest połączony w strefie cza- sowej „Europe/Warsaw” (UTC+1) i zapisze do bazy danych datę „1970-01-02 01:00:00” a następnie zmieni strefę czasową na „America/Chicago” (UTC-6) to od- czyta tą samą datę ale, dla nowo wybranej strefy czaso- wej, czyli „1970-01-01 18:00:00”. W tym przypadku nie ma znaczenia w jakiej strefie czasowej działa silnik bazy danych ani system operacyjny na którym jest zainstalo- wany. Przyjrzyjmy się szczegółowo jak to wygląda w konkret- nych bazach danych. MySQL MySQL ma dwa typy pól pozwalających na przechowy- wanie daty z czasem: • datetime – nie interpretuje strefy czasowej. • timestamp – przy zapisie czas jest konwertowany do UTC i tak trzymany w bazie, a następnie przy wy- świetleniu konwertowany do aktualnej strefy czaso- wej klienta. Poniższy przykład ilustruje różnice w działaniu obydwu typów danych: mysql> set time_zone = ″+01:00″; mysql> insert into field_type_test (datetime_field, timestamp_field) values (″1970- 01-02 01:00:00″, ″1970-01-02 01:00:00″); mysql> select * from field_type_test; +---------------------+---------------------+ | datetime_field | timestamp_field | +---------------------+---------------------+ | 1970-01-02 01:00:00 | 1970-01-02 01:00:00 | +---------------------+---------------------+ mysql> set time_zone = ″-06:00″; mysql> select * from field_type_test; +---------------------+---------------------+ | datetime_field | timestamp_field | +---------------------+---------------------+ | 1970-01-02 01:00:00 | 1970-01-01 18:00:00 | +---------------------+---------------------+ Podejmując decyzje co do wyboru typu pola na datę z czasem trzeba też zwrócić uwagę na możliwy do prze- chowywania zakres dat: • datetime – od „1000-01-01 00:00:00” do „9999-12- 31 23:59:59” • timestamp – od „1970-01-01 00:00:01” UTC do „2038-01-19 03:14:07” UTC I jeszcze jedno związane z tym ostrzeżenie: przy pró- bie wstawienia daty spoza dopuszczalnego zakresu przez JDBC otrzymamy wyjątek „MysqlDataTruncation: Data truncation: Incorrect datetime value”. Jednak przy próbie wstawienia niepoprawnej wartości z linii komend polecenie się powiedzie, ale zamiast spodziewanej war- tości zostanie tam umieszczona wartość „0000-00-00 00:00:00” oznaczająca „błąd”. Na wyjściu konsoli zoba- czymy obok standardowego „Query OK, 1 row affected” dodatkowy napis „1 warning”, który łatwo pominąć. PostgreSQL Podobnie jak MySQL, także PostgreSQL ma dwa typy pól na datę z czasem o analogicznych funkcjach i zasto- sowaniu tylko o innych nazwach: • timestamp without time zone – nie interpretuje strefy czasowej

www.sdjournal.pl 21 STREFY CZASOWE W JAVIE I BAZACH DANYCH • timestamp with time zone – interpretuje strefy cza- sowe Inne są także zakresy obsługiwanych wartości, ponie- waż obydwa pola pozwalają na zapisanie wartości od od „-4713-12-31 23:59:59” UTC do „294276-12-31 23:59:59” UTC, więc zapas jest spory. Oracle W Oracle jest najwięcej typów danych służących do re- prezentowania daty z czasem: • date – typ uproszczony, z dokładnością tylko do se- kund, nie interpretuje strefy czasowej. • timestamp – przechowuje części sekundy z dokład- nością do 9 miejsc po przecinku, nie interpretuje strefy czasowej. • timestamp with time zone – jak timestamp, tylko że zawiera informacje o strefie czasowej ale nie doko- nuje żadnych przeliczeń. • timestamp with local time zone – interpretuje strefy czasowe, tzn. dokonuje przeliczeń do strefy czasowej klienta. DB2 DB2 w wersji Express-C 10.5 oraz wersjach komer- cyjnych do wersji 9.x posiada tylko jeden typ pola timestamp, który nie interpretuje strefy czasowej. W dokumentacji DB2 jest zapowiedź nowego typu pola timestamp with time zone od wersji 10.5 dla systemu z/ OS, ale nie miałem możliwości jej przetestowania. Porównanie zachowania pól timestamp w bazie Załóżmy, że łączymy się klientem konsolowym w strefie czasowej Europe/Warsaw (UTC+1) i wpisujemy do ko- lumn każdego typu datę 1970-01-02 01:00:00. Następnie rozłączamy się, łączymy ponownie, ale w strefie Ameri- ca/Chicago (UTC-6) i odczytujemy wynik. Zachowanie oczekiwane jest takie, że w typach z przeliczaniem stref czasowych otrzymamy godzinę 18:00 a bez strefy 1:00 (tak jak zapisaliśmy). Tabela 1 prezentuje typy pól w poszczególnych bazach i otrzy- many wynik. Jak widzimy, wszystkie operacje wykonywane przez terminal są zgodne z oczekiwaniami. Obsługa stref czasowych przez JDBC Do tego momentu wszystko powinno być zrozumiałe i dosyć spójne, niestety przez różne implementacje ste- rowników JDBC, dostęp z kodu aplikacji Java do tak za- pisanych danych nieco się komplikuje. Zacznijmy od wyjaśnienia działania klasy java.sql.Time- stamp do reprezentowania daty z czasem pobranej z ba- zy. Załóżmy że w bazie danych mamy zapisaną datę „0” bez sprecyzowania strefy czasowej, np.: Table ″public.timestamp_test″ Column | Type | Modifiers --------+-----------------------------+----------- id | integer | not null test | timestamp without time zone | select * from timestamp_test ----------------------------------------------------- 1 1970-01-01 00:00:00.0 Po jej pobraniu przez JDBC metodą ResultSet. getTimestamp(...) do obiektu Timestamp intuicyjnie wy- dawałoby się że przechowuje on datę ″0″. I tak właśnie wyświetli się ona po wywołaniu metody getString. Jednakże gdy sprawdzimy „wewnętrzną” reprezenta- cję przy pomocy getTime() to nie otrzymamy spodzie- wanego ″0″: Timestamp timestamp = rs.getTimestamp(1); System.out.println(″Timestamp ′0’ toString(): ″ + timestamp); System.out.println(″Timestamp ′0’ time: ″ + timestamp.getTime()); System.out.println(″Timestamp ′0’ sformatowany: ″ + DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.FULL). format(timestamp)); ----------------------------------------------------- Timestamp ′0’ toString(): 1970-01-01 00:00:00.0 Timestamp ′0’ time: -3600000 Tabela 1. Timestamp bez strefy czasowej Timestamp ze strefą czasową Inne MySQL datetime 1970-01-02 01:00:00 timestamp 1970-01-01 18:00:00 PostgreSQL timestamp without time zone 1970-01-02 01:00:00 timestamp with time zone 1970-01-01 18:00:00-06 Oracle timestamp 1970-01-02 01:00:00.000000 timestamp with local time zone 1970-01-01 18:00:00.000000 date 1970-01-02 01:00:00 timestamp with time zone 1970-01-02 01:00:00.000000 +01:00 DB2 timestamp 1970-01-02-01.00.00 -

1/201422 Timestamp ′0’ sformatowany: czwartek, 1 styczeń 1970 00:00:00 CET Co więc się stało? Otóż sterownik JDBC (w tym przypadku do PostgreSQL) po pobraniu daty z bazy „cofnął” nam ją wg aktualnej strefy czasowej, tak aby przy „wyświetleniu” z powrotem reprezentowała do- kładnie tą samą wartość co w bazie. Wracając do kwestii komunikacji z poszczególnymi ba- zami danych: jeżeli chcemy skorzystać z zalet wykorzy- stania pól przeliczających strefy czasowe to klient Java musi poinformować bazę danych w jakiej strefie czaso- wej działa – podobnie jak w przypadku testu z bezpo- średnim dostępem do bazy. Niestety w przypadku każ- dej bazy danych działa to nieco inaczej. MySQL Korzystając z MySQL wystarczy że użyjemy pole- cenia TimeZone.setDefault(...) i data z pola typu ″timestamp″ (to obsługujące strefy) będzie przeliczana. Niestety, to ustawienie skutkuje także przeliczeniem daty z pola ″datetime″, które nie powinno obsługiwać przeliczeń. PostgreSQL Korzystając z PostgreSQL samo ustawienie domyślnej strefy nie wystarczy, trzeba posługiwać się metodami które przy zapisie i odczycie dat z czasami przekazują dodatkową informacje o strefie czasowej: PreparedStatement.setTimestamp(columnIndex, timestamp, calendar) ResultSet.getTimestamp(columnIndex, calendar) Oracle Korzystając z Oracle sytuacja jest najbardziej skompli- kowana: • Należy ustawić domyślną strefę czasową polece- niem TimeZone.setDefault(...) • Nie wolno korzystać z metod, które przy zapisie i odczycie dat z czasami przekazują dodatkową infor- macje o strefie czasowej (inaczej niż w PostgreSQL) • Należy skorzystać z dodatkowej metody specyficz- nej dla Oracle ustawiającej strefę czasową bezpo- średnio na obiekcie Connection: OracleConnection.setSessionTimeZone(timeZoneName); DB2 W DB nie ma rozwiązań specyficznych, bo nie ma typu pola w bazie, które by wspierało przeliczanie stref cza- sowych. Porównanie zachowania pól timestamp w aplikacji Java Tabela 2 porównuje wyniki testu analogicznego do wy- konanego poprzednio bezpośrednio na bazie, tylko teraz przy pomocy aplikacji Java. Wpisaliśmy do kolumn każ- dego typu datę 1970-01-02 01:00:00 w strefie czasowej Europe/Warsaw (UTC+1) a następnie odczytujemy wy- nik z aplikacji Java w strefie America/Chicago (UTC-6) z zastosowaniem metod opisanych wyżej. Jak widać, baza danych MySQL w przypadku pobiera- nia przez JDBC błędnie aplikuje przesunięcie strefy cza- sowej w odniesieniu do pola typu które nie powinno mieć takiego przesunięcia. Wiedząc o tym trzeba staran- nie stosować odpowiednie typy pól i ustawianie domyśl- nej strefy czasowej w zależności od tego jakiego efektu potrzebujemy. Pozostałe bazy danych nie mają tego błędu i przesu- nięcia są aplikowane tylko na właściwych typach kolumn. Implementacja systemów globalnych Załóżmy, że mamy do implementacji globalny system au- kcyjny, w którym każdy może wystawić przedmiot, np. Kowalski z Warszawy o godzinie 11:00, i każdy może go licytować, np. Kowalsky z Chicago. Funkcjonalnie mu- si to działać tak, że Kowalsky z Chicago widzi że au- kcja rozpoczęła się o 4:00 rano jego czasu lokalnego. Opcjonalnie może widzieć, że była to godzina 11:00 cza- su środkowo europejskiego, żeby nie dziwić się dlaczego sprzedawca nie śpi po nocach. Opisane powyżej trudności z korzystaniem przelicza- nia stref czasowych przy pomocy bazy danych skła- niają do rozwiązania nie polegającego na przeliczaniu Tabela 2. Timestamp bez strefy czasowej Timestamp ze strefą czasową Inne MySQL Datetime 1970-01-01 18:00:00 Timestamp 1970-01-01 18:00:00 PostgreSQL timestamp without time zone 1970-01-02 01:00 timestamp with time zone 1970-01-01 18:00 Oracle timestamp 1970-01-02 01:00 timestamp with local time zone 1970-01-02 18:00:00 date 1970-01-02 01:00:00 timestamp with time zone 1970-01-02 18:00:00 DB2 timestamp 1970-01-02 01:00:00.0 -

www.sdjournal.pl 23 STREFY CZASOWE W JAVIE I BAZACH DANYCH stref przez bazę danych. Szczególnie korzystanie w me- tody TimeZone.setDefault(...) i OracleConnection. setSessionTimeZone(...) w aplikacjach JEE, czyli wie- lowątkowych i korzystających z połączeń pobieranych z puli serwera aplikacyjnego jest proszeniem się o pro- blemy. Najprostszym sposobem implementacji jest założenie, że domyślnie aplikacja działa w czasie GMT i tak zapisuje dane do bazy. Żeby użytkownikowi wyświetlić dane w jego stre- fie czasowej, musimy ją najpierw „zgadnąć”. Określe- nie to jest nieprzypadkowe, ponieważ nie ma standar- dowych mechanizmów, które pozwalałyby aplikacji po stronie serwera ustalić w jakiej strefie czasowej pracuje przeglądarka WWW klienta, np. poprzez na- główki protokołu HTTP. Jednym z rozwiązań jest posłużenie się usługą „IP ad- dress geocoding”, która na podstawie adresu IP zwraca dane o położeniu geograficznym i strefie czasowej. Nie- stety tego typu usługi są płatne. Aby obejść ten problem można posłużyć się prostym kodem JavaScript: var myDate = new Date(); myDate.getTimezoneOffset(); Zwraca on informacje o przesunięciu aktual- nej strefy czasowej systemu operacyjnego klien- ta względem czasu GMT wyrażony w minutach, np. dla Polski jest to „-60” w czasie zimowym i „-120” w czasie letnim. Żeby poprawnie zinterpretować, z którym czasem mamy aktualnie do czynienia nale- ży dokonać dodatkowych obliczeń na stworzonych dwóch dodatkowych datach: jednej w styczniu (zi- mowy) i jednej w lipcu (letni) i porównanie przesu- nięć z datą bieżącą. Ale łatwiej jest skorzystać z małej biblioteki JS pt. „jsTimezoneDetect” (http://pellepim.bitbucket.org/jstz/), w której to już zostało zrobione, a ponadto obsłu- ga konwersji przesunięcia wyrażonego w minutach na identyfikatory stref czasowych rozpoznawanych przez Javę. Niedoskonałość biblioteki polega na tym że jeżeli dla jednego przesunięcia, np. „+1h” istnieje kilka stref czasowych, to ona podpowie tylko jedną z nich. Nie jest to przeszkodą, jeżeli pozwolimy ją użytkownikowi potwierdzić albo ewentualnie zmienić. Przykład dzia- łania wygląda tak: var timezone = jstz.determine(); timezone.name(); ----------------------------------------------------- ″Europe/Berlin″ Jak widać na tym przykładzie, biblioteka popraw- nie zgadła przesuniecie +1h, ale przyporządkowała mu strefę czasową ″Europe/Berlin″ zamiast ″Europe/ Warsaw″ - trudno. Taką informacje musimy przesłać z przeglądarki do serwera i zapamiętać w sesji użytkow- nika. Gdy już znamy strefę czasową klienta, wszystkie daty, które chcemy mu wyświetlić musimy przeliczyć z GMT (tak są zapisane w bazie) na jego czas lokalny: Calendar gmtCalendar = Calendar.getInstance(TimeZone. getTimeZone(″GMT″)); PreparedStatement ps = c.prepareStatement(″select date_gmt from gmt_test″); ResultSet rs = ps.executeQuery(); if(rs.next()) { Timestamp timestampGmt = rs.getTimestamp(1, gmtCalendar); System.out.println(″Czas GMT: ″ + timestampGmt); // konwersja do czasu ″Europe/Warsaw″: TimeZone tz = TimeZone.getTimeZone(″Europe/ Warsaw″); Calendar calendar = Calendar.getInstance(tz); calendar.setTimeInMillis(timestampGmt.getTime()); System.out.format(″Czas lokalny: %tc\n″, calendar); // konwersja do czasu ″Europe/Chicago″: tz = TimeZone.getTimeZone(″America/Chicago″); calendar = Calendar.getInstance(tz); calendar.setTimeInMillis(timestampGmt.getTime()); System.out.format(″Czas lokalny: %tc\n″, calendar); } ----------------------------------------------------- Czas GMT: 1970-01-02 00:00:00.0 Czas lokalny: Pt sty 02 01:00:00 CET 1970 Czas lokalny: Cz sty 01 18:00:00 CST 1970 Żeby z kolei datę w lokalnej strefie czasowej wprowa- dzoną przez użytkownika zapisać do bazy danych w strefie GMT należy: Calendar gmtCalendar = Calendar.getInstance(TimeZone. getTimeZone(″GMT″)); PreparedStatement ps = c.prepareStatement(″UPDATE gmt_test SET date_gmt = ?″); SimpleDateFormat df = new SimpleDateFormat(″yyyy- MM-dd HH:mm″); df.setCalendar(Calendar.getInstance(TimeZone. getTimeZone(″Europe/Warsaw″))); Date date = df.parse(″1970-01-02 01:00″); // to czas wprowadzony przez usera

1/201424 System.out.println(″Czas użytkownika: ″ + date. toString()); ps.setTimestamp(1, new Timestamp(date.getTime()), gmtCalendar); ps.executeUpdate(); ----------------------------------------------------- Czas użytkownika: Fri Jan 02 00:00:00 GMT 1970 Jak widać, zmienna date jest już w domyślnej strefie GMT. Ciekawe problemy Niebanalnym problemem do rozwiązania jest kwestia ustalania czasu zdarzeń „systemowych”, czyli nie związa- nych z sesją konkretnego użytkownika. Przykładami ta- kich zdarzeń mogą być: • publikacja / archiwizowanie aktualności, • zakończenie czasu trwania promocji, • zakończenie terminu opłacenia usługi. Gdyby uzależnić takie zdarzenia od czasu użytkownika, to on zmieniając strefę czasową mógłby „gonić promo- cje”. W systemach globalnych najprostszym sposobem uniknięcia takich problemów i nieporozumień z klienta- mi jest wykonywanie zmian statusów takich obiektów przez crona systemowego działającego w strefie GMT i wyraźne informowanie swoich użytkowników, że tak to działa. Podsumowanie Jak widać, obsługa stref czasowych w aplikacjach global- nych nie jest taka prosta jak by można początkowo są- dzić. Mam nadzieję że niniejszy artykuł wyjaśnił podsta- wowe kwestie w tym zakresie. W ramach podsumowania warto jeszcze zaznaczyć że użyteczność klas związanych z obsługą dat w JDK (Date, Calendar) jest mało wygodna i powstały biblioteki, które to nieco ułatwiają, np. Joda-Time (http://www.joda.org/joda- time/). Także w JDK 8 zapowiadany jest nowy pakiet java. time, który ma ułatwić operacje na typach daty i czasu. Marek berkan Marek Berkan, Zastępca Dyrekto- ra ds. Realizacji w firmie e-point S.A. Odpowiada za proces im- plementacji systemów informa- tycznych, a następnie za ich dłu- gofalowy rozwój i utrzymanie. Od 13 lat programuje w J2EE, od 8 lat pracuje nad dynamicznie rozwija- jącym się projektem w technologii J2EE, koor- dynując prace zespołu programistów. Użyteczne komendy Oracle: • Ustawienie TZ klienta: TZ=America/Chicago sqlplus … • Ustawienie TZ całej bazy: ALTER DATABASE SET TIME _ ZONE = ″-06:00″; • Sprawdzenie TZ klienta: SELECT SESSIONTIMEZONE FROM DUAL; • Ładne formatowanie pola typu timestamp: ALTER SESSION SET nls _ timestamp _ format = ″yyyy-mm-dd hh24:mi:ss″; • Ładne formatowanie pola typu timestamp ze strefą czasową: ALTER SESSION SET nls _ timestamp _ tz _ format = ″yyyy-mm-dd hh24:mi:ss.ff tzh:tzm″; • Ładne formatowanie pola typu date z czasem: ALTER SESSION SET nls _ date _ format = ″yyyy-mm-dd hh24:mi:ss″; MySQL: • Ustawienie TZ klienta: SET TIME ZONE = ″+01:00″; • Sprawdzenie TZ klienta: SELECT @@global.time _ zone, @@session.time _ zone; PostgreSQL: • Ustawienie TZ klienta: SET TIME ZONE ″America/Chicago″; • Sprawdzenie TZ klienta: SHOW TIMEZONE;