Weblogic Deployment Plan

Wczoraj miałem przyjemność przygotowywać procedurę instalacyjną kolejnej wersji dewelopowanej aplikacji Java EE na serwerze WebLogic. Niestety najnowsza odsłona nie pozostaje kompatybilna wstecz (zmiana WSDL) i powinna egzystować “obok” poprzedniej. Przypadłość ta uniemożliwia wykorzystanie mechanizmu wersjonowania aplikacji udostępnianego przez serwer firmy Oracle (parametr Weblogic-Application-Version pliku MANIFEST.MF, http://eelzinga.wordpress.com/2009/08/05/weblogic-side-by-side-deployment). Instalacja EAR’a pod inną nazwą owocuje stosem wyjątków, których przyczyny są dwojakie:

  • ten sam Context Root modułu WAR.
  • identyczne Endpoint’y WebService’ów.

Zabrałem się więc za czytanie o tzw. Deployment Plan (DP). Technologia bardzo ciekawa polegająca na automatycznej zmianie konfiguracyjnych plików XML aplikacji przed jej instalacją. Tutaj przychodzi mi na myśl odwieczny spór odnośnie stosowania adnotacji, czy opisywania np. EJB w pliku ejb-jar.xml. Sam deskryptor Plan.xml zawiera dwie główne sekcje. Druga (umieszczana w dalszej części pliku) odpowiada za mapowanie modułów, określenie URI danego pliku konfiguracyjnego, oraz definicję nazw zmiennych i ścieżek XPath do nich prowadzącym. Pierwsza sekcja przypisuje konkretnym zmiennym odpowiednie wartości. Istnieje możliwość posiadania różnych Deployment Plan’ów na poszczególne środowiska (dev, test, prod). Nad samą technologią nie będę się dalej rozwodził. Podam jedynie kilka użytecznych linków, gdyż treść ich uważam za wystarczającą do “szybkiego startu”:

Przykładowy fragment pliku Plan.xml:

...
<variable-definition>
  <variable>
    <name>web-app-context-root</name>
    <value>newRoot</value>
  </variable>
</variable-definition>
...
<module-override>
  <module-name>MyWebApp.war</module-name>
  <module-type>war</module-type>
  <module-descriptor external="false">
    <root-element>weblogic-web-app</root-element>
    <uri>WEB-INF/weblogic.xml</uri>
    <variable-assignment>
      <name>web-app-context-root</name>
      <xpath>/weblogic-web-app/context-root</xpath>
      <operation>replace</operation>
    </variable-assignment>
  </module-descriptor>
</module-override>
...

Task Ant’owy wldeploy posiada parametr pozwalający wyspecyfikować plan deployment’u. Niestety z konsoli web’owej nie udało mi się tego dokonać bezpośrednio podczas instalacji. Objaśnienie znajdujące się przy polu “Upload a deployment plan (this step is optional)” jest dla mnie niezrozumiałe. Czy wie ktoś jak stworzyć archiwum DP? W pomocy serwera WebLogic znalazłem instrukcję jak zastosować DP do świeżo wgranej aplikacji z poziomu konsoli administracyjnej. Należy bowiem przejść do sekcji “Deployments”, wybrać interesującą nas aplikację poprzez zaznaczenie odpowiedniego checkbox’a, a następnie wykonać komendę “update”. Na nowym ekranie podajemy ścieżkę do Deployment Plan’u i “Finish” – parametry zostały zaktualizowane.

Poniżej zamieszczam skrypt powłoki pozwalający na szybki deploy:

set BEA_HOME=E:\Programy\Oracle\Middleware
call %BEA_HOME%\wlserver_10.3\server\bin\setWLSEnv.cmd
set PATH=%BEA_HOME%\jdk160_14_R27.6.5-32\bin
java weblogic.Deployer -adminurl http://localhost:7101 -user weblogic -password weblogic1 -deploy -name SwdStarterService -source SwdStarterService.ear -targets DefaultServer -stage -plan Plan.xml

Konkluzja moich bojów z dania 14.02.2011:

  • WebLogic nie obsługuje zmiany parametru context-root pliku META-INF/application.xml (EAR). Parametr ten należy przenieść do deskryptora weblogic.xml (WAR), gdzie możliwa jest tego modyfikacja za pomocą DP.
  • Powtórny deployment aplikacji o tej samej nazwie z poziomu konsoli administracyjnej WLS przebiega bezproblemowo – system sam prosi o podanie nowej, unikalnej nazwy.
  • Przy wyspecyfikowaniu parametru wsdlLocation adnotacji @WebService zmienna contextpath Endpoint’u Web Service (http://host:port/contextpath/serviceName) wyliczana jest na podstawie atrybutu location tag’a soap:address. Przy deploy’u drugiej, niemal identycznej aplikacji na jednym serwerze aplikacyjnym WLS, należy zmodyfikować automatycznie każdy z WSDL’i (oczywiście za pomocą DP).

Miłe wspomnienie z dnia 14.02.2011 – kłódka na moście Świętokrzyskim :).

Kłódka most Świętokrzyski 14.02.2011

Advertisements

1000 odwiedzin

17 stycznia 2011 – na panelu administracyjnym WordPress liczba odwiedzin przekroczyła tysiąc. Nie wliczam tutaj własnych wizyt na blogu. Pierwszy wpis ukazał się 21 maja 2010, kiedy to zaczynałem przygotowania do egzaminu SCBCD. Niewątpliwie największą popularnością cieszy się post pod tytułem “Budowanie aplikacji Java EE wykorzystując Apache Maven”. Dziękuje wszystkim i mam nadzieje, że moje tutoriale i miniartykuły okazały się Wam pomocne.

IBM DB2 9.7 Academic Workshop, Test IBM 000-302

W dniach 11-12 grudnia 2010 miałem przyjemność uczestniczyć w warsztatach IBM DB2 9.7 Academic Workshop. Uprzednio nie korzystałem w ogóle ze wspomnianego produktu firmy IBM. Organizatorzy szkolenia – Wydział Elektroniki i Technik Informacyjnych Politechniki Warszawskiej – nalegali na wcześniejsze zapoznanie się z podstawami DB2. Zabrałem się więc za przeglądania Google’a i natrafiłem na bardzo ciekawe wideo-prezentacje (http://www.ibm.com/developerworks/wikis/display/DB2/DB2+on+Campus+videos). Stworzyłem nową maszynę wirtualną Ubuntu i rozpocząłem instalacje bazy danych. Całość poszła bezproblemowo. Prezentacje, do których link podałem powyżej, przesłuchałem jednokrotnie i pewny swojej podstawowej wiedzy udałem się do gmachu WEiTI.

Pierwszy dzień szkolenia podsumować mogę w dwóch słowach jako reklama firmy IBM i ciągłe porównywanie DB2 do produktu Oracle’a. Zestawienie to było bynajmniej jednostronne. Prowadzący nie podał żadnych niedociągnięć i wad DB2. Analizując obecnie zaistniałą sytuację dochodzę do wniosku, że właśnie takiej postawy można się było spodziewać. Wracając jednak do informacji technicznych – okazały się one zupełnie podstawowe, na tyle, że ktoś nie uczęszczający wcześniej na żaden przedmiot bazodanowy miał szanse wszystko dokładnie zrozumieć. Nie uznaję tego za wadę, gdyż był to warsztat akademicki. Skąd organizatorzy mogli wiedzieć, że na auli znajduje się pracownik Oracle’a :P? Pierwszy dzień dobiegł końca. Z uczelni wyszedłem bogatszy o informacje na temat wszelakich produktów rodem z IBM, podręcznik DB2 9.7 Academic Workshop oraz płytkę z przygotowaną do ćwiczeń maszyną wirtualną. Tego samego wieczoru, przeglądając slajdy z podręcznika, zainstalowałem na swojej Ubuntu VM oprogramowanie Data Studio Administration Console. Jest to ciekawe narzędzie, które porównać można do Enterprise Manager’a. Niestety posiada ono jedynie funkcje monitorujące stan bazy danych (np. obciążenie, informacje o alertach). Oracle EM umożliwia ponadto zarządzanie całym DBMS.

Drugi, ostatni dzień wykładów zawierał znacznie więcej informacji technicznych. Pośpiech zmusił pewnie prowadzących do ograniczenia się do czytania slajdów :(. Całość okazała się jednak dobrym podsumowaniem i usystematyzowaniem mojej wiedzy nabytej głównie z wideo-prezentacji (tak gruntowne przygotowanie się było błędem). Forma nauki przez żywy kontakt z wykładowcą wydaje mi się znacznie skuteczniejsza od samodzielnego studiowania książek. Bardzo podobał mi się sposób prowadzenia zajęć przez Pana Marcina Marczewskiego, który sporo dyskutował ze słuchaczami, podawał informacje nie zawarte na slajdach oraz “z życia wzięte”.

O warsztacie (ćwiczeniach w siedzibie IBM) nie ma co mówić. Udostępnione zostały te same maszyny wirtualne, co rozdane podczas pierwszego dnia wykładu. Następnie każdy we własnym zakresie przeprowadzał ćwiczenia dokładnie opisane w podręczniku. Jedyny plus jaki dostrzegłem to brak Internetu, co mobilizowało do nauki :).

Sam egzamin 000-302, którego konspekt znaleźć można pod adresem: http://www-03.ibm.com/certify/tests/obj000-302.shtml, okazał się rzeczywiście bardzo podstawowy. To znaczy, zawierał on sporo pytań ogólnych dotyczących baz danych, modelu relacyjnego i prostych kwerend SQL. Kilka pytań posiadało niejednoznaczną formę, przez co musiałem zastanawiać się “co autor miał na myśli”, lub która odpowiedź jest bardziej poprawna. Na pewno podczas egzaminu wymagana jest znajomość SQL, podstaw systemów RDBMS, pureXML. Nie należy jednak wkuwać na pamięć instrukcji do backup’u bazy danych, czy też struktur słownika (nawyk pewnie z OCE i OCA). Popełniłem ten błąd, przez co zmarnowałem wczorajszy wieczór, a i tak za tydzień nie będę nic z tego pamiętał. Żałuję, że nie przyszło mi zdawać egzaminu 000-730, do którego chyba jestem nieźle przygotowany. Organizatorzy nie umożliwili wyboru, mimo (ponoć) identycznej ceny obydwu testów – 200 USD. Egzamin 000-302 nie został jak dotąd uwzględniony w profesjonalnej ścieżce certyfikacyjnej DB2, a porównując go do OCE był niewątpliwi “akademicki”.

Przydatne materiały dodatkowe:

Może niewłaściwie zredagowałem niniejszego posta, gdyż na koniec chciałem bardzo podziękować działaczom firmy IBM oraz wydziału WEiTI za organizację owego warsztatu. Muszę przyznać, że sporo się nauczyłem i DB2 nie jest już dla mnie zagadką. Szczególne uznanie należy się korporacji IBM, która chyba jako jedyna “wyciąga rękę” do studentów. Nie słyszałem o podobnych event’ach organizowanych przez Oracle czy Microsoft.

Na koniec screen z moim wynikiem z egzaminu. Chyba najsłabszy jak dotąd rezultat (85%), ale przyznać muszę, iż najkrócej się też przygotowywałem.

Lukasz Antoniak 000-302 85%

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.