28 grudnia 2007

Prezentacja dużych tabel na stronach HTML

W czym tkwi problem? Otóż chciałbym mieć komponent, który umożliwi prezentację dużej tabeli na stronie HTML, przy czym tabela ta, jest zarówno bardzo długa, jak i bardzo szeroka. Jedną z możliwości, właśnie tą, o której dziś będę pisał jest wyświetlanie tylko pewnego "okna" tej tabeli wraz z nagłówkami widocznych wierszy i kolumn. Naturalnie trzeba też umożliwić przewijanie, tj. zmianę pozycji "okna". Jeden rysunek wart jest tysiąca słów, a więc ideę tę prezentuję poniżej w postaci graficznej:


Zatem zamiast wyświetlać całą tabelę wyświetlamy tylko pewien jej wycinek i dodajemy guziki do przewijania. Powinniśmy także prezentować informację o aktualnie wyświetlanym fragmencie, czyli np. jak to pokazano na powyższej ilustracji napis "2-4 / 5" co oznacza, że aktualnie wyświetlone są kolumny od 2. do 4. a cała tabela ma 5 kolumn.

Moim ostatecznym celem jest implementacja komponentu JSF, ewentualnie w postaci kompozycji Facelets, ale w dzisiejszym artykule poprzestanę na warstwie prezentacji, czyli JavaScript + HTML = DHTML. W jaki sposób zrealizujemy mechanizm ukrywania kolumn i wierszy, które nie należą do wyświetlanego "okna"? Naturalnie poprzez dodanie stylu style="display: none;" do odpowiednich elementów <tr> i <td>. Przewijanie "okna" zaimplementujemy przy pomocy jednej klasy JavaScript. Przy okazji polecam świetny artykuł o klasach w języku JavaScript – "3 ways to define a JavaScript class". JavaScript będzie użyty tylko do zmiany pozycji "okna" poprzez odpowiednie żonglowanie wartościami display, stylów elementów <tr> i <td> natomiast inicjalizacja wstępnej pozycji "okna" musi być zakodowana w tabeli HTML. Tabela zaprezentowana uprzednio na ilustracji (z dodanymi elementami <input type="checkbox" /> w komórkach) powinna więc wyglądać tak:

<table id="myTable">
<tr>
<td>&nbsp;</td>
<td style="display:none;">Arek</td>
<td>Borys</td>
<td>Czarek</td>
<td>Darek</td>
<td style="display:none;">Ewa</td>
</tr>
<tr style="display:none;">
<td>1</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
<tr style="display:none;">
<td>2</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
<tr>
<td>3</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
<tr>
<td>4</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
<tr>
<td>5</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
<tr style="display:none;">
<td>6</td>
<td style="display:none;"><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td><input type="checkbox" /></td>
<td style="display:none;"><input type="checkbox" /></td>
</tr>
</table>

Aby kod JavaScript, który za chwilkę zaprezentuję działał poprawnie ważne jest żeby określić styl style="display:none;" dla wszystkich elementów <td> z ukrytej kolumny tabeli, nawet wtedy gdy dana komórka znajduje się w ukrytym wierszu. Wiersz może bowiem stać się widoczny w skutek przewijania "okna" w górę lub w dół, natomiast wszystkie komórki danej kolumny powinny pozostać ukryte. A oto klasa JavaScript implementująca przewijanie tabeli:

function TableRewinder
(tableName, vCols, vRows, leftOffset, topOffset, leftHeader, topHeader) {

// wartość atrybutu id tabeli, na której operujemy
this.tableName = tableName;

// ile kolumn, wliczając okno i nagłówek ma tabela
this.visibleCols = vCols;

// ile wierszy, wliczając okno i nagłówek ma tabela
this.visibleRows = vRows;

// przesunięcie okna względem pierwszej kolumny treści tabeli
this.leftOffset = leftOffset;

// przesunięcie okna względem pierwszego wiersza treści tabeli
this.topOffset = topOffset;

// ile kolumn tabeli stanowi lewy nagłówek
this.leftHeader = leftHeader;

// ile wierszy tabeli stanowi górny nagłówek
this.topHeader = topHeader;

this.getTable = function() {
return document.getElementById(this.tableName);
};

this.visibleLeftmost = function() {
return this.leftHeader + this.leftOffset;
};

this.visibleRightmost = function() {
return this.leftOffset + this.visibleCols - 1;
};

this.visibleTopmost = function() {
return this.topHeader + this.topOffset;
};

this.visibleBottommost = function() {
return this.topOffset + this.visibleRows - 1;
};

this.canGoLeft = function() {
return this.visibleLeftmost() > this.leftHeader;
};

this.canGoRight = function() {
return this.visibleRightmost() < this.getTable().rows[0].cells.length - 1;
};

this.canGoUp = function() {
return this.visibleTopmost() > this.topHeader;
};

this.canGoDown = function() {
return this.visibleBottommost() < this.getTable().rows.length - 1;
};

this.toggleRows = function(hideRowNum, showRowNum) {
this.getTable().rows[hideRowNum].style.display = 'none';
this.getTable().rows[showRowNum].style.display = '';
};

this.toggleColumns = function(hideColNum, showColNum) {
for(i = 0 ; i < this.getTable().rows.length; i++) {
rowColumns = this.getTable().rows[i].cells;

rowColumns[hideColNum].style.display = 'none';
rowColumns[showColNum].style.display = '';
}
};

this.rewindLeft = function() {
if(this.canGoLeft()) {
this.toggleColumns(this.visibleRightmost(), this.visibleLeftmost() - 1);

this.leftOffset -= 1;
}
};

this.rewindRight = function() {
if(this.canGoRight()) {
this.toggleColumns(this.visibleLeftmost(), this.visibleRightmost() + 1);

this.leftOffset += 1;
}
};

this.rewindUp = function() {
if(this.canGoUp()) {
this.toggleRows(this.visibleBottommost(), this.visibleTopmost() - 1);

this.topOffset -= 1;
}
};

this.rewindDown = function() {
if(this.canGoDown()) {
this.toggleRows(this.visibleTopmost(), this.visibleBottommost() + 1);

this.topOffset += 1;
}
};
}

Kod jest nieco rozwlekły, ale to dobrze robi jego czytelności. Znaczenie poszczególnych parametrów konstruktora klasy (funkcji TableRewinder) zostało opisane nad odpowiadającymi im zmiennymi klasowymi. Jak widać, klasa działa również z tabelami, które mają liczbę wierszy czy kolumn nagłówka inną niż 1. Poniżej kod, który utworzy instancję odpowiednią dla przedstawionej powyżej tabeli HTML oraz przykład użycia tej instancji:

var myTableRewinder = new TableRewinder('myTable', 4, 4, 1, 2, 1, 1);

<span onclick="myTableRewinder.rewindLeft()">lewo</span>
<span onclick="myTableRewinder.rewindRight()">prawo</span>
<span onclick="myTableRewinder.rewindUp()">góra</span>
<span onclick="myTableRewinder.rewindDown()">dół</span>

Po złożeniu zaprezentowanych elementów w jedną stronę DHTML otrzymujemy poniższy efekt. Nie ładny, bo bez odpowiednich CSS’ów, ale działa! Jak dobrze pójdzie w jednym z kolejnych artykułów opakuje to w komponent JSF, który wygeneruje coś ładniejszego.

17 grudnia 2007

Jestem magistrem

Dziś jest dla mnie dzień wyjątkowy. O godzinie 15 z minutami ukończyłem studia broniąc – na ocenę bardzo dobrą – pracę magisterską i uzyskując tytuł Magistra Informatyki na wydziale Matematyki, Informatyki i Mechaniki Uniwersytetu Warszawskiego. Co prawda z opóźnieniem półtora rocznym, ale zważywszy na fakt, że od początku czwartego roku studiów pracowałem zawodowo nie jest to wynik słaby, tym bardziej gdy weźmie się pod uwagę, jak wielu moich kolegów z wydziału nie zdobyło się na ten ostatni wysiłek. No ale nie trudno to zrozumieć, dobra praca wyjątkowo skutecznie studzi zapał do aktywności uniwersyteckiej. W tym miejscu chciałem serdecznie podziękować mojemu promotorowi prof. Janowi Madeyowi. Dziękuję Panie Profesorze za poświęcony czas i energię!

9 grudnia 2007

Uwierzytelnianie i autoryzacja w Acegi Security

Zajmuję się ostatnio nieco zabezpieczaniem różnego rodzaju zasobów IT, w szczególności aplikacji WWW i im więcej to robię tym więcej zbiera mi się na krytykę standardów przemysłowych, które do tego służą. Postanowiłem podzielić się moim postrzeganiem tematu, ale zanim to zrobię ugruntuję nieco swą wiedzę i przygotuje podstawę do dyskusji. Mówiąc standardy przemysłowe mam na myśli de-facto-standardy, czyli np. Acegi Security i mechanizmy popularnych serwerów aplikacyjnych, np. BEA WebLogic Server. W dzisiejszym artykule opiszę, jak wygląda architektura uwierzytelniania i autoryzacji w Acegi. Będzie to jednak opis nie konstruktywny w tym sensie, że nie będę opisywał konfiguracji i nie pokażę przykładu – to już zostało zrobione przez Michała Gołackiego w artykule "Acegi - Uwierzytelnianie i Autoryzacja w Springu".

Całość składa się z trzech zasadniczych części: mechanizmu odpowiedzialnego za uwierzytelnienie, który tworzy kontekst bezpieczeństwa, mechanizmu, który przechowuje ten kontekst oraz mechanizmu autoryzacji, który w trakcie działania aplikacji z tego kontekstu korzysta. Zacznijmy od mechanizmu przechowującego kontekst i tego, co to jest ten kontekst.

Klasą, która przechowuje kontekst bezpieczeństwa jest org.acegisecurity.context. SecurityContextHolder. Najciekawszą z punktu widzenia tego artykułu metodą wspomnianej klasy jest statyczna metoda getContext(), która udostępnia obiekt implementujący interfejs org.acegisecurity.context.SecurityContext, czyli właśnie rzeczony kontekst. Interfejs ten definiuje metodę getAuthentication(), która zwraca z kolei obiekt implementujący interfejs org.acegisecurity.Authentication. I o to właśnie chodzi, jest to bodaj najważniejszy obiekt w całej tej zabawie. Obiekt ten tworzony jest w pewnych nieinteresujących nas w tej chwili okolicznościach i początkowo zawiera dane uwierzytelniające, np. login i hasło. Następnie, ów obiekt przekazywany jest do metody authenticate(Authentication authentication) klasy implementującej interfejs org.acegisecurity.AuthenticationManager. Jak zwykle, to jaka to jest klasa zależy od konfiguracji i jest to punkt w którym możemy podłączyć nasz mechanizm uwierzytelniania. AuthenticationManager weryfikuje dane uwierzytelniające, np. login i hasło i jeśli są one poprawne wypełnia obiekt Authentication danymi określającymi uprawnienia uwierzytelnionej tożsamości, w przeciwnym wypadku rzuca wyjątek. Uprawnienia te to tablica obiektów implementujących interfejs org.acegisecurity.GrantedAuthority i w typowej sytuacji odpowiada to tablicy nazw ról. Zauważmy, że oznacza to pobranie zestawu uprawnień danej tożsamości w momencie uwierzytelnienia. A co, gdy zestaw uprawnień zmienia się w czasie, ale nie w prostej zależności od czasu? Nie twierdzę, że nie da się zaimplementować takiego scenariusza dla Acegi, ale jest to ewidentnie droga wbrew jego architekturze, która jest zwyczajnie nie dość elastyczna. Przyjrzyjmy się teraz autoryzacji.

Autoryzacja przeprowadzana jest przez klasę implementującą interfejs org.acegisecurity. AccessDecisionManager, a dokładnie, przez jej metodę decide(Authentication authentication, Object secureObject, ConfigAttributeDefinition config). Naturalnie to, jaka klasa jest użyta zależy od konfiguracji i możemy w to miejsce podstawić własną implementację mechanizmu autoryzacji. Metoda ta nie zwraca jednak typu logicznego, który określa decyzję o dostępie. Kluczowy jest tutaj parametr secureObject, który de facto jest akcją, dla której wykonujemy autoryzację. Niestety typ parametru nie mówi nam nic o tym obiekcie, ale może to być np. implementacja interfejsu org.aopalliance.intercept.MethodInvocation. Interfejs ten w szczególności definiuje metodę proceed(), którą należy wywołać, jeśli autoryzacja się powiedzie. Jak dla mnie przedziwne rozwiązanie, ale zapewne były jakieś powody aby zrobić to w ten właśnie sposób. Stosując jedną z typowych konfiguracji wykorzystującą klasy dostarczane przez Acegi, wywołanie wspomnianej metody decide(…) interfejsu AccessDecisionManager możemy sprowadzić do wywołania metody vote(…) klasy org.acegisecurity.vote.RoleVoter z tymi samymi parametrami. Klasa ta po prostu sprawdza czy tablica obiektów GrantedAuthority zapisanych w obiekcie Authentication zawiera uprawnienia wymagane do wykonania danej akcji.

Ciekaw jestem, jaka jest wasza opinia o Acegi Security. Ja osobiście uważam, że nie jest to rozwiązanie dostosowane do środowisk korporacyjnych. Prosty model autoryzacji dla pojedynczej aplikacji, oparty na dobrze zdefiniowanych rolach owszem, ale nie wiele więcej.

3 grudnia 2007

Dynamiczne proxy i adnotacje, czyli dobrana para

Dzisiaj dodam nieco smaczku (dla zaostrzenia apetytu) do rozwiązania, które opisałem ostatnio w artykule "Mechanizm dynamicznego proxy z Reflection API". Zmodyfikuję pokazany tam kod w ten sposób, by metody proxy odczytywały adnotację umieszczoną na interfejsie klasy biznesowej i wykonywały akcję na podstawie wartości parametrów tejże adnotacji. Zacznę od pokazania jeszcze raz interfejsu i klasy, dla których budujemy proxy, ale tym razem ze wspomnianą adnotacją. Oto one:

public interface MyService {

@MyAnnotation(“my annotation value”)
public void myOperation();
}

public class MyServiceBean implements MyService {

public void myOperation () {
System.out.println("Message from myOperation()");
}
}

Naturalnie użyta tu adnotacja MyAnnotation nie jest jakąś magiczną adnotacją, musimy ją sami zdefiniować tak jak definiujemy klasy i interfejsy. Oto odpowiedni kod:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}

Podkreślę tutaj, iż to dzięki temu, że jedyny parametr naszej adnotacji nazywa się 'value' możemy używać notacji skróconej i zamiast @MyAnnotation(value = "my annotation value") pisać @MyAnnotation("my annotation value") tak jak to pokazałem w przykładzie na początku artykułu. Naturalnie, jakby komuś było za mało można dodać kolejne parametry.

Zmodyfikujmy teraz interfejs ProxyAction i jego testową implementację tak, aby metody doBefore() i doAfter() akceptowały parametr typu String będący wartością naszej adnotacji. Oto kod:

public interface ProxyAction {

public boolean doBefore(String annotationValue);

public void doAfter(String annotationValue);
}

public class InvokeAction implements ProxyAction {

public boolean doBefore(String annotationValue) {
System.out.println("Message from doBefore(" + annotationValue + ")");

return true;
}

public void doAfter(String annotationValue) {
System.out.println("Message from doAfter("+ annotationValue + ")");
}
}

Pozostaje jeszcze tylko zmodyfikować kod klasy ProxyHandler implementującej interfejs InvocationHandler tak aby odczytywał i przekazywał w wywołaniach wartość adnotacji. W tym celu dodajmy metodę getValue() i zmodyfikujmy metodę invoke() tak jak pokazano poniżej:

public Object invoke(Object proxy, Method proxiedMethod, Object[] args)
throws Throwable {

Object result = null;

try {
String annotationValue = getValue(proxiedMethod);

if (proxyAction.doBefore(annotationValue))
result = proxiedMethod.invoke(proxiedInstance, args);

proxyAction.doAfter(annotationValue);
} catch (InvocationTargetException invocationExc) {
throw invocationExc.getTargetException();
}

return result;
}

private String getValue(Method proxiedMethod) {

for(Annotation annotation : proxiedMethod.getAnnotations()) {
if(annotation instanceof MyAnnotation) {
return ((MyAnnotation)annotation).value();
}
}

throw new RuntimeException("Annotation not found");
}

Prawdopodobnie dobrze by jeszcze było zastąpić wyjątek RuntimeException rzucany z metody getValue() jakimś własnym, ale to już szczegół. Nie wiem jak wy, ale ja jestem zachwycony elegancją, prostotą i siłą wyrazu tego rozwiązania i niewykluczone, że zaprezentuje jeszcze coś ciekawego na bazie tych mechanizmów.

2 grudnia 2007

Mechanizm dynamicznego proxy z Reflection API

Dzisiaj zaprezentuję mechanizm dynamicznego proxy (ang. dynamic proxy) udostępniany przez Reflection API, mechanizm wykorzystywany (między innymi) przez Spring’a do implementacji AOP (akr. Aspect-Oriented Programming). Generalnie, dynamiczne proxy umożliwia wywoływanie metod naszej klasy za pośrednictwem metod innej klasy. Pozwala to wykonać pewne akcje przed jak i po wywołaniu właściwej metody albo też ewentualnie zadecydować o nie wykonywaniu tejże. Ale gdyby to było tylko tyle to byłoby to zwykłe proxy. Dlaczego jest ono dynamiczne? Otóż dlatego, że klasa będąca klasą proxy jest generowana dynamicznie w czasie wykonania. Nie mam pomysłu jak zwięźle i trafnie opisać, o co dokładnie chodzi, więc od razu przechodzę do meritum. Załóżmy, że mamy następujący interfejs i implementującą go klasę:

public interface MyService {

public void myOperation();
}

public class MyServiceBean implements MyService {

public void myOperation () {
System.out.println("Message from myOperation()");
}
}

Aby to, co chcę finalnie zbudować wyglądało ładnie użyjemy Spring’a (choć można i bez niego). Poniżej konfiguracja ziarna dla klasy pokazanej powyżej, póki co nic ciekawego, ale będziemy modyfikować:

<bean id="myService" class="mypackage.MyServiceBean">
</bean>

A teraz zdefiniujmy interfejs z dwiema metodami – doBefore() i doAfter() – które to będą wywołane przed i po właściwym wywołaniu metody biznesowej. Dodatkowo, metoda doBefore() będzie zwracała rezultat typu logicznego (typu boolean) o tym znaczeniu, że metoda biznesowa będzie wywoływana tylko wtedy, gdy rezultat ten będzie miał wartość ‘true’. Oto on wraz z implementacją testową:

public interface ProxyAction {

public boolean doBefore();

public void doAfter();
}

public class InvokeAction implements ProxyAction {

public boolean doBefore() {
System.out.println("Message from doBefore()");

return true;
}

public void doAfter() {
System.out.println("Message from doAfter()");
}
}

Naszym celem jest oczywiście takie skonstruowanie kodu, aby to, jaka implementacja interfejsu ProxyAction będzie użyta zależało od konfiguracji Spring’a. To co było banalne już mamy, teraz przechodzimy do sedna.

W dzisiejszej zabawie chodzi o to, aby zastąpić definicję elementu zarządzanego Spring pokazaną powyżej taką definicją, aby zamiast instancji klasy implementującej metody biznesowe dostać instancje innej klasy, utworzonej dynamicznie dzięki mechanizmowi dynamicznych proxy z Reflection API. Taka instancja naturalnie będzie implementowała te same interfejsy co nasza klasa biznesowa. To jest właśnie gwóźdź programu. W tym celu musimy napisać klasę implementującą interfejs java.lang.reflect.InvocationHandler. Oto kod:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ProxyHandler implements java.lang.reflect.InvocationHandler {

private Object proxiedInstance;

private ProxyAction proxyAction;

protected ProxyHandler(Object obj, ProxyAction proxyAction) {
this.proxiedInstance = obj;
this.proxyAction = proxyAction;
}

public Object invoke(Object proxy, Method proxiedMethod, Object[] args)
throws Throwable {

Object result = null;

try {
if (proxyAction.doBefore())
result = proxiedMethod.invoke(proxiedInstance, args);

proxyAction.doAfter();
} catch (InvocationTargetException invocationExc) {
throw invocationExc.getTargetException();
}

return result;
}
}

Kolejnym elementem układanki jest klasa implementująca wzorzec fabryki i tworząca instancje odpowiednio skonfigurowanych proxy. Oto kod:

public class ProxiedServiceFactory {

private ProxyAction proxyAction;

public Object newInstance(Object proxiedInstance) {
return java.lang.reflect.Proxy.newProxyInstance(
proxiedInstance.getClass().getClassLoader(),
proxiedInstance.getClass().getInterfaces(),
new ProxyHandler(proxiedInstance, proxyAction));
}

public void setProxyAction(ProxyAction proxyAction) {
this.proxyAction = proxyAction;
}
}

Działa to tak, że fabryka buduje obiekt dynamicznego proxy na bazie instancji klasy implementującej logikę biznesową przekazanej do wywołania metody newInstance(). Budując wspomniany obiekt proxy przekazujemy jednocześnie instancję klasy implementującą interfejs java.lang.reflect.InvocationHandler. Metoda invoke() tej klasy będzie wywoływana zamiast metod biznesowych i to właśnie tam decydujemy, co tak naprawdę się stanie. Ja akurat postanowiłem, że wywołamy tam metody doBefore() i doAfter() na instancji implementującej interfejs MyService zdefiniowanej w momencie tworzenia fabryki. Aby spiąć wszystko elegancko do kupy należy użyć konfiguracji Spring pokazanej poniżej (w miejsce tej pokazanej na początku artykułu):

<bean id="proxiedServiceFactory" class="mypackage.ProxiedServiceFactory">
<property name="proxyAction">
<bean class="mypackage.InvokeAction" />
</property>
</bean>

<bean id="myService" factory-bean="proxiedServiceFactory" factory-method="newInstance">
<constructor-arg>
<bean class="mypackage.MyServiceBean" />
</constructor-arg>
</bean>

To tyle. Naturalnie, z dokładnością do możliwości użycia operatora ‘instanceof’, ziarna zarządzanego ‘myService’ używamy tak, jakby to była normalna instancja naszej klasy biznesowej. Podkreślę jeszcze tylko, bo być może nie jest to jasne, że to co pokazałem to jest rodzaj szkieletu aplikacji (ang. framework). Mam tu na myśli to, że całą tę skomplikowaną infrastrukturę implementujemy tylko raz, niezależnie od tego ile mamy klas biznesowych. W następnym artykule mam nadzieję pokazać przykład ciekawego wykorzystania pokazanego tu mechanizmu.