Konektory


Wstęp

Zadaniem konektorów jPALIO jest realizowanie dostępu/zestawianie połączeń do dowolnych zasobów takich jak baza danych, zasoby sieciowe itd oraz udostępnianie API umożliwiającego wykonywanie operacji na danych zasobach. Przykładowo, najczęsciej wykorzystywanym konektorem w jPALIO jest konektor do bazy danych (SQLConnector). Realizuje on połączenie do bazy danych, a także udostępnia API do wykonywania zapytań SQL (odczyt, zapis).

Z każdym konektorem związany jest adres URL, który jednoznacznie powinien wskazywać zasób do którego będzie realizowany dostęp.

W jaki sposób stworzyć własny konektor jPALIO?

Klasą bazową dla wszystkich konektorów jPALIO jest klasa palio.connectors.Connector. Poprzez stworzenie klasy dziedziczącej po tej klasie można uzyskać prosty konektor, który jednak nie będzie posiadał wsparcia zarządzania pulą połączeń. W przypadku naturalnego dla jPALIO środowiska wielowątkowego dobrym rozwiązaniem, przez wzgląd na wydajność oraz oszczędność wykorzystanych zasobów, jest tworzenie konektorów wspierających pooling połączeń. jPALIO posiada wsparcie dla tworzenia konektorów wspierających zarządzanie pulą połączeń w postaci klasy palio.connector.PooledConnector.

Dobrą praktyką jest utworzenie modułu jPALIO związanego z danym konektorem udostępniającego jego API. Przykłady takich par:

W przypadku metod tych modułów, wykonujących dowolne operacje na konektorach, jako pierwszy argument podawana jest nazwa konektora, który powinien być użyty do wykonania danej operacji.

 

W tym rozdziale zostanie przedstawione w jaki sposób stworzyć konektor wspierający pooling połączeń.

Pierwszym krokiem jest utworzenie klasy dziedziczącej po klasie palio.connector.PooledConnector oraz klasy reprezentującej połączenie tworzone przez dany konektor. Klasa reprezentująca połączenie powinna być rozszerzeniem klasy palio.connector.PooledConnection. Klasa zapewnia ujednolicenie dostępu do różnego rodzaju połaczeń.

Klasa konektora powinna zapewnić implementację poniższych metod:

 

 protected PooledConnection newConnection(PooledConnection conn) throws Exception 

Metoda odpowiedzialna za tworzenie nowych połączeń. Jest wywoływana poprzez mechanizm zarządzania pulą połączeń w momencie gdy przy próbie pobrania połączenia żadne połączenie nie zostało do tej pory nawiązane lub wszystkie istniejące były w danej chwili zajęte.

 

void refreshConnection(PooledConnection connection) throws Exception

Metoda odpowiedzialna za podtrzymywania połączenia oraz diagnozowanie połączenia podczas procedury ponownego nawiązwyania połączenia (reconnect'u).

 

Klasa reprezentująca połączenie powinna zapewnić implementację poniższych metod:

 

protected void closeNormal()

Metoda odpowiedzialna za zamknięcie danego połączenia oraz zwolnienie zajętych zasobów. Metoda ta powinna być wykonywana w bieżącym wątku (synchronicznie)

 

protected void closeParaller()

Metoda odpowiedzialna za zamknięcie danego połączenia oraz zwolnienie zajętych zasobów. Metoda ta powinna być wykonywana w innym wątku (asynchronicznie). Używana jest do zamykania wielu połączeń bez blokady bieżącego wątku.

 

protected void setDedicated() throws PalioException

Metoda odpowiedzialna za ustawienie flagi oznaczającej, że dane połączenie jest przywiązane do bieżącego wątku i tylko bieżący wątek będzie mógł korzystać z tego połączenia do czasu gdy zostanie zwolnione. Metoda może wykonywać także inne, dodatkowe operacje, które są zależne od danego typu połączenia. Np. w przypadku konektora typu SQL jest to rozpoczęcie transakcji.

 

protected void unsetDedicated(

Metoda odpowiedzialna za zresetowanie flagi oznaczającej, że dane połaczenie jest przywiązane do bieżącego wątku. Metoda może wykonywać także inne, dodatkowe operacje, które są zależne od danego typu połączenia. Np. w przypadku konektora typu SQL jest to zakończenie transakcji oraz wycofanie zmian (rollback) jeżeli nie zostały wcześniej zatwierdzone (commit).

 

protected void resetExtend()

Metoda odpowiedzialna za wyczyszczenie "pozostałości" po poprzednim "użyciu". Jest wywoływana w monecie gdy po zakończeniu operacji na danym połaczeniu, połaczenie to wraca do puli wolnych połączeń.

Zobacz także:


[Moduły] W jaki sposób stworzyć własny moduł jPALIO?
 

Przykładowa implementacja konektora

Jako przykład zostanie przedstawiony prosty konektor realizujący odczytywanie linii wskazanego pliku. Wykorzystana zostanie klasa RandomAccessFile. Dodatkowe udostępnione operacje to zwrócenie bieżącej pozycji w pliku (getFilePointer) oraz ustawienie nowej pozycji (seek).

Implementacja konektora:

package palio.connectors;

import java.util.Properties;

import palio.PalioException;


public class SamplePooledConnector extends PooledConnector {
    
    private final String filePath;
    
    /**
     * Connector constructor
     * 
     * @param url URL in following format: file://tmp/lorem_ipsum.txt
     * @param properties Connector properties
     * @throws PalioException
     */
    public SamplePooledConnector(String url, Properties properties) throws PalioException {
        super(url, properties);
        filePath = url.substring(("file://".length() - 1));
    }
    
    @Override
    protected PooledConnection newConnection(PooledConnection conn) throws Exception {
        if (conn == null) {
            return new SamplePooledConnection(this);
        } else {
            return conn;
        }
    }
    
    @Override
    void refreshConnection(PooledConnection connection) throws Exception {
        // Do nothing
    }
    
    public String getFilePath() {
        return filePath;
    }
    
    public String readLine() throws PalioException {
        SamplePooledConnection conn = null;
        try {
            conn = (SamplePooledConnection) getConnection();
            return conn.getRaf().readLine();
        } catch (Exception ex) {
            throw new PalioException(ex);
        } finally {
            if (conn != null) {
                putConnection(conn);
            }
        }
    }
    
    public void seek(Long position) throws PalioException {
        SamplePooledConnection conn = null;
        try {
            conn = (SamplePooledConnection) getConnection();
            conn.getRaf().seek(position);
        } catch (Exception ex) {
            throw new PalioException(ex);
        } finally {
            if (conn != null) {
                putConnection(conn);
            }
        }
    }
    
    public Long getFilePointer() throws PalioException {
        SamplePooledConnection conn = null;
        try {
            conn = (SamplePooledConnection) getConnection();
            return conn.getRaf().getFilePointer();
        } catch (Exception ex) {
            throw new PalioException(ex);
        } finally {
            if (conn != null) {
                putConnection(conn);
            }
        }
    }
}

Implementacja klasy reprezentującej połączenie:

package palio.connectors;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import palio.PalioException;


public class SamplePooledConnection extends PooledConnection {
    
    private RandomAccessFile raf;
    
    SamplePooledConnection(PooledConnector connector) throws FileNotFoundException {
        super(connector);
        raf = new RandomAccessFile(((SamplePooledConnector) connector).getFilePath(), "r");
        super.init();
    }
    
    public RandomAccessFile getRaf() {
        return raf;
    }
    
    @Override
    protected void closeParaller() {
        Thread thread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                closeNormal();
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
    
    @Override
    protected void closeNormal() {
        try {
            raf.close();
        } catch (IOException ex) {
            // Do some exception handling
            ex.printStackTrace();
        }
    }
    
    @Override
    protected void setDedicated() throws PalioException {
        dedicated = true;
    }
    
    @Override
    protected void unsetDedicated() {
        dedicated = false;
    }
    
    @Override
    protected void resetExtend() {
        if (raf != null) {
            try {
                raf.seek(0);
            } catch (IOException ex) {
                // Do some exception handling
                ex.printStackTrace();
            }
        }
    }
    
}

Moduł udostępniający API konektora:

package palio.modules;

import java.util.Properties;

import palio.Instance;
import palio.ModuleManager;
import palio.PalioException;
import palio.connectors.SamplePooledConnector;
import palio.modules.core.Module;


public class SamplePooledConnectorModule extends Module {
    
    static {
        ModuleManager.registerModule("sample", SamplePooledConnectorModule.class, ModuleManager.MODULE_STANDARD);
    }
    
    public SamplePooledConnectorModule(Instance instance, Properties parameters) {
        super(instance, parameters);
    }
    
    @Override
    public String getVersion() {
        return "1.0.0";
    }
    
    public String nextLine(String connectorName) throws PalioException {
        return ((SamplePooledConnector) instance.getConnector(connectorName)).readLine();
    }
    
    public void seek(String connectorName, Long position) throws PalioException {
        ((SamplePooledConnector) instance.getConnector(connectorName)).seek(position);
    }
    
    public Long getFilePointer(String connectorName) throws PalioException {
        return ((SamplePooledConnector) instance.getConnector(connectorName)).getFilePointer();
    }
    
}

Przykład użycia modułu:

$sample.seek("sample", 120)
$sample.nextLine("sample")
$sample.getFilePointer("sample")

Konfiguracja i parametryzowanie konektorów

Jeżeli chcemy dodać własny konektor do instancji jPALIO należy dodać konfigurację tego konektora do konfiguracji instancji jPALIO. Konfiguracja konektora z przedstawionego przykładu moze wyglądać w sposób następujący:

<connector name="sample" class="SamplePooledConnector" url="file://tmp/lorem_ipsum.txt">
</connector>

Nazwa konektora jest kluczowym atrybutem w konfiguracji. Za pomocą tej nazwy dany konektor jest identyfikowany w ramach instancji jPALIO. W przypadku konektorów wbudowanych w jPALIO nie ma potrzeby podawania nazwy klasy implementującej dany konektor. Odpowiedni konektor jest pobierany na podstawie podanego adresu URL (atrybut url w konfiguracji konektora).

Jeżeli konektor korzysta z dodatkowych parametrów konfiguracyjnych, powyższy podstawowy wpis należy rozszerzyć do postaci następującej:

<connector name="sample" class="SamplePooledConnector" url="file://tmp/lorem_ipsum.txt">
    <param1>param1Value</param1>
    <param2>param2Value</param2>
    ...
</connector>

Dostęp do parametrów konfiguracyjnych realizowany jest poprzez pole properties w klasie bazowej.

Atrybuty wspierane definiowane dla każdego konektora (niekoniecznie wykorzystywane):

Parametry wspierane przez konektory z zarządzaniem pulą połączeń:

Przykładowa pełna konfiguracji konektora:

<connector name="palio" url="jdbc:oracle:thin:@pingwin.torn.com.pl:6372:ePALIO" refresh="0" traceRead="true" traceWrite="true" traceExecute="true">
      <user>jpalio</user>
      <password>torn</password>
      <maxConnections>0</maxConnections>
      <minConnections>0</minConnections>
      <maxInactivity>15</maxInactivity>
 <maxUseCount>100000</maxUseCount>
 <maxLifeTime>720</maxLifeTime>
</connector>