Zabawa w administratora

Wczoraj miałem okazję zabawić się w młodego administratora Windows. Niestety moja wiedza na temat systemu firmy Microsoft, jego GUI oraz poleceń konsoli jest na tyle ograniczona, że postanowiłem odwołać się do wujka Google. Odnalazłem projekt UnxUtils (http://sourceforge.net/projects/unxutils), który udostępnia implementację Windows’ową szeregu popularnych narzędzi Linux’owych takich jak grep, ls, tail (znaleźć je można w podkatalogu usr/local/wbin). Programiki te bardzo uprzyjemniają proste czynności administracyjne. Polecam dodanie ich na ścieżkę $PATH. Przez projekt UnxUtils w dalszym ciągu nie wiem jak w Windows sprawdzić uprawnienia i właściciela danego katalogu :).

Mój backup UnxUtils (na wypadek padu SourceForge) znaleźć można tutaj.

Post odnośnie zabijania zdalnych, pozostawionych sesji Remote Desktop: http://kodethoughts.blogspot.com/2006/12/kill-remote-user-session-remotely.html.

Przesyłanie plików za pomocą Web Service

Zleco no mi ostatnio wykonanie Web Service’u, którego zadanie polegało na przesyłaniu niewielkich plików binarnych. Początkowo (w pośpiechu) payload wiadomości transmitowałem jako tablicę bajtów w klasie POJO reprezentującej obiekt odpowiedzi. Niestety, podczas testów przeprowadzanych na serwerze aplikacyjnym WLS, owy mechanizm doprowadził do całkowitego padu serwera podczas przetwarzania pliku o wielkości około 10 MB. 10 MB to dziesięć razy więcej niż maksymalna rzeczywista przesyłana wielkość paczki. Szybki wgląd w szkolenie odnośnie BEA Web Services zaowocował niemal natychmiastową odpowiedzią dotyczącą sposobu przesyłania plików w technologi Web Service. Wszelkie obiekty pokaźnych rozmiarów należy bowiem stream’ować jako załączniki komunikatu SOAP.

Implementacja

Podczas implementacji WS skorzystałem z adnotacji @HandlerChain wskazującej na deskryptor zawierający definicję łańcucha wywołań kolejnych “interceptorów” metody serwisowej (patrz poniższe listingi). Plik ten znajdować się powinien w tym samym katalogu co skompilowane klasy serwisu. Każde ogniwo łańcucha wywołań spowodować może zaprzestanie dalszego przetwarzania wiadomości. Dzięki temu adnotację @HandlerChain stosuje się często podczas realizacji niestandardowego procesu autentykacji, autoryzacji, czy też pomiaru wydajności. Zawartość pliku definiującego handler chain przedstawia następujący listing:

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
  <handler-chain name="SampleAttachmentHandler">
    <handler>
      <handler-class>edu.lantoniak.service.handler.AttachmentHandler</handler-class>
    </handler>
  </handler-chain>
</handler-chains>

Kod handler’a dodającego załącznik gdy ustawiono określoną zmienną kontekstową:

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.File;
import java.util.Collections;
import java.util.Set;

public class AttachmentHandler implements SOAPHandler<SOAPMessageContext> {
   private static final Logger log = Logger.getLogger(AttachmentHandler.class);

   public Set<QName> getHeaders() {
      return Collections.emptySet();
   }

   public boolean handleMessage(SOAPMessageContext context) {
      if (shouldAddAttachment(context)) {
         SOAPMessage soapMessage = context.getMessage();
         String path = (String) context.get(SampleServiceImpl.MY_KEY);
         FileDataSource fileDataSource = new FileDataSource(path);
         DataHandler dataHandler = new DataHandler(fileDataSource);
         AttachmentPart attachmentPart = soapMessage.createAttachmentPart(dataHandler);
         attachmentPart.setContentId(fileDataSource.getFile().getName());
         soapMessage.addAttachmentPart(attachmentPart);

         context.put(SampleServiceImpl.MY_KEY, null); // Cleanup
      }
      return true;
   }

   public boolean handleFault(SOAPMessageContext context) {
      return true;
   }

   public void close(MessageContext context) {
   }

   /*
    * Add attachment only if SampleServiceImpl.MY_KEY is set.
    */
   private boolean shouldAddAttachment(MessageContext context) {
      String patchKey = (String) context.get(SampleServiceImpl.MY_KEY);
      return patchKey != null;
   }
}

W przypadku zwrócenie false przez metodę handleMessage, któregoś z ogniw łańcucha, dalsze przetwarzanie pozostaje przerwane (czytaj dokumentację JAX-WS). Sama klasa serwisu powinna wyglądać następująco:

@Stateless
@WebService(serviceName="SampleService",
            targetNamespace="http://edu.lantoniak",
            wsdlLocation = "META-INF/wsdl/SampleService.wsdl",
            portName="SampleServicePort",
            endpointInterface="edu.lantoniak.service.SampleService")
@HandlerChain(file="handler-chain.xml")
public class SampleServiceImpl implements SampleService {
   public static final String MY_KEY = "edu.lantoniak.service.MY_KEY";

   @Resource
   WebServiceContext webServiceContext;

   @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
   public GetFileRequestElement getSampleFile(GetFileResponseElement request) {
      ...
      webServiceContext.getMessageContext().put(MY_KEY, request.getAbsoluteFilePath());
      ...
      return response;
   }
}

Zwracam uwagę na współdzielenie kontekstu wiadomości MessageContext. Plik reprezentowany przez ścieżkę request.getAbsoluteFilePath() dodawany jest jako zacznik podczas generacji komunikatu odpowiedzi SOAP (po ukończeniu przetwarzania instrukcji zawartych w metodzie getSampleFile). Handler wykonuje się zawsze przed i po wywołaniu docelowej procedury (patrz poniższy diagram, źródło: http://www.javaworld.com/javaworld/jw-02-2007/jw-02-handler.html). Etap przetwarzania komunikatu poznać można za pomocą np. atrybutu MessageContext.MESSAGE_OUTBOUND_PROPERTY znajdującego się w obiekcie typu MessageContext.

Źródło: http://www.javaworld.com/javaworld/jw-02-2007/jw-02-handler.html

Piszę tutaj zapewne o rzeczach dla większości znanych. Teraz przejdę jednak do sedna. W adnotacji @WebService wskazałem na własnoręcznie stworzony plik WSDL. Według specyfikacji Attachments Profile Version 1.0 (http://www.ws-i.org/profiles/attachmentsprofile-1.0.html) chęć przesyłania załączników powinna zostać specjalnie oznaczona we wspomnianym deskryptorze. Relewantne fragmenty WSDL zamieszczam poniżej:

<xs:element name="AttachmentElement" type="xs:base64Binary"/>

<wsdl:message name="GetFileRequestMessage">
  <wsdl:part name="request" element="tns:GetFileRequestElement"/>
</wsdl:message>
<wsdl:message name="GetFileResponseMessage">
  <wsdl:part name="response" element="tns:GetFileResponseElement"/>
  <wsdl:part name="fileAttachment" element="tns:AttachmentElement"/>
</wsdl:message>

<wsdl:portType name="SampleServicePort">
  <wsdl:operation name="getSampleFile">
    <wsdl:input message="tns:GetFileRequestMessage"/>
    <wsdl:output message="tns:GetFileResponseMessage"/>
  </wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SampleServiceBinding" type="tns:SampleServicePort">
  <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
  <wsdl:operation name="getSampleFile">
    <soap:operation soapAction=""/>
    <wsdl:input>
      <soap:body use="literal"/>
    </wsdl:input>
    <wsdl:output>
      <mime:multipartRelated>
        <mime:part>
          <soap:body parts="response" use="literal"/>
        </mime:part>
        <mime:part>
          <mime:content part="fileAttachment" type="application/octet-stream"/>
        </mime:part>
      </mime:multipartRelated>
    </wsdl:output>
  </wsdl:operation>
</wsdl:binding>

Oczywiście w przypadku pominięcia wstawek określających załączniki (linie 8 i 26 – 33), pliki są w dalszym ciągu wysyłane oraz prawidłowo odbierane przez klienta! Specyfikacja jest jednak specyfikacją i nie powinna być lekceważona. Alternatywną metodę wysyłania i odbierania plików przez Web Service (bez użycia @HandlerChain) opisał Geemon Kadambalil: http://geemonkb.blogspot.com/2009/01/jax-wx-streaming-soap-attachments.html. Spoglądając z obecnej perspektywy na efekt mojej pracy, dochodzę do wnioski, iż rozwiązanie Geemon’a wydaje się bardziej eleganckie i w przyszłości będę je stosował. Własnoręczne pisanie WSDL spowodowane było koniecznością trzymania się wewnętrznych standardów nazewniczych. Może któryś z czytelników zna adnotację JAXB pozwalającą na zmianę atrybutu name tag’a wsdl:binding? Co prawda po zapoznaniu się z dokumentem JSR224 v2.2 mrel3, wątpie w istnienie owej funkcjonalności: The value of the name attribute of the wsdl:binding is not significant, by convention it contains the qualified name of the corresponding wsdl:portType suffixed with “Binding”.

Przydatne linki:

SpringSource University – Free Online Training

Logo Spring FrameworkPodczas kolejnego wieczoru spędzanego nad edycją pracy magisterskiej wpadłem na ciekawą inicjatywę społeczności SpringSource. Na stronie SpringSource University opublikowano trzy, godzinne wykłady odnośnie Groovy, OSGi oraz AOP. Wszystkie bezpładnie dostępne do ściągnięcia pod adresem: http://www.springsource.com/training/freeonline. No cóż – kolejna pozycja wędruje na coraz dłuższą listę TODO. Życzę wszystkim miłego senasu :P.

Zależności między obiektami bazodanowymi w Oracle

Na jakiś czas skończyłem zabawę z JBoss, Maven oraz EJB. Dla odmiany zagłębie się na chwile w otchłań Oracle’owej bazy danych. Przygoda ta będzie wiązała się z wyszukiwaniem wszystkich komponentów wykorzystujących dany obiekt bazodanowy. Oracle w swojej uprzejmości dostarcza widok DEPTREE oraz bardziej przyjazny dla oczu IDEPTREE. Perspektywy te mają za zadanie wskazać wszystkie miejsca wykorzystania danej funkcji, procedury, pakietu, widoku oraz tabeli (mam nadzieję, że wymieniłem wszystkie). W celu uzupełnienia danych prezentowanych przez wcześniej wspomniane perspektywy, należy wykonać jako SYS skrypt $ORACLE_HOME/RDBMS/admin/utldtree.sql, a następnie wygenerować informację o interesującym Nas obiekcie:

EXECUTE deptree_fill('TABLE','HR','EMPLOYEES');

Operacja to spowoduje uzupełnienie widoków IDEPTREE oraz DEPTREE odpowiednimi danymi.

SELECT * FROM ideptree;

Wszystko wygląda pięknie. No może poza brakiem takich informacji jak np. numery linii, w których znajduje się dane odwołanie. Bez informacji owej można sobie jednak poradzić. Problem powstaje gdy grupujemy funkcje oraz procedury w pakiety i mamy na celu wyszukanie wywołań konkretnej z nich. DEPTREE obsługuje jedynie całe pakiety, ewentualnie uwzględniając podział na specyfikację oraz ciało.

Trochę dłubaniny i napisałem prosty skrypt umożliwiający wyszukanie wszystkich wystąpień danego fragmentu kodu. Przedstawia się on następująco:

CREATE OR REPLACE TYPE usage_location IS OBJECT (
   object_type VARCHAR2(50),
   object_name VARCHAR2(50),
   line_number NUMBER,
   CONSTRUCTOR FUNCTION usage_location(object_type VARCHAR2
                                     , object_name VARCHAR2
                                     , line_number NUMBER) RETURN SELF AS RESULT
   );

CREATE OR REPLACE TYPE BODY usage_location AS
   CONSTRUCTOR FUNCTION usage_location(object_type VARCHAR2
                                     , object_name VARCHAR2
                                     , line_number NUMBER) RETURN SELF AS RESULT
   AS
   BEGIN
      SELF.object_type := object_type;
      SELF.object_name := object_name;
      SELF.line_number := line_number;
      RETURN;
   END;
END;

CREATE OR REPLACE TYPE usage_location_list
IS TABLE OF usage_location;

CREATE OR REPLACE FUNCTION find_usage(p_text IN VARCHAR2
                                    , p_owner VARCHAR2 := user) RETURN usage_location_list PIPELINED
IS
   c_view_type_name CONSTANT VARCHAR2(10) := 'VIEW';

   v_view_source CLOB;
   v_location usage_location;
BEGIN
   -- Checking views
   FOR r IN (SELECT * FROM all_views WHERE owner = p_owner)
   LOOP
      SELECT dbms_metadata.get_ddl(c_view_type_name, r.view_name)
        INTO v_view_source
        FROM dual;
      IF (dbms_lob.instr(LOWER(v_view_source), LOWER(p_text)) != 0) THEN
         v_location := NEW usage_location(c_view_type_name, r.view_name, NULL);
         PIPE ROW ( v_location );
      END IF;
   END LOOP;

   -- Checking packages & triggers
   FOR r IN ( SELECT *
                FROM all_source
               WHERE owner = p_owner
                 AND LOWER(text) like LOWER('%' || p_text || '%') )
   LOOP
      v_location := NEW usage_location(r.type, r.name, r.line);
      PIPE ROW ( v_location );
   END LOOP;
END;

SELECT *
  FROM TABLE ( find_usage('employees') );

Na początku tworzymy typ obiektowy usage_location z jednym konstruktorem. Typ ten określa wszystkie właściwości pojedynczego wystąpienia danego obiektu, jakie będziemy prezentować: rodzaj komponentu, jego nazwę oraz numer linii. Kolejny krok polega na zdefiniowaniu kolekcji wspomnianego typu, którą zwracać będzie docelowa funkcja pipeline’owa find_usage. Polecam zapoznanie się z jej ciałem. Na końcu skryptu zaprezentowano przykładowe wywołanie. W celu odszukania konkretnej procedury umieszczonej w pakieci napisalibyśmy coś w stylu: SELECT * FROM TABLE ( find_usage(‘mypackage.myprocedure’) );. Kod PL/SQL posłuży zapewne czytelnikom w celach edukacyjnych.

Errata: Biblioteki zewnętrzne, EAR i Maven

Pierwsza errata na moim blogu :). Umiem przyznać się do błędu, lecz miejmy nadzieję, że to jedna z ostatnich tego typu sytuacji. Rzecz dotyczy dodawania bibliotek zewnętrznych do pakietu EAR zbudowanego przy użyciu Maven. Poniżej zamieszczam najbardziej elegancką według mnie konfigurację maven-ear-plugin:

<plugin>
  <artifactId>maven-ear-plugin</artifactId>
  <version>2.4.2</version>
  <configuration>
    <jboss>
      <version>5</version>
    </jboss>
    <defaultLibBundleDir>lib</defaultLibBundleDir>
    <modules>
      <ejbModule>
        <groupId>javaee-app.controller</groupId>
        <artifactId>controller</artifactId>
      </ejbModule>
      <webModule>
        <groupId>javaee-app.view</groupId>
        <artifactId>view</artifactId>
        <contextRoot>view</contextRoot>
      </webModule>
      <jarModule>
        <groupId>javaee-app.model</groupId>
        <artifactId>model</artifactId>
        <includeInApplicationXml>true</includeInApplicationXml>
        <bundleDir>/</bundleDir>
      </jarModule>
      <jarModule>
        <groupId>javaee-app.utils</groupId>
        <artifactId>utils</artifactId>
        <includeInApplicationXml>true</includeInApplicationXml>
        <bundleDir>/</bundleDir>
      </jarModule>
      <jarModule>
        <groupId>org.springframework</groupId>
        <artifactId>spring</artifactId>
      </jarModule>
    </modules>
  </configuration>
</plugin>

Szczególną uwagę należy zwrócić na tag defaultLibBundleDir, dzięki któremu wszystkie biblioteki dodatkowe zostaną umieszczane domyślnie w katalogu lib (tutaj Spring Framework). Wszystkie moduły deploy’owane jako jarModule także znalazłyby się we wspomnianym wcześniej katalogu gdyby nie nadpisanie konfiguracji znacznikiem bundleDir. W tym wypadku należy jednak wymusić dodanie odpowiedniego wpisu do pliku application.xml przez ustawienie atrybutu includeInApplicationXml na true.

Dzięki przedstawionej wyżej konfiguracji dodatkowe biblioteki zostaną umieszczone w katalogu lib archiwum EAR oraz nie będą wymagały dodatkowego wpisu w głównym deskryptorze aplikacji.

Pragnę także oznajmić, że idąc z duchem czasu zamierzam przesiąść się ze starego, dobrego Subversion na Mercurial’a. Źródła projektu można pobrać z repozytorium: https://bitbucket.org/lukasz_antoniak/javaee-app.