niedziela, 16 marca 2014

PLOUG znowu w akcji


Dobre wieści – po dwuletniej przerwie powraca do swojej aktywności Polish Oracle Users Group. W nieco zmienionej formule, już 10 kwietnia 2014 w Warszawie odbędzie się XVIII Konferencja po hasłem „20-lecie PLOUG i Oracle 12c”.
Mnie cieszy to tym bardziej, że wśród szacownego grona prelegentów znalazła się i moja skromna osoba.
Konferencja oprócz sesji plenarnej podzielona będzie na 2 równoległe seminaria:
  • Administrowanie Oracle Database 12c
  • Tworzenie aplikacji biznesowych dla baz danych Oracle Database 12c
Tematy zapowiadają się wyjątkowo ciekawie – z abstraktami można zapoznać się już na stronie domowej konferencji.
W swoim i organizatorów imieniu, serdecznie zapraszam do rejestracji wszystkich, którzy na co dzień mają do czynienia z oprogramowaniem giganta z Redwood.

niedziela, 16 czerwca 2013

Oracle ADF Design & Architecture Principles Training – moje subiektywne wrażenia

W tym tygodniu miałem przyjemność uczestniczyć w 5-cio dniowym szkoleniu zorganizowanym przez Oracle dla swoich pracowników i partnerów. Ta się składa, że mój obecny pracodawca jest jednym z  partnerów Oracle w Polsce, w związku z czym, nie omieszkałem wykorzystać okazji i wraz z kolegą z naszego Oracle Competence Center udaliśmy się do stolicy. Już same nazwiska prowadzących zapowiadały wysoki poziom zajęć. Szkolenie prowadzili sami mistrzowie ze szkoły Jedi :) - Frank Nimphius i Chris Muir. Z oboma panami znam się od ładnych paru lat z community,  ale nigdy nie mieliśmy okazji poznać się osobiście. Tym bardziej więc nie mogłem się doczekać, bo co innego dyskutować mailowo lub na forum, a co innego wymienić opinie bezpośrednio.
Pierwsze co zaskoczyło mnie na wejściu to nieobecność programistów z polskich partnerów Oracle - trochę to dziwne. Zjechali się natomiast ADF'owcy z całej Europy i to było super, ze względu na możliwość wymiany doświadczeń.
Ale do rzeczy. Szkolenie miało formę prezentacji tematycznych i dyskusji. Nie było hands-on lab'ów i dobrze, bo ilość materiału z jaką przyjechali prelegenci byłaby nie do przekazania w 5 dni. Omawiane zagadnienia nie dotyczyły problemów typu how-to. Advanced i complex to były słowa klucze do wszystkich omawianych problemów.
Tematy, o których poniżej, to subiektywny wycinek całości, zasługujący na szczególną uwagę. Aczkolwiek muszę podkreślić - nie było zagadnień niepotrzebnych czy o charakterze marketingowym - czyste techniczne tematy.

Budowanie zespołu Application Development Framework

Tutaj wszyscy mieli podobne doświadczenia i byli zgodni co do jednego. Jest problem z doświadczonymi programistami, którzy przychodzą ze świata Hibernate i Spring. Zamiast szukać rozwiązań w ramach frameworka implementują własne rozwiązania. Konkluzja na koniec była taka, że chyba lepiej brać niedoświadczonych ludzi do zespołu :). To co powinni umieć na początek to SQL (dobrze), Java (na poziomie podstawowym), podstawy JEE (oczywiście JSF obowiązkowo).


ADF Architectural Patterns

Temat bardzo ważny z punktu widzenia architekta aplikacji. Chris omawiał różne podejścia do budowy struktury projektu, wszystkie za i przeciw. Część omawianych zagadnień można odsłuchać i obejrzeć na jego screancaście "Angels in the ADF Architecture".

Task Flow Data Control Scope Options and Task Flow Transaction Options


Temat rzeka i naprawdę złożony. Widoczne na pierwszym obrazku lista rozwijalna i checkbox, w różnych kombinacjach całkowicie zmieniają mechanizm zarządzania transakcjami i połączeniami do bazy danych. Ja wreszcie dowiedziałem się co to jest Current Data Control Frame :). Zagadnienie z kategorii absolutely must know. Dokumentacja jak przyznał sam prowadzący jest niestety słaba jak herbata w akademiku. Na szczęście główne problemy opisał na "Oracle ADF Architecture Square".

Task Flow Communication Patterns

Temat zaawansowany. Nie jest obowiązkowy ale warto i wypada znać. Jedyne czego zabrakło mi w zakresie to omówienie wzorca "Dynamic Tabs UI Shell Template Functional UI Pattern". Zainteresowanych odsyłam do artykułów Franka na OTN. Jest tam większość omawianych zagadnień.

ADF Region Interaction Functional Pattern
Contextual Events Introduction
Master- commander: reusable contextual event solution
Contextual Events: Callback pattern
Parent view initializing region navigation

Service Integration Architectures and ADF Service Architectures

Jeden z najbardziej interesujących mnie tematów. Jak budować aplikację ADF opartą o usługi Web Services. Tak naprawdę ścierają się tutaj dwie koncepcje i możliwości. Można zawrapować proxy w komponenty biznesowe i pracować tak jakby pod spodem była baza danych. Można też wygenerować ADF Data Controls dla Java Beanów. Nie mam zdania co jest lepsze. W pierwszym wypadku zyskujemy łatwość tworzenia GUI. Należy jednak pamiętać, że BC to framework stworzony do komunikacji z bazą danych z nie bezstanowymi usługami. Trzeba dysponować zaawansowaną wiedzą, żeby dostosować działanie BC do Web Service'ów. Obszerna dyskusja nad właściwym podejściem toczyła się już zresztą wcześniej na ADF  Enterprise Methodology Group (Best practices for ADF Frontend on top of OSB or SOA Suite). Konkluzja Franka była następująca: Oracle używa obu podejść. On osobiście preferuje podejście POJO Proxy, szczególnie, że można tam zapiąć Coherence.
To zagadnienie potraktowałem jako ciekawostkę. ADF Business Components mają możliwość wystawiania w postaci SDO Web Services. Podobno zostało to już dopracowane i nie ma błędów.

Więcej o ADF i WS można poczytać i posłuchać tutaj:

Oracle Magazine "Service Please" By Frank Nimphius
Oracle Magazine "Consume Early, Consume Often" By Frank Nimphius
Best Practices for Integrating SOAP and REST services into Oracle ADF

Application Customization and MDS


Temat kastomizacji aplikacji z wykorzystaniem Metadata Services (MDS) został potraktowany przez Franka bardzo obszernie i kompleksowo. Okazało się jednak, że w rzeczywistości nikt tego nie używa w praktyce poza WebCenter. Cóż, obawa o performace? Dodatkowe koszty projektu w stosunku do tego co się zyskuje? Ja osobiście widzę pewne istotne zastosowanie ale może o tym przy innej okazji.

Architecting for ADF Mobile Integration

ADF Mobile to chyba najmłodsze dziecko Oracle. Zagadnienie w zasadzie na osobne szkolenie. Tak, da się w ADF robić aplikacje mobilne. Co więcej jest do dość łatwe dla ludzi ze znajomością framework'a. Chris pokazał to na żywo w 10 min. (jedyne demo przez całe 5 dni). Zagadnienie w zasadzie na osobne szkolenie.

Error Handling

Gruby temat jeśli chce się to zrobić porządnie. W zasadzie obowiązkowe do zapoznania się: Oracle Magazine "Catch Me If You Can" By Frank Nimphius

Programming Best Practices

Wzorce, wzorce i jeszcze raz wzorce. Wrócił temat pisania kodu obok framework'a i zakresu umiejętności developerów. Dziedziczenie vs. klasy narzędziowe. W zasadzie tematy obowiązkowe dla każdego ADFowca.

Afterpaty :)

Goście zafascynowani byli Warszawą i polskim jedzeniem. Potwierdziła się też teoria, że nasze piwo i Żubrówka likwidują wszystkie bariery językowo-kulturowe :).


Podsumowanie

To było super 5 dni. Chyba jedno z najlepszych i najbardziej merytorycznych szkoleń na jakich byłem.

poniedziałek, 24 września 2012

Oracle ADF Essentials - wreszcie za darmo

Stało się. Po wielu latach nacisków ze strony community Oracle wypuściło darmową wersję swojego framework'a. Okrojona (niezbyt mocno) wersja nosi nazwę Oracle ADF Essentials. Od tej chwili używając GlassFish'a (bądź innego serwera, na którym zadziała ADF) nie potrzebujemy żadnych licencji.

Co mamy za darmo 
  • ADF Faces Rich Client components - kompletny zestaw komponentów (ponad 150) JSF 2.0 w implementacji Oracle 
  • ADF Controller – rozszerzenie standardowego kontrolera JSF
  • ADF Binding - warstwa abstrakcji pomiędzy serwisami dostarczającymi dane a warstwą widoku (brak odpowiednika w innych frameworkach) 
  • ADF Business Components - lekki framework do komunikacji z bazą danych i definiowania logiki biznesowej

  

Czego nie ma w wersji darmowej
  • ADF Mobile 
  • ADF Desktop Integration 
  • ADF Security (OPSS) 
  • ADF Web service data control 
  • ADF remote taskflows 
  • ADF Business Component’s Service Interfaces
  • ADF Data Controls dla BI, Essbase, and BAM 
  • Integracja z MDS, OWSM, Enterprise Manager i MBeans 
  • High Availability and Clustering 

Jak widać powyżej nie są to kluczowe elementy ADF i praktycznie w niczym nie uszczuplą one funkcjonalności aplikacji ani nie wprowadzą istotnych zmian w podejściu do developmentu.

Wspierane serwery Oracle WebLogic 11g, WebSphere i GlassFish 3.1 (Full Platform). Swoją drogą szkoda, że Oracle w dalszym ciągu nie wspiera Tomcata.

ADF Essentials dostępny jest z wersją JDevelopera 11.1.2.3.0, ale w najbliższych planach jest także pełne wsparcie w ramach Enterprise Pack For Eclipse, oczywiście w tym przypadku zamiast Business Components używamy EJB/JPA.

Oracle nie poszło niestety o krok dalej i nie opublikowało kodów źródłowych. Są one dostępne jedynie dla posiadaczy asysty technicznej na My Oracle Support.

Czy dzisiejszy krok spowoduje zwiększenie popularności framework'a - cóż pożyjemy - zobaczymy :)

środa, 6 kwietnia 2011

Własne komponenty JSF 2.0

Od dłuższego czasu chodzi mi napisanie biblioteki komponentów JSF zawierającej zestaw flash'owych wykresów FusionCharts Free. Zabierałem się już do tego kilkakrotnie ale zawsze miałem coś pilniejszego do zrobienia. Tym razem jednak zebrałem się w sobie i po lekturze książki, o której pisałem ostatnio postanowiłem przebrnąć przez problematykę tworzenia własnych bibliotek JSF. Początkowo, po wprowadzeniu wersji 2.0, myślałem o wykorzystaniu do tego celu komponentów złożonych (composite components), jednak po głębszym przemyśleniu tematu, doszedłem do wniosku, że bardziej elastyczne będzie utworzenie własnej biblioteki.
Postanowiłem zacząć od czegoś najprostszego – komponentu, który wygeneruje mi HTML'owy element DIV ze wstawionym przez aplikację kliencką tekstem. Komponent taki, może w zasadzie składać się tylko z jednaj klasy – klasy komponentu, dziedziczącej z klasy UIComponent. W praktyce stosuje się głównie dziczenie po jednej z trzech klas: UIOutput, UIInput, UIComand.
Twórcy technologii JSF założyli, że formatem wyjściowym może być nie tyko HTML (generowany domyślnie prze klasę komponentu) dlatego też, wprowadzili możliwość wydelegowania zadania wizualizacji do odrębnego mechanizmu. Ja jednak, aby nie komplikować tematu rendererów, do którego pewnie jeszcze wrócę, skupię się na podstawowym rozwiązaniu. Poniżej klasa mojego komponentu
package kuba.demo.customcomp;

import java.io.IOException;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

@FacesComponent("com.blogspot.javaspotlight.Div")
public class UIDiv extends UIOutput{

   @Override
   public void encodeBegin(FacesContext context) throws IOException {

      ResponseWriter writer = context.getResponseWriter();
      String clientId = getClientId(context);

      String compId   = (String)getAttributes().get("id");
      String divStyle = (String)getAttributes().get("style");
      String divText  = (String)getAttributes().get("divText");

      writer.startElement("div", this);

      if (compId != null){
         writer.writeAttribute("id", compId, "id");
      }else{
         writer.writeAttribute("id", clientId, null);
      }

      if (divStyle != null){
         writer.writeAttribute("style", divStyle, null);
      }

      if (divText != null){
         writer.writeText(divText, null, null);
      }

      writer.endElement("div");
   }
}

Jej zadaniem jest wygenerowanie elementu div z trzema atrybutami pochodzącymi z a aplikacji klienckiej: id, style, divText.
Warto też zwrócić uwagę na adnotację @FacesComponent("com.blogspot.javaspotlight.Div"). Zawiera ona identyfikator klasy komponentu JSF. We wcześniejszych wersjach odwzorowanie identyfikatora na klasę komponentu umieszczało się w pliku faces-config.xml






Od wersji 2.0 można używać obu metod odwzorowań.
Drugim krokiem tworzenia własnego komponentu JSF jest utworzenie w katalogu WEB-INF pliku deskryptora biblioteki zawierającego: przestrzeń nazw, nazwę znacznika i typ komponentu. Jego nazwa musi posiadać zakończenie .taglib.xml
   



























Warto zwrócić uwagę na jedną rzecz – deklarację atrybutów znacznika. Nie jest ona wymagana, klasa komponentu i tak będzie „widziała” atrybuty. Jednak bez tej deklaracji tworząc aplikację z wykorzystaniem własnej biblioteki, nie będzie ich rozpoznawać nasze środowisko programistyczne – w moim przypadku NetBeans.
Wreszcie krok trzeci – ostatni. W pliku web.xml musimy wskazać na położenie deskryptora






Teraz pozostaje już tylko użycie komponentu na stronie.













Teoretycznie to już wszystko, tylko po co nam taki komponent ? Celem bibliotek jest możliwość ich wielokrotnego wykorzystania w różnych projektach. Pozostaje jeszcze spakowanie naszego komponentu. Dopiero tutaj zaczął się problem :)
Przygotowanie komponentu JSF do dystrybucji
  1. utworzyć archiwum .jar
  2. przekopiować deskryptor .taglib.xml do katalogu META-INF
  3. w katalogu META-INF musi znajdować się plik faces-config.xml – nawet jeśli nie zawiera żadnych wpisów (tutaj właśnie utknąłem – przecież JSF 2.0 załatwia wszystko przez adnotacje i teoretycznie ten plik nie jest potrzebny. Jakże się myliłem – JEST POTRZEBNY :))
  4. plik web.xml nie jest potrzebny
Struktura biblioteki po spakowaniu powinna wyglądać następująco

Własny język wyrażeń (Expression Language) w JSF 2.0

Dzisiejszy wpis ma na celu pokazanie na przykładzie dwóch prostych przypadków, jak rozszerzyć istniejący język wyrażeń EL.

Przykład 1 – rozszerzenie języka wyrażeń

Załóżmy, że zaimplementowaliśmy własny mechanizm warunkowego renderowania komponentów JSF (w zależności od posiadanych przez użytkownika ról)
Wyrażenie #{userHasRole['EMPLOYEE, MANAGER']} powinno zwracać true lub false. Wymaga to utworzenia własnego mechanizmu przetwarzającego, który w pierwszym kroku zinterpretuje ciąg znaków userHasRole , a następnie ciąg 'EMPLOYEE, MANAGER'. Mechanizm ten to nic innego jak rozszerzenie klasy ELResolver , której najważniejszą dla nas jest metoda:

public Object getValue(ELContext context, Object base, Object property)

Poniżej kod własnego resolver'a
package kuba.demo.el;

import java.beans.FeatureDescriptor;
import java.util.Iterator;
import javax.el.ELContext;
import javax.el.ELResolver;
import kuba.demo.UserRolesController;

public class UserRolesResolver extends ELResolver{ 

    @Override
    public Object getValue(ELContext context, Object base, Object property) {

        // base=null  property=userHasRole

        if(base==null && "userHasRole".equals(property)){

            context.setPropertyResolved(true);

           return new UserRolesController();

        }

        // base=UserRolesController  property=EMPLOYEE, MANAGER

        if(base instanceof UserRolesController && property instanceof String){

            context.setPropertyResolved(true);

           return UserRolesController.checkUserAccess((String)property);

        }        

        return false;
    }
  

    @Override
    public Class getType(ELContext context, Object base, Object property) {



         if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return UserRolesController.class;

        }

      return null;

    }


    @Override
    public Class getCommonPropertyType(ELContext context, Object base) {

        if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return String.class;

        }

      return null;

    }


    @Override
    public boolean isReadOnly(ELContext context, Object base, Object property) {

        if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return true;
        }
      return false;
    }


    @Override
    public void setValue(ELContext context, Object base, Object property, Object value) {
    }


    @Override
    public Iterator getFeatureDescriptors(ELContext context, Object base) {
       return null;

    }
}

Ja, na potrzeby niniejszego przykładu, dodałem jeszcze klasę pomocniczą UserRolesController przechowującą i sprawdzającą role użytkownika
package kuba.demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

public class UserRolesController {

    public static boolean checkUserAccess(String roleNames) {

        List userRoles = new ArrayList(Arrays.asList("ADMIN",
                                                     "MANAGER",
                                                     "USER"
                                                    ));

        StringTokenizer token = new StringTokenizer(roleNames,",");
      
        while (token.hasMoreTokens()) {

            String roleName = ((String)token.nextElement()).trim();

            if(userRoles.contains(roleName)){

                return true;

            }
        }
        return false;
    }
}

Aby nasza klasa przetwarzająca EL była widoczna w aplikacji, musimy dodać następujący w pis w pliku faces-config.xml





Jest tutaj pewna niekonsekwencja twórców specyfikacji. Nie istnieje bowiem możliwość rejestracji własnego resolver'a przy pomocy odpowiedniej adnotacji – tak jak odbywa się to w przypadku innych elementów JSF 2.0.  

Przykład 2 – dodanie funkcji do języka wyrażeń

Załóżmy, że chcemy na tronie JSF wywołać przy pomocy EL własną funkcję przyjmującą dwa argumenty



W tym celu musimy zaimplementować statyczną metodę
package kuba.demo.el;

public class SumELFunction {

    public static int sumTwoArgs(int arg1, int arg2){
        return arg1 + arg2;
    }
}

oraz odwzorować ją w znajdującej się w katalogu WEB-INF bibliotece znaczników el.taglib.xml
















pozostaje jeszcze zarejestrować bibliotekę w pliku web.xml


Java Context and Dependency Injection w akcji

Dzisiaj niechcący wygooglowałem projekt poświęcony w całości framewokowi Context and Dependency Injection – CDISource. Muszę przyznać, że pod względem treści wygląda bardzo imponująco. Jak twierdzą autorzy, Andy Gibson i Rick Hightower powstał on w celu promocji CDI w kontekście różnych możliwych zastosowań – nie tylko w Java EE. Myślę, że warto śledzić rozwój tego projektu, tym bardziej, że zawiera on nie tylko opisy rozwiązań, ale także spore ilości przykładowego kodu.

P.S.
Wiem. Od bardzo dawna nic ciekawego nie napisałem. Niestety cierpię na permanentny brak czasu. Szczególne wyrzuty sumienia mam wobec Grześka Kukawskiego - autora Darmowego kursu UML, na który się zapisałem i niestety utknąłem na drugiej części. Na swoje usprawiedliwienie mam jedynie to, że nie przebimbałem tego czasu. W ostatnim czasie nasza firmowa biblioteka wzbogaciła się o kolejną pozycję – trzecie wydanie JavaServer Faces (David Geary, Cay S. Horstmann) – nie mogłem sobie odpuścić i jej przeczytanie stanęło na pierwszym miejscu :)

niedziela, 20 lutego 2011

Autentykacja zarządzana przez kontener Java EE dla aplikacji JSF 2.0 z wykorzystaniem Servlet 3.0 API

Do napisania niniejszego postu skłoniło mnie spostrzeżenie, że większość śledzących nowości w Java EE 6 (włącznie ze mną oczywiście) skupiła się na adnotacjach. Tymczasem w Servlet API 3.0 weszło bardzo dobre rozwiązanie - a mianowicie - możliwość programowej autentykacji do kontenera JEE. Dotychczas stroną logowania mogła być jedynie zwykła strona HTML z polami j_username, j_password i formularzem z akcją j_security_check. Dzięki wprowadzonym nowościom, strona logowania może być już teraz stroną JSF. Ale zacznijmy od początku. Poniżej kroki konfiguracji GlassFish'a:




















Utworzenie nowego realm'a

Dodanie użytkowników i ról
Struktura mojej aplikacji demo
Plik sun-web.xml
Plik web.xml
Strona logowania
Managed bean z funkcją odpowiedzialną za autentykację

import javax.faces.application.FacesMessage;;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

@ManagedBean
@RequestScoped
public class LoginBean {

    private String inputLogin;
    private String inputPassword;

    public String loginAction(){

        FacesContext ctx = null;
        ExternalContext ectx = null;
        HttpServletRequest request = null;
        FacesMessage msg = null;
        String action = null;

        try {
                ctx = FacesContext.getCurrentInstance();
                ectx = ctx.getExternalContext();
                request = (HttpServletRequest) ectx.getRequest();
                request.login(inputLogin, inputPassword);
                action = "/protected/welcome.xhtml";

        } catch (ServletException ex) {

            ctx = FacesContext.getCurrentInstance();
            msg = new FacesMessage(ex.getMessage());
            msg.setSeverity(FacesMessage.SEVERITY_ERROR);
            ctx.addMessage(null, msg);

        }

       return action;
    }

    // getters and setters
    
}

Jak widać nowością jest funkcja login() wywoływana na obiekcie HttpServletRequest. W przypadku niepoprawnego logowania otrzymamy w wyjątek, który można obsłużyć dowolnym faces'owym komunikatem.









Analogicznie, możemy wywołać funkcję logout(), przy czym należy pamiętać, że wylogowanie musi się odbywać z redirect'em
@ManagedBean
@RequestScoped
public class WelcomeBean {


    public String logoutAction(){

        FacesContext ctx = null;
        ExternalContext ectx = null;
        HttpServletRequest request = null;
        String action = null;

        try {
                ctx = FacesContext.getCurrentInstance();
                ectx = ctx.getExternalContext();
                request = (HttpServletRequest)ectx.getRequest();
                request.logout();
                action = "/faces/index.xhtml?faces-redirect=true";

        } catch (ServletException ex) {
            ex.printStackTrace();
        }

      return action;
    }
}

Pobierz projekt (NetBeans 6.9.1)

Kto nie ma w głowie - ten klepie zbędny kod

Tytuł jak najbardziej adekwatny do tematu dzisiejszego postu. Niestety powyższa (nomen omen) refleksja naszła mnie dopiero pod koniec projektu - sporej wielkości aplikacji do raportowania z bazy, czyli mapowanie ResultSet'ów na kolekcje klas POJO. Wszystko niby fajnie ale wyniki wyświetlane na stronie trzeba było przesłać do Birt Viewer'a, co skutkowało pisaniem dodatkowej klasy handlera dla każdej tabelki – a można było prościej. W poniższym przykładzie wykorzystam własne adnotacje atrybutów POJO do opisania wyglądu raportu PDF (dla uproszczenia użyłem biblioteki iText).

Mając poniższą klasę POJO
package kuba.demo.dao;

public class Employee {

    private String firstName;
    private String lastName;
    private double salary;

    public Employee() {
    }

    public Employee(String firstName, String lastName, double salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.salary = salary;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

oraz managed beana
package kuba.demo.view;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.ActionEvent;
import kuba.demo.dao.Employee;
import kuba.demo.dao.EmployeesDAO;

@ManagedBean(name = "employeeBean")
@RequestScoped
public class EmployeeBean {

    private List emp;
    
    
    @PostConstruct
    private void init(){

        emp = EmployeesDAO.getAll();
    }


    public List getEmp() {
        return emp;
    }

    public void setEmp(List emp) {
        this.emp = emp;
    }
}

wyświetlam na stronie prostą tablekę JSF

Chciałbym ją wyeksportować do pliku PDF co oczywiście nie jest problemem, tyle  że przy kilkuset tabelach dla każdego eksportu musiałbym pisać osobną klasę opisującą formatowanie tej tabeli.
Mój cel, to sprowadzić kod odpowiedzialny za generowanie PDF do takiej postaci
public void downloadPdf(ActionEvent evt){
 
        PdfBuilder builder = new PdfBuilder(emp);
        builder.printToOutputStream("TestWeb");
    }

Tabelki są jedna różne – gdzie więc kod odpowiedzialny za takie informacje jak nagłówek czy szerokość kolumny? Tu z pomocą przychodzą własne adnotacje.
Moja wyglądają następująco
public class Employee {

    @Pdf(label="Imie",width=100)
    private String firstName;

    @Pdf(label="Nazwisko",width=150)
    private String lastName;

    @Pdf(label="Pensja",width=50)
    private double salary;

    // getters setters
}

Drugim krokiem jest utworzenie typu adnotacji
package kuba.demo.pdf;

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.FIELD)
public @interface Pdf {

    String label() default "";
    int    width() default 20;
}

I to w zasadzie wszystko. Ja musiałem utworzyć jeszcze pomocniczą klasę przechowującą na potrzeby generowania PDF informacje o atrybutach tabeli wczytanych z argumentów adnotacji

package kuba.demo.pdf;

public class PdfTableModel {

    private String fieldName;
    private String label;
    private int width;
    
    // getters setters
}

I wreszcie klasa generująca PDF
package kuba.demo.pdf;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

public class PdfBuilder {

    private static final Logger log = Logger.getLogger(PdfBuilder.class.getName());

    private List data;
    private Class listElement;
    private List tableModel;

    private Document document;

    public PdfBuilder(List data) {
        this.data = data;
    }


    private void scanAnnotations(){

        if(data != null && data.size()>0){
            listElement = data.get(0).getClass();
        }

        tableModel = new ArrayList();

        for(Field field : listElement.getDeclaredFields()){

            Pdf pdf = field.getAnnotation(Pdf.class);

            if(pdf != null){

                String fieldName = field.getName();
                String label = pdf.label();
                int width = pdf.width();

                tableModel.add(new PdfTableModel(fieldName,label,width));
            }
        }
    };


    private void createDocument(Document document){

        scanAnnotations();

        try {

            Object row = Class.forName(listElement.getName()).newInstance();

            int tableSize = tableModel.size();
            float[] columns = new float[tableSize];
            
            int i = 0;
            for(PdfTableModel ptm: tableModel){
                columns[i] = ptm.getWidth();
                i++;
            }

            PdfPTable table = new PdfPTable(columns);

            // Table headers
            for(PdfTableModel ptm : tableModel){

                PdfPCell cell = new PdfPCell(new Phrase(ptm.getLabel()));
                cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
                table.addCell(cell);
            }
            
            // Table rows
            for(Object listRow : data){

                row = listRow;

                for(PdfTableModel ptm : tableModel){

                    Field f = row.getClass().getDeclaredField(ptm.getFieldName());
                    f.setAccessible(true);
                    Object cellValue = f.get(row);
                    PdfPCell cell = new PdfPCell(new Phrase(cellValue.toString()));
                    table.addCell(cell);
                 }
            }

            document.add(table);

        } catch (InstantiationException ex) {
            log.severe(ex.getMessage());
        } catch (ClassNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (IllegalArgumentException ex) {
            log.severe(ex.getMessage());
        } catch (IllegalAccessException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchFieldException ex) {
            log.severe(ex.getMessage());
        } catch (SecurityException ex) {
            log.severe(ex.getMessage());
        } catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }

    }

    
    public void printToOutputStream(String fileName){

        ServletOutputStream out = null;

        FacesContext ctxContext = FacesContext.getCurrentInstance();
        ExternalContext ectx = ctxContext.getExternalContext();
        HttpServletResponse res = (HttpServletResponse)ectx.getResponse();
        res.setContentType("application/pdf");
        res.setHeader("Content-Disposition", " inline; filename=\""+fileName+".pdf\"");

        try {

             out = res.getOutputStream();
             document = new Document();
             PdfWriter.getInstance(document, out);
             document.open();
             createDocument(document);
             document.close();
             
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        }catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }
    }


    public void printToFile(String filePath){

        try {
                document = new Document();
                PdfWriter.getInstance(document, new FileOutputStream(filePath));
                document.open();
                createDocument(document);
                document.close();
                
        }catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        }catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }
    }
}

scanAnnotations() - odczytuje wartości argumentów adnotacji i zapisuje je do listy obiektów PdfTableModel (nagłówki i szerokości kolumn)
createDocument() - buduje tabelę w dokumencie PDF wykorzystując refleksje do odczytania wartości atrybutów POJO
printToOutputStream() - zapisuje dokument PDF do obiektu ServletOutputStream

Jak widać działa :)
Pobierz projekt (NetBeans 6.9.1)

środa, 19 stycznia 2011

Inastalacja Informix'a 11.70 na Ubuntu 10.04

Jakieś 3 tygodnie temu ostatecznie rozstałem się z Windowsem i dotychczas nie zainstalowałem żadnej lokalnej bazy.  Biorąc pod uwagę, że ostatnio ostro wziąłem się za naukę Hibernate, a w swoim Redbook'u pod tytułem IBM Informix Developer's Handbook producent zaleca używanie właśnie jego – postanowiłem upiec dwie pieczenie na jednym ogniu - czyli rozpoznać dobrze i jedno i drugie.
Zdecydowałem się na najnowszą, trialową wersję Ultimate Edition  11.70 (dostępne są 2 darmowe, ale okrojone wersje – Developer Edition i Innovator-C Edition) ponieważ zawiera ona wszystkie dostępne cechy bazy w wersji Enterprise.

Instalację przeprowadziłem w trybie graficznym wydając polecenie: ./ids_install -i gui


W trakcie instalacji utworzony został sytemowy użytkownik informix i tylko on może uruchomić bazę. Aby to zrobić wymagane jest ustawienie kilku zmiennych środowiskowych. Ja utworzyłem w tym celu w jego katalogu domowym plik profilu /home/informix/.profile z następującymi zmiennymi:

INFORMIXDIR=/opt/IBM/informix
INFORMIXSERVER=ol_informix1170
ONCONFIG=onconfig.ol_informix1170
INFORMIXSQLHOSTS=/opt/IBM/informix/etc/sqlhosts.ol_informix1170
PATH=${PATH}:${INFORMIXDIR}/bin:${INFORMIXDIR}/extend/krakatoa/jre/bin
export INFORMIXDIR INFORMIXSERVER ONCONFIG INFORMIXSQLHOSTS PATH


To w zasadzie wszystko. Można teraz poleceniem oninit uruchomić serwer bazy danych

Status serwera możemy sprawdzić poleceniem onstat

 Serwer zatrzymujemy poleceniem onmode -ky

Informix zawiera demonstracyjną bazę danych o nazwie stores_demo. Aby ją zainstalować wydajemy polecenie dbaccessdemo7.

Do pracy z bazą najlepiej nadaje się chyba darmowy oparty o środowisko Eclipse IBM Data Studio Standalone. Do pobrania jest także darmowy podręcznik użytkownika Getting started with IBM Data Studio for DB2 . Poniżej proces instalacji i konfiguracji.

piątek, 14 stycznia 2011

Lektura na weekend

Przymierzałem się do tego od jakiegoś czasu i skuszony pozytywną recenzją Jacka Laskowskiego zakupiłem dzisiaj książkę (podobno lektura obowiązkowa dla każdego programisty Javy) "Hibernate. Od nowicjusza do profesjonalisty" wydaną nakładem wydawnictwa Power Net (oryg. Apress). Po pobieżnym przejrzeniu mogę powiedzieć - warto było. Jedynym chyba minusem jest fakt, że nie jest to II wydanie tej pozycji, które ukazało się w ubiegłym roku. Niestety jak dowiedziałem się w wydawnictwie nie planują oni upgrade'u. Szkoda, że dzisiaj już nie wezmę się za lekturę. Jutro egzamin na 7 Kup - trzeba jeszcze powtórzyć układy i teorię :)

sobota, 8 stycznia 2011

Cyfrowe podpisywanie plików PDF

Dzisiejszy post trochę odbiega od poprzednich, niemniej jednak uznałem, że temat jest warty uwagi. W tym tygodniu zajmowałem się podpisywaniem plików PDF przy pomocy klucza prywatnego i weryfikacją takiego podpisu w oparciu o klucz publiczny.

Zacznijmy od wygenerowania repozytorium z parą takich kluczy przy pomocy keytool'a

keytool -genkey -alias kuba  -keypass demokeypass  -storepass demostorepass  -keystore demo-keystore.jks -validity 360 -dname "CN=Jakub Pawlowski, OU=blog, O=javaspotlight.blogspot.com, L=Lodz, S=Lodzkie, C=PL"

Następnie należy wyeksportować klucz publiczny
keytool -export -alias kuba  -keypass demokeypass  -storepass demostorepass  -keystore demo-keystore.jks -file demo-cert.cer

Poniższe polecenia umożliwiają weryfikacje zawartości obu plików
keytool -list -alias kuba -storepass demostorepass -v -keystore ~/tmp/demo-keystore.jks

keytool -printcert -v -file ~/tmp/demo-cert.cer

Do podpisania PDF'a użyłem oczywiście biblioteki iText (wymagana jest także biblioteka Bouncy Castle).
Klasa odpowiedzialna za podpisanie PDF'a wygląda następująco
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.logging.Logger;

public class PdfSigner {

    private static final Logger log = Logger.getLogger(PdfSigner.class.getName());
    private static final String PDF_PATH = "/home/kuba/tmp/demo-doc.pdf";
    private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf";
    private static final String KEYSTORE_PATH = "/home/kuba/tmp/demo-keystore.jks";
    private static final String KEYSTORE_PASSWORD = "demostorepass";
    private static final String KEY_ALIAS = "kuba";
    private static final String KEY_PASSWORD = "demokeypass";

    private Certificate[] certificates;
    PrivateKey privateKey;

    public PdfSigner() {

        try {
             KeyStore ks = KeyStore.getInstance("JKS");
             FileInputStream fis = new FileInputStream(KEYSTORE_PATH);
             ks.load(fis, KEYSTORE_PASSWORD.toCharArray());
             certificates = ks.getCertificateChain(KEY_ALIAS);
             privateKey = (PrivateKey)ks.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray());

        } catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (KeyStoreException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchAlgorithmException ex) {
            log.severe(ex.getMessage());
        } catch (CertificateException ex) {
           log.severe(ex.getMessage());
        } catch (UnrecoverableEntryException ex) {
          log.severe(ex.getMessage());
        }

    }


    public void signPdf(){

        try {

             PdfReader pdfReader = new PdfReader(PDF_PATH);
             FileOutputStream fos = new FileOutputStream(PDF_PATH_SIGNED);
             PdfStamper stamper = PdfStamper.createSignature(pdfReader, fos, '\0');
             PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
             appearance.setReason("Demo Blog");
             appearance.setCrypto(privateKey, certificates, null, PdfSignatureAppearance.WINCER_SIGNED);
             appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
             stamper.close();
             log.info("Document signed");

        } catch (IOException ex) {
           log.severe(ex.getMessage());
        } catch (DocumentException ex) {
           log.severe(ex.getMessage());
        }

    }

}

Po zaimportowaniu klucza publicznego do Adobe Reader'a i otwarciu podpisanego dokumentu, powinny pokazać się następujące informacje o certyfikacie


Poprawność podpisu można także sprawdzić programowo
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfPKCS7;
import com.itextpdf.text.pdf.PdfReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.logging.Logger;

public class PdfValidator {

    private static final Logger log = Logger.getLogger(PdfValidator.class.getName());
    private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf";
    private static final String CERTIFICATE_PATH = "/home/kuba/tmp/demo-cert.cer";

    private KeyStore keyStore;

    public PdfValidator() {

        try {
             keyStore = KeyStore.getInstance("JKS");
             keyStore.load(null, null);
             FileInputStream fis = new FileInputStream(CERTIFICATE_PATH);
             CertificateFactory cf = CertificateFactory.getInstance("X509");
             X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
             keyStore.setCertificateEntry("cacert", cert);

        } catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (KeyStoreException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchAlgorithmException ex) {
            log.severe(ex.getMessage());
        } catch (CertificateException ex) {
           log.severe(ex.getMessage());
        }
    }
    

    public void validatePdf(){

        try {
            
            PdfReader reader = new PdfReader(PDF_PATH_SIGNED);
            AcroFields af = reader.getAcroFields();
            ArrayList names = af.getSignatureNames();
            
            for (String name : names) {
                
                PdfPKCS7 pk = af.verifySignature(name);
                Calendar calendar = pk.getSignDate();
                Certificate[] certificates = pk.getCertificates();
                log.info("Revision modified: " + !pk.verify());
                Object[] fails = PdfPKCS7.verifyCertificates(certificates, keyStore, null, calendar);
                if (fails == null) {
                    log.info("Certificates verified against the KeyStore");
                } else {
                    log.info("Certificate failed: " + fails[1]);
                }
            }

        } catch (SignatureException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        }
    }
}

wtorek, 21 grudnia 2010

Contexts and Dependency Injection – część II

Drugi z serii wpisów na temat CDI poświęcony będzie zdarzeniom. Pozwalają one luźno powiązać ze sobą bean'y na zasadzie Akcja → Reakcja.
Załóżmy, że nasza aplikacja chce odnotować w dowolnym miejscu moment operacji na obiekcie użytkownika. Należy w tym celu wstrzyknąć obiekt javax.enterprise.event.Event i wywołać na nim metodę fire(). Jej argumentem, będzie obiekt, który ma podlegać obserwacji.
@Named
@RequestScoped
public class UserBean{

    private List users;

    @Inject
    Event createUserEvent;


    @PostConstruct
    private void init(){
        
        users = new ArrayList();
        users.add(new User("Nowak"));
        users.add(new User("Lis"));
    }

    public void addUser(){

        User user = new User("Kowalski");
        users.add(user);
        createUserEvent.fire(user);
    }
}

Teraz wystarczy już w dowolnym miejscu aplikacji zaimplementować metodę nasłuchującą, będącą odbiorcą powyższego zdarzenia
@Named
@RequestScoped
public class UserObserver {

    public void onCreateUser(@Observes User user){

        System.out.println(">> User "+ user.getName() + " has been created");
    }
}

A co w przypadku gdybyśmy chcieli obserwować kilka różnych zdarzeń na obiekcie User ? Tutaj znowu z pomocą przychodzą kwalifikatory
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface CreateUser {
}
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface DeleteUser {
}

Zmodyfikowana klasa obserwująca
import javaspotlight.qualifiers.CreateUser;
import javaspotlight.qualifiers.DeleteUser;
import javax.enterprise.event.Observes;
import javax.faces.bean.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class UserObserver {

    public void onCreateUser(@Observes @CreateUser User user){

        System.out.println(">> User "+ user.getName() + " has been created");
    }

    public void onDeleteUser(@Observes @DeleteUser User user){

        System.out.println(">> User "+ user.getName() + " has been deleted");
    }
}

Sposób inicjacji zdarzeń – oczywiście nie muszą znajdować się one w tej samej klasie
import java.util.ArrayList;
import java.util.List;
import javaspotlight.qualifiers.CreateUser;
import javaspotlight.qualifiers.DeleteUser;
import javax.annotation.PostConstruct;
import javax.enterprise.event.Event;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class UserBean{

    private List users;

    @Inject
    @CreateUser
    Event createUserEvent;

    @Inject 
    @DeleteUser
    Event deleteUserEvent;

    @PostConstruct
    private void init(){
        
        users = new ArrayList();
        users.add(new User("Nowak"));
        users.add(new User("Lis"));
    }

    public void addUser(){

        User user = new User("Kowalski");
        users.add(user);
        createUserEvent.fire(user);
    }

    public void removeUser(){

        for(User user : users){
     
            if("Nowak".equals(user.getName())){

                deleteUserEvent.fire(user);
                users.remove(user);
            }
        }
    }

sobota, 4 grudnia 2010

Contexts and Dependency Injection – część I

Jako wielki entuzjasta Jboss Seam'a i korzystania z adnotacji, nie mogłem odpuścić sobie nauki specyfikacji CDI, (JSR-229), Jest to zbiór usług stanowiący integralną część standardu Java EE 6 i ułatwiający  powiązanie ze sobą poszczególnych komponentów (warstw) składających się  na powyższą platformę.

Contexts – przywiązanie komponentu Java EE do ściśle zdefiniowanego (ale rozszerzalnego) kontekstu (cyklu życia).
Dependency Injection – możliwość interakcji pomiędzy komponentami  aplikacji takimi jak serwlet, sesyjny EJB czy  JSF'owy managed bean poprzez wstrzyknięcie (Injection) czyli umieszczenie ich w dowolnym momencie w cyklu życia aplikacji.

Najlepiej naukę zacząć od praktycznego przykłady. Ja wykorzystam w tym celu środowisko NetBeans 6.9.1 z dostarczonym przez nie serwerem GlassFish 3.0.1. Zakładamy nowy nowy projekt: File › New Project › Web Application. Przy wyborze serwera należy pamiętać aby zaznaczyć opcję „Contexts and Dependency Injection”.
Powyższy krok, spowoduje nasza aplikacja będzie korzystać z implementacji CDI – WELD. W katalogu WEB-INF zostanie utworzony plik beans.xml, Jego istnienie sygnalizuje kontenerowi CDI, że aplikacja zawiera wstrzykiwane bean'y i musi być skanowana w poszukiwaniu klas zawierających adnotacje.

W kolejnym kroku z dostępnych technologii wybieram JSF

Celem aplikacji jest wstrzyknięcie do managed bean'a interfejsu odpowiedzialnego za logowanie.
public interface MyLogger {

    public void log(Class c, String message);
}

Implementacja wygląda następująco:
import javaspotlight.beans.MyLogger;
import javaspotlight.qualifiers.InfoLog;

public class InfoLogger implements MyLogger{

    @Override
    public void log(Class c, String message) {

      Logger.getLogger(c.getName()).info(message);
    }

Do wstrzyknięcia komponentu używamy adnotacji: @javax.inject.Inject
import java.util.logging.*;
import javaspotlight.beans.MyLogger;
import javaspotlight.qualifiers.InfoLog;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class ExampleManagedBean {

    private String outputMessage;

    @Inject
    MyLogger myLogger;

    public String exampleAction(){
        
        outputMessage = "Hello";

        myLogger.log(getClass(), outputMessage);
        return null;
    }
    


    public String getOutputMessage() {
        return outputMessage;
    }

    public void setOutputMessage(String outputMessage) {
        this.outputMessage = outputMessage;
    }

W sumie trywialne. Co jednak się stanie jeśli nasz interfejs będzie posiadał więcej implementacji ? Kontener nie będzie wiedział, której z nich należy użyć. Problem ten rozwiązują kwalifikatory (Qualifiers). Pozwalają nam one na przypisanie dodatkowych informacji do naszego bean'a.

Ja utworzyłem dwie implementacjie loggera:
@InfoLog
public class InfoLogger implements MyLogger{

    @Override
    public void log(Class c, String message) {

      Logger.getLogger(c.getName()).info(message);
    }
}

@WarnLog
public class WarnLogger implements MyLogger{

    @Override
    public void log(Class c, String message) {

      Logger.getLogger(c.getName()).warning(message);
    }
}

Teraz aby móc wskazywać przy pomocy adnotacji na konkretny typ, należy utworzyć kwalifikatory
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface InfoLog {
}

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface WarnLog {
}

dzięki nim, wstrzykując nasz interfejs łatwo możemy wskazać jakiego typu bean zostać użyty.
@Inject
@WarnLog
MyLogger myLogger;

albo
@Inject
@InfoLog
MyLogger myLogger;

Struktura mojego projektu wygląda następująco:

piątek, 26 listopada 2010

News dla programistów Oracle ADF

Stało się. Oracle oficjalnie uruchomiło ścieżkę certyfikacji dla programistów ADF. Wiadomość opublikował wczoraj na swoim blogu Shay Shmeltzer. Egzamin oficjalnie nosi nazwę Oracle Application Development Framework Essentials (1Z1-554) i jak na razie jest w wersji beta – czyli ma służyć głównie wysądowaniu zainteresowania ścieżką certyfikacji z zakresu technologii Oracle Fusion oraz ustaleniu ostatecznego zakresu tematycznego. Dla bywalców forum dyskusyjnego OTN nie jest to zaskoczenie. Temat certyfikatów z ADF jest cyklicznie poruszany od co najmniej trzech lat. Na temat problematyki egzaminu można poczytać tutaj. Moje osobiste wrażenia są takie – sporo tego – nawet dla doświadczonego developera. Wrzucono do jednego worka w zasadzie całość technologii, lekko nawet poza nią wykraczając (ADF + SOA, WebCenter). Zresztą sam Shay napisał:
„...this is not a trivial exam - you should only apply if you actually have practical experience developing with ADF”.
Nie wiem też, jaka będzie forma tego egzaminu. Część pytań jest czysto teoretyczna, niektóre zagadnienia natomiast w zasadzie nie do opisania bez użycia JDeveloper'a. Cóż, wysłałem maila do autora postu i być może wkrótce będę wiedział coś więcej. Postaram się też skonfrontować zakres tematyczny egzaminu z tym, co napisane jest w dostępnej literaturze przedmiotu.

czwartek, 25 listopada 2010

Kolejny OTN Developer Day za mną

To był bez wątpienia jeden z bardziej produktywnie spędzonych ostatnio dni:). Naprawdę było warto. Tym razem cykliczna impreza organizowana przez Oracle poświęcona była m.in. cechom serwera GlassFish i planom jego rozwoju w przyszłości. Większa część zajęć, skupiona była jednak na nowościach w Javie EE 6, czyli EJB 3.1, JAX-RS, JSF 2.0, CDI, Servlet 3.0 i Java Persistence API 2.0. Wszystko oczywiście w wykorzystaniem powyższego serwera i NetBeans'a. Jedyny minus to ograniczony czas na zajęcia praktyczne, co zaskutkowało tym, że chyba nikt nie przerobił wszystkich ćwiczeń. Na szczęście każdy uczestnik dostał komplet zadań na płytce. Obiecałem sobie przerobić je wszystkie i oczywiście w miarę możliwości podzielić się tutaj wrażeniami.

P.S. Dzieląc się miedzy sobą opiniami na temat szóstki zgodnie stwierdziliśmy, że nie da się pisać zapytań przy pomocy Criteria API :).

sobota, 23 października 2010

Java Authentication and Authorization Service (JAAS) od podstaw, czyli jak stworzyć własny moduł logowania

Dzisiejszy wpis poświęcony będzie podstawom implementacji szkieletu JAAS odpowiedzialnego w Javie za autentykację do programu i autoryzację dostępu. Ideą JAAS jest swobodne łączenie modułów odpowiedzialnych za bezpieczeństwo - PAM (Pluggable Authentication Modules). Poniższy przykład zaczniemy od utworzenia klasy CustomPrincipal przechowującej nazwę naszego podmiotu (Subject) zgłaszającego prośbę o uwierzytelnienie - dla uproszczenia nazywajmy go dalej użytkownikiem. Musi ona implementować interfejsy: java.security.Principal i java.io.Serializable oraz zawierać metody toString() i equals(). Subject może przechowywać wiele obiektów Principal takich jak np: grupy, jednak na potrzeby niniejszego przykładu wykorzystamy tylko jeden.

package kuba.demo.jaas.module;

import java.io.Serializable;
import java.security.Principal;
import java.util.logging.Logger;

public class CustomPrincipal implements Principal, Serializable {
 
 private static Logger log =Logger.getLogger(CustomPrincipal.class.getName());
 private static final long serialVersionUID = 1L;
 
 private String name;

 
    public CustomPrincipal(String name) {
     
     if (name == null){
      log.severe("Brak principal name");
         throw new NullPointerException("Principal name is null");
     }
     this.name = name;
   }


    @Override
    public String getName() {
 return name;
     }

 
    public String toString() {
     return("CustomPrincipal-" + name);
    }


    public boolean equals(Object o) {
     if (o == null){
         return false;
     }
        if (this == o){
               return true;
        }
        
        if (!(o instanceof CustomPrincipal)){
                return false;
        }
        
        CustomPrincipal that = (CustomPrincipal)o;

     if (this.getName().equals(that.getName())){
         return true;
     }
    
     return false;
    }
     
    
    public int hashCode() {
     return name.hashCode();
    }
}


Kolejną wymaganą klasą jest CustomCallbackHandler - implementuje interfejs javax.security.auth.callback.CallbackHandler. Można powiedzieć, że jest ona pośrednikiem pomiędzy naszą aplikacją, a usługą logowania, przekazując jej nasze dane uwierzytelniające (credentials).

package kuba.demo.jaas.module;

import java.io.IOException;
import java.util.logging.Logger;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;

public class CustomCallbackHandler implements CallbackHandler {
 
 private static Logger log =Logger.getLogger(CustomCallbackHandler.class.getName());
 
 private String user;
 private String password;

 public CustomCallbackHandler(String user, String password) {
  this.user = user;
  this.password = password;
 }

 @Override
 public void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException {
  
  NameCallback nameCallback  = (NameCallback)callbacks[0];
  PasswordCallback passwordCallback = (PasswordCallback)callbacks[1];
  nameCallback.setName(user);
  passwordCallback.setPassword(password.toCharArray());
  log.info("Inicjalizacja callback handler'a dla uzytkownika: "+user);
 }
}

Pozostała jeszcze klasa odpowiadająca za uwierzytelnienie użytkownika czyli właściwy moduł logowania. Moduł taki - CutomLoginModule.java - musi implementować interfejs javax.security.auth.spi.LoginModule. Cała logika odpowiedzialna za uwierzytelnienie odbywa się w metodzie login(). Możemy w niej sięgnąć do bazy danych lub serwera usług katalogowych. Jeśli autentykacja nie powiedzie się, zwrócony zostanie wyjątek javax.security.auth.login.LoginException. Jeśli logowanie będzie poprawne wywoływana jest metoda commit(). Przesłaniając ją możemy przypisujemy naszemu uwierzytelnionemu obiektowi (Subject) odpowiednie właściwości. W przypadku gdy któraś z powyższych metod zwróci błąd, wywoływana jest metoda abort().

package kuba.demo.jaas.module;

import java.security.Principal;
import java.util.Map;
import java.util.logging.Logger;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class CustomLoginModule implements LoginModule{
 
 private static Logger log =Logger.getLogger(CustomLoginModule.class.getName());
 
 private Subject subject;
 private CallbackHandler callbackHandler;
 private Map sharedState;
 private Map options;
 private String userName;
 private String userPassword;
 
 
 @Override
 public void initialize(Subject subject, CallbackHandler callbackHandler,
         Map sharedState, Map options) {
  
  this.subject = subject;
  this.callbackHandler = callbackHandler;
  this.sharedState = sharedState;
  this.options = options;
 }

 
 @Override
 public boolean login() throws LoginException {
  
  NameCallback nameCallback = new NameCallback("Username");
  PasswordCallback passwordCallback = new PasswordCallback("Password",false);
  
  Callback[] callbacks = new Callback[]{ nameCallback, passwordCallback };
  
  try {
    callbackHandler.handle(callbacks);
    
  } catch (Exception e) { 
   e.printStackTrace();
  }
  
  userName = nameCallback.getName();
  userPassword = String.valueOf(passwordCallback.getPassword());
  
  if("jakub.pawlowski".equals(userName) && "my_password".equals(userPassword)){
   
   log.info("Poprawnie zalogowano uzytkownika [ "+userName+" ]");
   return true;
   
  }else{
   
   log.info("Logowanie nie powiodlo sie");
   return false;
  }
 }

 
 @Override
 public boolean commit() throws LoginException {
  
  Principal principal = new CustomPrincipal(userName);
  subject.getPrincipals().add(principal);
  userPassword = null;
  log.info("Dodano obiekt CustomPrincipal");
  
  return true;
 }

 
 @Override
 public boolean abort() throws LoginException {
  
  log.info("Wywolanie abort()");
  userName = null;
  userPassword = null;
  return true;
 }

 
 @Override
 public boolean logout() throws LoginException {
  
  log.info("Uzytkownik [ "+userName+" ] zostal wylogowany");
  userName = null;
  userPassword = null;
  return true;
 }

}

Ostatnim krokiem jest utworzenie pliku konfiguracyjnego wskazującego na mój moduł logowania. Ja nazwałem go custom_jaas.config

MyLoginModule
{
 kuba.demo.jaas.module.CustomLoginModule REQUIRED;
};

Czas na sprawdzenie jak to wszystko działa. Poniżej prosty klient, należy go uruchomić z opcją wskazującą na plik konfiguracyjny: -Djava.security.auth.login.config=="e:/custom_jaas.config"

package kuba.demo.jaas.client;

import java.security.Principal;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import kuba.demo.jaas.module.CustomCallbackHandler;
import kuba.demo.jaas.module.CustomPrincipal;

public class CustomLoginModuleClient {

 private static Logger log =Logger.getLogger(CustomLoginModuleClient.class.getName());
 
 public static void main(String[] args) {
  
  CustomCallbackHandler callbackHandler = new CustomCallbackHandler("jakub.pawlowski", "my_password");
  
  try {
   LoginContext loginContext = new LoginContext("MyLoginModule",callbackHandler);
   loginContext.login();
   
   Subject s = loginContext.getSubject();
   Set ps = s.getPrincipals();
   Iterator iter = ps.iterator();
    
    while(iter.hasNext()){
     
     Principal principal = (Principal)iter.next();
     
     if(principal instanceof CustomPrincipal){
      
      log.info("Principal [ "+principal.getName()+" ]");
     }
    }
    
    loginContext.logout();
   
  } catch (LoginException e) {
   e.printStackTrace();
  }

 }
}


Jak widać poniżej autoryzacja przebiegła poprawnie

INFO: Poprawnie zalogowano uzytkownika [ jakub.pawlowski ]
2010-10-23 19:28:09 kuba.demo.jaas.module.CustomLoginModule commit
INFO: Dodano obiekt CustomPrincipal
2010-10-23 19:28:09 kuba.demo.jaas.client.CustomLoginModuleClient main
INFO: Principal [ jakub.pawlowski ]
2010-10-23 19:28:09 kuba.demo.jaas.module.CustomLoginModule logout
INFO: Uzytkownik [ jakub.pawlowski ] zostal wylogowany