Uszczelnianie aplikacji z Grails i JSecurity – ciąg dalszy

Po ostatnim prostym zabezpieczeniu aplikacji Grailsowej z użyciem wtyczki JSecurity pozostał pewien niedosyt. Nasz przykład – i owszem – skutecznie zabezpieczył dostęp osób niezalogowanych, ale nie pokazał prawdziwej potęgi JSecurity – możliwości autoryzacji, czyli sprawdzania praw dostępu do określonych zasobów. Wyglądało to trochę tak, jakbyśmy wytaczali armatę po to by zabić muchę, dlatego teraz spróbujemy tą armatą zapolować na grubszego zwierza ;) wciąż jednak pozostając przy założeniu, że ma być prosto i estetycznie na ile to możliwe.
Przyjmijmy zatem, że nasza aplikacja ma:

  • dopuszczać tylko zalogowanych użytkowników (tak, jak poprzednio),
  • pozwalać na oglądanie zasobów wszystkim zalogowanym użytkownikom (posiadającym rolę “users”),
  • pozwalać na edycję/usuwanie/tworzenie nowych zasobów tylko użytkownikom posiadającym rolę “admin” (pozostali użytkownicy dostają informację o braku dostępu)

Jak widzisz, wykorzystamy szkielet poprzedniej aplikacji. Zauważ też, że wprowadzamy pojęcie roli, czym jest rola?
Zgodnie z infomacją na stronie wtyczki, to

a job or a set of responsibilities that a person can have

, czyli zbiór odpowiedzialności, które posiada dany użytkownik w systemie. System, oczywiście, musi takiemu użytkownikowi udostępnić możliwość wykonania czynności wchodzących w skład takich odpowiedzialności. Upraszczając – posiadanie danej roli pozwala na dostęp do zasobów systemu, do których dostęp bez niej jest zabroniony.
Założeniem naszej aplikacji jest, aby każdy zalogowany użytkownik mógł oglądać dane, jednak modyfikacja jakichkolwiek informacji wiąże się z obowiązkiem posiadania roli admina.

Zaczynajmy.

1. Dodajemy nowe klasy dziedzinowe

Musimy zdefiniować nowe klasy dziedzinowe, które pozwolą nam utrwalać informacje o rolach i powiązaniach ich z użytkownikami.

class Role {
    String name
}
class UserRoleRef {
    User user
    Role role
}

Dzięki takim klasom dziedzinowym (i klasie User z poprzedniego wpisu) możliwa będzie realizacja referencji wiele-do-wielu – jeden użytkownik może mieć wiele ról w systemie, a jedna rola może być w posiadaniu wielu użytkowników.

Możemy teraz zapisać wstępnie uprawnienia dostępu do naszego systemu, np. tak:

// użytkownicy
def jasiu = new User(username: "jasiu", password: new Sha1Hash("sekret").toHex()).save()
def admin = new User(username: "admin", password: new Sha1Hash("admin").toHex()).save()
 
// role
def usersRole = new Role("users").save()
def adminRole = new Role("admin").save()
 
// powiązania user-role
new UserRoleRef(user: jasiu, role: usersRole).save()
new UserRoleRef(user: admin, role: adminRole).save()

2. Uaktualniamy filtr

Role i powiązania z użytkownikami mamy już zdefiniowane i zapisane w bazie danych, jednak wciąż nie wiemy po co nam one. Samo posiadanie praw “admina” nic nie daje, jeśli nie wiemy, co one dają. Uaktualniamy nasz plik SecurityFilters.groovy tak:

class SecurityFilters {
    def filters = {
        auth(controller: "*", action: "*") {
            before = {
                accessControl { true }
            }
        }
 
        manageRecord(controller: "*", action: "(create|edit|save|update|delete)") {
            before = {
                accessControl {
                    role("admins")
                }
            }
        }
 
        showRecord(controller: "*", action: "show") {
            before = {
                accessControl {
                    role("admins") || role("users")
                }
            }
        }
    }
}

Jak widać, mamy zdefiniowane dwie reguły dostępu do dowolnego kontrolera (gwiazdka), ale z wyszczególnionymi akcjami. Akcja show wymaga posiadania roli “users”, albo “admins” (czyli dowolnej, na chwilę obecną), a akcje pozwalające na modyfikacje informacji – roli “admins”.

3. Rozszerzamy funkcjonalność MyRealm

Czas na fragment kodu, który będzie odpowiadał za sprawdzanie, czy dany użytkownik posiada daną rolę. Wzbogacamy naszą klasę MyRealm w dwie dodatkowe (poza authenticate()) metody:

    def hasRole(principal, roleName) {
        def criteria = UserRoleRef.createCriteria()
        def roles = criteria.list {
            role {
                eq('name', roleName)
            }
            user {
                eq('username', principal)
            }
        }
 
        return roles.size() > 0
    }
 
    def hasAllRoles(principal, roles) {
        def criteria = UserRoleRef.createCriteria()
        def r = criteria.list {
            role {
                'in'('name', roles)
            }
            user {
                eq('username', principal)
            }
        }
 
        return r.size() == roles.size()
    }

Obie są dość proste – pierwsza sprawdza istnienie referencji użytkownik-rola dla zadanych parametrów, druga metoda robi to samo, ale ze zbiorem ról zadanych jako parametr.

4. Dodajemy widok informujący o braku dostępu

Założenia mamy takie, żeby użytkownik bez dostępu do konkretnego zasobu został o tym poinformowany komunikatem. W tym celu wprowadzamy drobne usprawnienie do naszego kontrolera AuthController. Wprowadzamy domknięcie unauthorized, które będzie wykonane za każdym razem, gdy użytkownik spróbuje wykonać akcję, do której nie ma dostępu.

def unauthorized = {
}

… oraz plik widoku dla takiej akcji (views/auth/unauthorized.gsp):

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Brak dostępu</title>
  </head>
  <body>
    <h1>Dostęp zabroniony</h1>
  </body>
</html>

5. Uaktualnianie menu

Nasze zabezpieczanie właściwie się zakończyło, jednak warto jeszcze zadbać o dodatkową rzecz. Jeśli pracujemy na widokach wygenerowanych przez Grails (np. za pomocą komendy grails generate-all <klasa_dziedzinowa>), to każdy z widoków wyposażony jest w przyciski/odnośniki kierujące do stron pozwalających na modyfikację/dodawanie/usuwanie rekordów, np. takich:

<div class="nav">
    <span class="menuButton"><a class="home" href="${createLinkTo(dir:'')}">Home</a></span>
    <span class="menuButton">New Book</span>
</div>

Teoretycznie nic się nie stanie, jeśli tak to zostawimy, bo nawet kliknięcie na link New Book nie spowoduje wygenerowania formularza tworzenia nowej książki, tylko skieruje nas do strony z informacją o braku dostępu. Jednak… czego oczy nie widzą, tego sercu nie żal :) więc warto ukryć przed użytkownikiem te pozycje menu, które nie są mu potrzebne. Wykorzystamy więc znacznik jsec:hasRole:

<div class="nav">
    <span class="menuButton"><a class="home" href="${createLinkTo(dir:'')}">Home</a></span>
    <jsec:hasRole name="admins">
        <span class="menuButton"><g:link class="create" action="create">New Book</g:link></span>
    </jsec:hasRole>
</div>

Od tej pory odnośnik New Book pokaże się tylko użytkownikom posiadającym rolę “admins”. Takie modyfikacje możemy wprowadzić we wszystkich widokach naszej aplikacji.

I to właściwie wszystko jeśli chodzi o kontrolę ról za pomocą JSecurity w Grails.

Dość ciekawą rzeczą, która warta jest poznania, ale nie opisałem jej tutaj (miało być prosto, więc nie chciałem dodatkowo komplikować) jest kontrola uprawnień za pomocą JSecurity. Wtyczka dla Grails pozwala na kontrolę uprawnień nie tylko na poziomie kontroler/akcja, ale też np. dostępu do plików, albo innych zasobów systemowych.

Tagi: ,

Skomentuj

Proste uwierzytelnianie w Grails z JSecurity

Pisałem ostatnio aplikację webową, która wymagała bardzo prostego uwierzytelniania. Grails – jak się okazuje – ma wtyczkę JSecurity, dzięki której można realizować proste uwierzytelnianie, ale i bardziej skomplikowane operacje autoryzacji. Wtyczka JSecurity dla Grails jest wyposażona w fajny QuickStart, który to już po instalacji pozwala na całkiem kompleksowe zabezpieczenie aplikacji. Mi jednak potrzebny był ekstremalnie prosty mechanizm, na szczęście wtyczka zaoferowała coś w sam raz.

Poniższy wpis jest oparty właśnie na wspomnianym wyżej QuickStarcie, jednak został uproszczony i ograniczony jedynie do mechanizmu autentykacji (uwierzytelniania), nie zaś autoryzacji (kontroli dostępu do zasobów) użytkowników.

Dostęp do aplikacji z JSecurity jest realizowany za pomocą przestrzeni (ang. realms), można skonstruować mechanizm autentykacji (i autoryzacji) na różne sposoby, np. za pomocą katalogów LDAP, loginów systemowych, usług sieciowych itd. My wykorzystamy najbardziej tradycyjny mechanizm: sprawdzania użytkowników zapisanych w bazie danych. JSecurity realizuje mechanizm weryfikacji dostępu do poszczególnych zasobów systemu za pomocą przynależności użytkowników do ról, jednak mój przykład będzie znacznie prostszy – wystarczy, że użytkownik będzie istniał w bazie danych, a już będzie miał dostęp do zasobów systemu.

Oto jak wzbogacić naszą aplikację o wymaganie autentykacji użytkowników, kompletnie nie zajmując się kwestiami autoryzacji dostępu do zasobów (wszyscy zalogowani użytkownicy mają dostęp):

1. Instalujemy wtyczkę

grails install-plugin jsecurity

2. Tworzymy klasę dziedzinową użytkownika:

class User {
    String username
    String password
 
    static mapping = {
        table "users"
    }
 
    public String toString() {
        username
    }
}

Używam tutaj mapowania na inną niż domyślna nazwę tabeli (domyślnie byłaby “user”, a ja dałem “users”) – taką kiedyś mi wpojono konwencję, by tabele miały nazwy w liczbie mnogiej. Z kolei metoda toString() przyda się nam nieco później.

Sama tabelka nie wystarczy, trzeba jeszcze użytkownika zapisać w bazie. Możemy to zrobić używając poniższego fragmentu kodu:

new User(username: "jasiu", password: new Sha1Hash("sekret").toHex()).save()

Jak widzisz, hasło jest kodowane za pomocą algorytmu Sha1 (kodowanie to wspierane jest przez JSecurity), nie obędzie się więc bez:

import org.jsecurity.crypto.hash.Sha1Hash

3.  Dodajemy do katalogu grails-app/conf/ pliczek SecurityFilters.groovy o zawartości:

class SecurityFilters {
    def filters = {
        auth(controller: "*", action: "*") {
            before = {
                accessControl { true }
            }
        }
    }
}

Ten filtr spowoduje, że wszystkie żądania (gwiazdki oznaczają dowolny kontroler, i dowolną akcję) zostaną poddane działaniu naszego filtra, a ten, przed wykonaniem żądania, wymusi na nim sprawdzenie praw dostępu.

4. W katalogu realms/ tworzymy klasę MyDbRealm, o postaci:

import org.jsecurity.authc.AccountException
import org.jsecurity.authc.IncorrectCredentialsException
import org.jsecurity.authc.UnknownAccountException
import org.jsecurity.authc.SimpleAccount
 
class MyDbRealm {
    static authTokenClass = org.jsecurity.authc.UsernamePasswordToken
 
    def authenticate(authToken) {
        def username = authToken.username;
        def user = User.findByUsername(username);
        if (!user) {
            throw new UnknownAccountException("Użytkownik ${username} nie istnieje w bazie danych")
        }
 
        def account = new SimpleAccount(username, user.password, "MyDbRealm")
        if (!credentialMatcher.doCredentialsMatch(authToken, account)) {
            log.info 'Nieprawidłowe hasło dla użytkownika ${user.username}'
            throw new IncorrectCredentialsException("Nieprawidłowe hasło dla użytkownika ${user.username}")
        }
        return user;
    }
}

5. Tworzymy kontroler naszego mechanizmu uwierzytelniania AuthController (zapisujemy go oczywiście w katalogu controllers/ naszej aplikacji):

import org.jsecurity.authc.AuthenticationException
import org.jsecurity.authc.UsernamePasswordToken
import org.jsecurity.SecurityUtils
 
class AuthController {
    def jsecSecurityManager
 
    def index = { redirect(action: 'login', params: params) }
 
    def login = {
        return [ username: params.username, targetUri: params.targetUri ]
    }
 
    def signIn = {
        def authToken = new UsernamePasswordToken(params.username, params.password)
 
        try{
            this.jsecSecurityManager.login(authToken)
            def targetUri = params.targetUri ?: "/"
            redirect(uri: targetUri)
        }
        catch (AuthenticationException ex){
            log.info "Błąd logowania użytkownika: '${params.username}'."
            flash.message = "Logowanie nieudane"
 
            def m = [ username: params.username ]
            if (params.targetUri) {
                m['targetUri'] = params.targetUri
            }
 
            redirect(action: 'login', params: m)
        }
    }
 
    def signOut = {
        SecurityUtils.subject?.logout()
        redirect(uri: '/')
    }
}

6. Tworzymy okienko logowania (views/auth/login.gsp):

 
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta name="layout" content="main" />
  <title>Login</title>
</head>
<body>
  <g:if test="${flash.message}">
    <div class="message">${flash.message}</div>
  </g:if>
  <g:form action="signIn">
    <input type="hidden" name="targetUri" value="${targetUri}" />
    <table>
      <tbody>
        <tr>
          <td>Username:</td>
          <td><input type="text" name="username" value="${username}" /></td>
        </tr>
        <tr>
          <td>Password:</td>
          <td><input type="password" name="password" value="" /></td>
        </tr>
        <tr>
          <td />
          <td><input type="submit" value="Zaloguj" /></td>
        </tr>
      </tbody>
    </table>
  </g:form>
</body>
</html>

7. Modyfikujemy nasz główny layout tak, by pokazał czy i jako kto jesteśmy zalogowani:

<jsec:isLoggedIn>
    <div>Witaj <jsec:principal/>! (<g:link controller="auth" action="signOut">Wyloguj</g:link>)</div>
</jsec:isLoggedIn>

I tu właśnie przydała nam się metoda toString() klasy User, albowiem znacznik jsec:principial zwróci nam właśnie wartość obiektu, który przedstawia zalogowanego aktualnie użytkownika (to, co zwróciła metoda authenticate() klasy MyDbRealm), a ten reprezentowany jest właśnie przez toString().
Tag jsec:isLoggedIn wraz z zawartością umieszczamy np. nad użyciem znacznika g:layoutBody. Dla chcących potestować inne tagi dostarczane przez wtyczkę polecam lekturę ich listy.

Gotowe, aplikacja zabezpieczona, a my możemy spać spokojnie :)

I na koniec, dwa linki, jako uzupełnienie tematu zabezpieczania aplikacji Grails z JSecurity:

Tagi: ,

Komentarze (2)

Upiększanie JTable

Przy okazji moich zabaw ze Swingiem – krótko o tym jak ulepszyć JTable.

Mamy sobie JTable taką:

przed

A będziemy chcieli taką:

po1

Wzbogacimy więc naszą tabelkę o bardzo ładne oznaczenie numerów wierszy, oraz kolorowanie co drugiego rekordu.

Zabieg pierwszy: kolorowanie co drugiego wiersza

Aby używać warunkowego kolorowania wierszy, musimy wskazać naszej tabelce, by wykorzystała obiekt klasy pochodnej od DefaultTableCellRenderer, np. takiej:

class ColorCellRenderer extends DefaultTableCellRenderer {
 
    @Override
    public Component getTableCellRendererComponent( JTable table, Object val, boolean selected, boolean focused, int row, int col ) {
        Component comp = super.getTableCellRendererComponent( table, val, selected, focused, row, col );
        if( selected == false ) {
            if( ( row % 2 ) == 1 ) {
                comp.setBackground( color );
            }
            else {
                comp.setBackground( null );
            }
        }
        return comp;
    }
}

Zabieg drugi: numeracja wierszy

Na forum Suna znalazłem b. stary, ale nadal ciekawy przykład rozwiązania tego problemu. Budujemy klasę LineNumberTable, która dziedziczy po JTable:

public class LineNumberTable extends JTable {
    private JTable mainTable;
 
    public LineNumberTable( JTable table ) {
        super();
        mainTable = table;
        setAutoCreateColumnsFromModel( false );
        setModel( mainTable.getModel() );
        setSelectionModel( mainTable.getSelectionModel() );
        setAutoscrolls( false );
 
        addColumn( new TableColumn() );
        getColumnModel().getColumn( 0 ).setCellRenderer( mainTable.getTableHeader().getDefaultRenderer() );
        getColumnModel().getColumn( 0 ).setPreferredWidth( 30 );
        setPreferredScrollableViewportSize( getPreferredSize() );
    }
 
    @Override
    public boolean isCellEditable( int row, int column ) {
        return false;
    }
 
    @Override
    public Object getValueAt( int row, int column ) {
        return new Integer( row + 1 );
    }
 
    @Override
    public int getRowHeight( int row ) {
        return mainTable.getRowHeight();
    }
}

OK, teraz wystarczy wykorzystać utworzone klasy w naszej JTable:

    public MyPanel() {
        initComponents();
        // wskazujemy nowy renderer, dla obiektów wszystkich typów naszej tabelki
        jTable1.setDefaultRenderer( Object.class, new ColorCellRenderer() );
        // scrollPane, którym mamy osadzoną tabelę dekorujemy oznaczeniem numerów wierszy
        jScrollPane1.setRowHeaderView( new LineNumberTable( jTable1 ) );
    }

Gotowe. Prawda, że ładniej?

Tagi: , ,

Skomentuj

NetBeans i ujednolicanie JDialog

Miałem niedawno okazję dłużej popracować nad projektami Javy w NetBeans 6.5 (wcześniej poważniej NetBeans używałem tylko do Grails). Środowisko to nie było dotychczas moim ulubionym, głównie ze względu na wydajność, co do której można mieć spore zastrzerzenia, jednak zauważyłem, że ma ono również mnóstwo zalet. Pierwszą jaką dostrzegłem jest to, że projekty generowane przez to IDE są naprawdę ładnie utworzone, wykorzystują standardowe narzędzia Javy, takie jak np. Ant i nie uzależniają użytkownika od używania “jedynego i właściwego” IDE. NetBeans może zauroczyć również stopniem dopracowania swoich elementów – jak już coś wspiera, to wspiera naprawdę dobrze. Przykładem tu może być sposób, w jaki można za pomocą tego środowiska utworzyć i zarządzać aplkacjami wykorzystującymi Web Services w aplikacji – zarówno klientem, jak i usługą wykorzystującącymi API JAX-WS. Kolejny (lecz na pewno nie ostatni) plus należy się NetBeans za właściwie bezkonkurencyjny edytor wizualny aplikacji wykorzystujących Swing.

I o Swingu właśnie będzie ten wpis (nieco przydługi, ostrzegam).

Nie do końca podoba mi się domyślna filozofia okien dialogowych w Javie. Np. rozmiar – niby można zmieniać rozmiar “świeżo” utworzonego okienka dialogowego rozciągając je, ale menadżery okien nie pozwalają na jego maksymalizację (brakuje bowiem przycisku maksymalizacji). Wiem, że takie jest założenie okienek dialogowych, ale wyczuwam tu pewną niekonsekwencję. Ponadto “goły” JDialog jest praktycznie pozbawiony funkcjonalności, np. brakuje mu domyślnych zachowań takich jak potwierdzanie i anulowanie, reakcję na przycisk Escape i Enter – te braki sprawiają, że każda implementacja okienka dialogowego musi być poprzedzona żmudnym wykonywaniem tych banalnych czynności.

Postanowiłem więc wykonać coś na wzór bazy dla okna dialogowego, która to baza będzie wyposażona we wszystkie cechy, które wg mnie posiadać powinna każda implementacja JDialog. W takiej bazie będę umieszczał różne komponenty – w zależności od tego, jakiego dialogu potrzebuję.

szkic

Chcę uzyskać bazę dla dialogu, w której będę umieszczał przeróżne panele – począwszy od prostych, informacyjnych (wtedy taki dialog przypominałby funkconalnością JOptionPane), skończywszy na całkiem zaawansowanych funkcjonalnie formularzach edycji wykorzystujących panele, które przygotuję sobie osobno (dzięki temu będę mógł wykorzystać panele także w innych sytuacjach). Wszystkie jednak dialogi cechować się mają wspólnymi właściwościami: dialog można zatwierdzić (submit), albo odrzucić (cancel). Wciśnięcie Escape na aktywnym oknie powoduje jego zamknięcie (funkcjonalność identyczna do wciśnięcia przycisku Anuluj), a Enter – zatwierdzenie (kliknięcie OK).

W tym celu zbudowałem JDialog w NetBeans wybierając z menu projektu New->JDialog Form i nazwałem go DialogBase. W edytorze wizualnym umieściłem dwa przyciski (nazwałem je jBtnOK i jBtnCancel), oraz jeden JPanel (jPanelMain), z ustawionym layoutem BorderLayout – w tym panelu będę umieszczał przeróżne panele “implementacyjne” – w zależności od przeznaczenia mojego dialogu.

Na początek, domyślny konstruktor i obsługa własności panel (czyli obiekt panelu, który umieszczamy w oknie):

    private JPanel panel;
 
    public DialogBase(JPanel panel) {
        this( null, true );
        setLocationRelativeTo(null);
    }
 
    protected void setPanel(JPanel panel) {
        this.panel = panel;
        jPanelMain.removeAll();
        jPanelMain.add( panel, BorderLayout.CENTER );
        panel.setVisible( true );
        Dimension d = new Dimension(panel.getPreferredSize());
        this.setSize( d.width, d.height+70 );
    }
 
    protected JPanel getPanel() {
        return panel;
    }

Metoda setPanel() ustawia własność panel oraz zajmuje się osadzeniem go w oknie dialogu i ustawieniem odpowiedniego rozmiaru okna (wysokość zwiększam o wielkość przycisków na dole okna).

Aby wiedzieć w jaki sposób moje okno dialogowe zostało zamknięte, wyposażam je we własność exitStatus:

    public static final int DLG_EXIT_CANCEL = 0;
    public static final int DLG_EXIT_OK = 1;
 
    private int exitStatus = DLG_EXIT_CANCEL;
 
    // getter i setter

Domyślne zamknięcie okna poskutkuje zapamiętaniem statusu “Cancel”.

Czas na obsługę klawisza Escape i Enter. Posiłkując się ciekawym na ten temat artykułem, wzbogaciłem mój DialogBase o następujący kod:

    @Override
    protected JRootPane createRootPane() {
        JRootPane theRootPane = new JRootPane();
 
        KeyStroke escStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 );
        theRootPane.registerKeyboardAction( new ActionListener() {
            public void actionPerformed( ActionEvent actionEvent ) {
                doCancel();
            }
        }, escStroke, JComponent.WHEN_IN_FOCUSED_WINDOW );
 
        KeyStroke entStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 );
        theRootPane.registerKeyboardAction( new ActionListener() {
            public void actionPerformed( ActionEvent actionEvent ) {
                doSubmit();
            }
        }, entStroke, JComponent.WHEN_IN_FOCUSED_WINDOW );
 
        return theRootPane;
    }

Jak widzisz, wykorzystuję tu tajemnicze metody doSubmit() i doCancel(). Co to za metody?

    private void doSubmit() {
        if( onSubmit() ) {
            setExitStatus( DLG_EXIT_OK );
            dispose();
        }
    }
 
    private void doCancel() {
        if( onCancel() ) {
            setExitStatus( DLG_EXIT_CANCEL );
            dispose();
        }
    }
 
    protected boolean onSubmit() {
        return true;
    }
 
    protected boolean onCancel() {
        return true;
    }

Obie metody działają dość podobnie: jeśli metoda onSubmit() (lub analogicznie onCancel()) zwróci true, ustawiamy odpowiedni exitStatus i zamykamy okno. Zarówno doSubmit() jak i doCancel() wiążę też za pomocą zdarzenia ActionEvent z przyciskami (wystarczy dwukrotnie kliknąć na przycisku w edytorze wizualnym i wyedytować wygenerowaną przez NetBeans pustą metodę):

    private void jBtnCancelActionPerformed(java.awt.event.ActionEvent evt) {
        doCancel();
    }
 
    private void jBtnOKActionPerformed(java.awt.event.ActionEvent evt) {
        doSubmit();
    }

OK, ale po co nam te trywialne metody onSubmit() i onCancel()? Wykorzystamy je np. do walidacji w przyszłych implementacjach naszych okien dialogowych (jeśli zdecydujemy się na ich przesłonięcie, jeśli nie – niczego złego nie zrobią ;) ).

Wygląda na to, że nasza baza jest gotowa. Użyjmy jej więc na jakimś przykładzie – np. formularzu logowania.

W NetBeans, tworzymy nowy JPanel Form, nazywamy go PanelLogin i dodajemy odpowiednie elementy interfejsu użytkownika (jak na załączonym wyżej szkicu), oczywiście to ten panel będziemy osadzać w bazie dialogu, więc nie dodajemy już przycisków OK i Anuluj, bo nie musimy. Element JTextField, zawierający nazwę użytkownika nazywamy jTxtUsername, a JPasswordField (z hasłem) – jTxtPassword.
Dodajemy dwie publiczne metody do pobrania wprowadzonych nazwy użytkownika i hasła:

    public String getUsername() {
        return jTxtUsername.getText();
    }
 
    public String getPassword() {
        return new String(jTxtPassword.getPassword());
    }

A teraz nasz dialog logowania.
W NetBeans tworzę nową klasę Javy (New->Java Class), a nie dialog(!) i nazywam ją DialogLogin. Nasza nowa klasa musi dziedziczyć po DialogBase. Ponadto, nasz dialog powinien zwracać obiekt użytkownika (jakiejś klasy np. User, której szczegóły nie są tu istotne) jeśli logowanie się powiedzie. W tym celu powołujemy własność returnValue (wraz z getterem i setterem). Przy okazji dodaję też licznik prób logowania – proste umożliwienie trzykrotnej pomyłki przy logowaniu.

public class DialogLogin extends DialogBase {
    private User returnValue;
    private int tries = 3;
 
    public DialogLogin() {
        super(new PanelLogin());
    }
}

Czas na obsługę logiki biznesowej naszego dialogu. Anulowanie dialogu zostawiamy bez zmian – po prostu zamykamy okno. Jednak musimy zaimplementować obsługę zatwierdzania formularza. W tym celu przesłaniamy naszą metodę onSubmit():

    @Override
    protected boolean onSubmit() {
        PanelLogin thePanel = ( PanelLogin ) this.getPanel();
 
        User u = App.login( thePanel.getUsername(), thePanel.getPassword() );
        if( u == null ) {
            JOptionPane.showMessageDialog( this, "Nie udało się zalogować");
            return (--tries<=0);
        }
 
        returnValue = u;
        return true;
    }

Jeśli App.login() (przyjmijmy, że ta metoda odpowiada za autoryzację aplikacji) zwróci null, to znaczy, ze nie zalogowaliśmy się do aplikacji – dialog nie zamknie się, ale pokaże nam info o nieprawidłowych parametrach logowania (i tak do momentu aż wartość tries osiągnie wartość mniejszą lub równą zeru). Jeśli użytkownik jest prawidłowy, dialog zamknie się ustawiwszy uprzednio własność returnValue.

Powinno działać. Sprawdźmy:

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
        }
        catch( Exception ex ) {}
 
        DlgLogin dlg = new DlgLogin();
        dlg.setVisible( true );
        System.err.println("Logowanie zakończone: "+dlg.getExitStatus());
        if( dlg.getExitStatus() == DialogBase.DLG_EXIT_OK ) {
            if( dlg.getReturnValue() != null ) {
                System.err.println("W porządku, użytkownik prawidłowy");
            }
            else {
                System.err.println("Brak dostępu!");
            }
        }
        else {
            System.err.println("Anulowano!");
        }
    }

Voila!

We wpisie użyłem ikon Free Business Icons opublikowanych na licencji Creative Commons oraz Silk na licencji LGPL.

Tagi: , , ,

Skomentuj

Zrób sobie przerwę

Dość intensywnie ostatnio pracuję (dużo pracy, a do tego taka ilość ciekawych rzeczy dookoła, że nie sposób nie poświęcić na ich badanie wieczora lub dwóch), do pracy tej używam rzecz jasna komputera. Odbiło się to niestety negatywnie na moim samopoczuciu, możnaby rzec, ze dopadło mnie coś na wzór RSI, tyle że dotyczyło wzroku i bólów głowy. Już tak mam, że kiedy “wkręcę” się w jakąś czynność, coś mnie zainteresuje, potrafię pracować bez przerw i kompletnie nie kontroluję tego ile czasu zajmuje mi praca. Skupiam się wtedy na wykonywanej czynności maksymalnie, zapominając o świecie poza nią. Postanowiłem więc coś w tej sprawie zrobić i – oprócz zdroworozsądkowego odstawienia na jakiś czas komputera – znaleźć narzędzie, które pozwoli mi nieco zadbać o swoje zdrowie w pracy z nim. Znalazłem dwa programy, ktore umożliwiają kontrolę nad przerwami.

Pierwszy z nich to EyesRelax. Programik ten umożliwia ustawienie odstępu czasowego między przerwami i – uruchomiony w tle – odlicza nam czas na do odpoczynku, gdy ten nadejdzie, EyesRelax zasłania ekran komputera białym tłem. Ciekawą jego funkcją jest Parent Mode, która pozwala na całkowite zablokowanie komputera na czas przerwy (odblokowanie może nastąpić dopiero po podaniu hasła). Programik ekstremalnie prosty, ale spełniający dokładnie swoje zadanie. Niestety jednak, nie do końca, bowiem jedna jego wada spowodowała, że zacząłem rozglądać się za innym rozwiązaniem. EyesRelax ma jakiś błąd, która sprawia, że białe plansze nie zawsze zasłaniają pulpit, często samoczynnie wędrują sobie w tło i – jeśli używa się zmaksymalizowanego okna jakiegoś programu – użytkownik nie ma pojęcia, że właśnie w tej chwili powinien zrobić przerwę. Ja niestety potrzebuję narzędzia, które – nawet brutalnie – wyrwie mnie z amoku programowania i każe odpocząć :) Dlatego zacząłem szukać dalej.

Znalazłem WorkRave. To narzędzie bardzo pozytywnie zaskoczyło mnie ilością możliwości konfiguracji, oraz samą koncepcją walki z RSI. Program ten oferuje trzy sposoby zabezpieczenia przed przepracowaniem – i wszystkie można stosować naraz:

  • Mikroprzerwy – krótkie odpoczynki pozwalające “odetchnąć”, przede wszytskim oczom i szyi,
  • Odpoczynki – dłuższe przerwy na np. spacer, śniadanie, ćwiczenia, albo po prostu zrobienie sobie herbaty,
  • Kontrola całkowitego czasu pracy – skutecznie powstrzymuje przed niechcianymi nadgodzinami.

Poza samym odliczaniem czasu, WorkRave mile zaskakuje możliwością dodania wstępnych “ostrzeżeń” o tym, że zbliża się przerwa, a także biblioteką porad dotyczących ćwiczeń (akomodacji oczu, mięśni szyi, rąk itd.) które możemy robić w trakcie odpoczynku – ilość proponowanych ćwiczeń jest całkiem spora, a same ich opisy dość szczegółowe (z obrazkami ;) ). Ponadto warto zauważyć, że program można przełączać w różne tryby pracy (Normalny, Cichy i Zawieszony), dzięki czemu nie jest on nachalny np. gdy oglądamy na komputerze film. Narzędzie to posiada też opcję pracy w sieci, ale nie udało mi się jej jeszcze przetestować.

Gorąco zachęcam do używania takich kontrolerów czasu pracy, niech najlepszym dowodem ich skuteczności będzie fakt, że skończyły się moje problemy z oczami i bólami głowy, ba – kiedy dokładnie stosuję się do zaleceń WorkRave, dzień w pracy mija mi tak, że wracam do domu w ogóle nie będąc zmęczonym!

Tagi: , ,

Komentarze (2)