Crosstab i Dataset użytkownika w Jasper Reports

23 sierpnia 2009 | 1 komentarze

Natrafiłem na banalny z pozoru problem w iReport: jak w prosty sposób poza głównym zapytaniem odczytać coś z DB i wyświetlić w raporcie, np. w sekcji nagłówka? Założenie: id rekordu jest stałe albo przekazywane jako parametr zewnętrzny.

Najłatwiej byłoby oczywiście dodać podzapytanie w głównym query, ale komplikowanie mocno rozbudowanego sql'a to zły pomysł - wydajnościowo i (nie)higienicznie. Raport podrzędny też odpada - armata na komara. Pozostaje użycie dedykowanego Dataset przez wstawienie tabelki krzyżowej.

Crosstabs to niewdzięczne struktury. Moj iReport (3.5.3) kompletnie sobie z nimi nie radzi - próby wstawienia tabeli krzyżowej z palety kończą się szybkim pożegnaniem z GUI NetBeans bez zapisania zmian i komunikatu o błędzie. Pozostaje edycja źródeł raportu w XML'u, ale po kolei.

Poniżej załączam przepis na wstawienie do dowolnej sekcji raportu pola odczytanego dedykowanym zapytaniem sql. Ważne jest takie zgrupowanie kolumn i wierszy w Crosstab, żeby została widoczna tylko jedna komórka sekcji Detail, bez nagłówków i sum. Oczywiście podany kod trzeba dopasowac do swoich potrzeb (zapytanie, wielkość elementów, itd).

Krok 1. Do raportu dodać nowy Dataset - "Add Dataset" z menu kontekstowego po kliknięciu na nazwę raportu. Dodać parametr. Typ parametru musi być zgodny z id rekordu i parametrem zewnętrznym raportu.


Krok 2. Wpisać sparametryzowane zapytanie do Dataset.


Krok 3. Wstawić Crosstab do raportu, np. do sekcji nagłówka (z palety). Jeśli iReport nie chce współpracować - przejść do następnego punktu (wkleić XML).


Krok 4. Wyciąć nagłówki i stopki kolumn i wierszy tabeli. W edytorze tabeli powinna zostać widoczna tylko jedna komórka sekcji Detail typu String (w sekcji Measures pole ma Value Expression=$F{number}). Jako bucket expression (wyrażenie grupujące) w Row Groups i Column Groups wpisać stały tekst, np. "a".


Jeśli przy edycji tabeli iReport odmawia współpracy, wkleić gotowy szablon kodu do żródła XML raportu.

<crosstab isRepeatColumnHeaders="false" isRepeatRowHeaders="false" columnBreakOffset="0">
    <reportElement x="60" y="14" width="200" height="14"/>
    <crosstabParameter name="orderId" class="java.lang.Long"/>
    <rowGroup name="row" width="0">
        <bucket>
            <bucketExpression class="java.lang.String"><![CDATA["a"]]></bucketExpression>
        </bucket>
        <crosstabRowHeader>
            <cellContents/>
        </crosstabRowHeader>
        <crosstabTotalRowHeader>
            <cellContents/>
        </crosstabTotalRowHeader>
    </rowGroup>
    <columnGroup name="col" height="0">
        <bucket>
            <bucketExpression class="java.lang.String"><![CDATA["a"]]></bucketExpression>
        </bucket>
        <crosstabColumnHeader>
            <cellContents/>
        </crosstabColumnHeader>
        <crosstabTotalColumnHeader>
            <cellContents/>
        </crosstabTotalColumnHeader>
    </columnGroup>
    <measure name="orderNumber" class="java.lang.Object">
        <measureExpression><![CDATA[$F{number}]]></measureExpression>
    </measure>
    <crosstabCell width="237" height="25">
        <cellContents>
            <textField>
                <reportElement style="Crosstab Data Text" x="0" y="0" width="237" height="14"/>
                <textElement textAlignment="Left">
                    <font isBold="true"/>
                </textElement>
                <textFieldExpression class="java.lang.String"><![CDATA[""+$V{orderNumber}]]></textFieldExpression>
            </textField>
        </cellContents>
    </crosstabCell>
</crosstab>

Krok 5. Przypisać nazwę Dataset do tabeli. Zrobić mapowanie wartości parametru raportu na Dataset.


Krok 6. Jeśli nie istnieje, wstawić pole tekstowe do sekcji Detail tabeli i przypisać zmienną typu String (Text Field Expression=$V{orderNumber}).



I to byłoby na tyle. Wszelkie uwagi są mile widziane. :)

Bloki deklaracyjne w JSP

21 sierpnia 2009 | 0 komentarze

Bloki deklaracyjne nie należą do często używanych elementów JSP. Wynika to po pierwsze z wąskiego zakresu zastosowania, po drugie ze znanych zaleceń i tendencji używania tagów/EL a unikania Scriptlets (czytelny kod, łatwe utrzymanie, mniej błędów, itd.). Mimo tego, deweloper JSP powinien mieć przynajmniej świadomość istnienia tego mechanizmu. ;-)

Kod JSP każdej strony/tagu użytkownika jest w pierwszej fazie tłumaczony na kod źródłowy (servlet) a w następnej kompilowany do bytecode. Deklaracje pozwalają na tworzenie metod i pól będących członkami klasy wynikowej strony JSP (class members). W fazie tłumaczenia treść tagów JSP i Scriptlets zostaną wrzucone do jednej metody. Ale nie deklaracje.

Podstawowym ograniczeniem bloków deklaracyjnych jest dostępność pól i metod tylko w Scriptlets - nie są widoczne w Expression Language. EL operuje przecież na elementach zawartych w kontekście.

Przykład deklaracji JSP - tabliczka mnożenia w tabeli HTML. Mnożenie wykonywane w metodzie multiply(), zdefiniowanej w bloku deklaracyjnym. Metodę można wywoływać wielokrotnie z dowolnego miejsca na stronie.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<body>
  <h1>Test</h1>
  <%!
    int multiply(int a, int b){
    return a*b;
  }
  %>
  <p>
    <table>
    <% for(int x=1; x<=10; x++){%>
    <tr>
      <% for(int y=1; y<=10; y++){%>
        <td><%= multiply(x, y) %></td>
      <% } %>
    </tr>
    <% } %>
    </table>
  </p>
</body>

W taki sam sposób można deklarować pola. Trzeba tylko pamiętać, że przechowywanie w ten sposób stanu w JSP nie jest bezpieczne - dostęp do pól ma wiele wątków jednocześnie i w zasadzie nie ma gwarancji operowania na konkretnej instancji klasy. Z kolei użycie słowa kluczowego synchronized na pewno zabije wydajność.

Istnieją dwie metody specjalne, uwzględnione w cyklu życia strony JSP: jspInit wywoływana podczas inicjalizacji, jspDestroy przy niszczeniu servlet'a. Deklaracje oferują możliwość przeciążenia obu metod.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%! public void jspInit() { System.out.println("* witaj!"); } %>
<%! public void jspDestroy() { System.out.println("* bye bye!"); } %>

W blokach deklaracyjnych działają słowa kluczowe i kwalifikatory dostępu, pola i metody mogą być oznaczone jako static, final, synchronized. Deklaracje pozwalają na tworzenie bloków inicjacyjnych, w tym statycznych. Więcej - choć specyfikacja o tym milczy, dopuszczone jest deklarowanie klas zagnieżdżonych, np.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<body>
  <h1>Test 2</h1>
  <%!
  public static class InnerClass {
    public static void testIt(String str){
      System.out.println("testIt:"+str);
    }
  }
  %>
  <p>
  <%
    InnerClass.testIt("ok!");
  %>
  </p>
</body>

Na koniec ciekawostka, a raczej wpis z działu Tips & Tricks. ;-) Na stronie JSP, mimo że Eclipse informuje o błędzie, można napisać działający konstruktor. :-)


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%! public myjsp_jsp() { System.out.println("* Dzień Dobry!"); } %>

Linki:
The Java EE 5 Tutorial, JSP Declarations
JavaServer Pages (JSP) v2.0 Syntax Reference

Wyszukiwanie węzłów w Alfresco, Część 2

25 lipca 2009 | 0 komentarze

Przykłady zapytań - wyszukiwanie dokumentów

Znajdź dokumenty utworzone przez "a.głowacki"
TYPE:"cm:content" AND PATH:"/app:company_home//*" AND @cm\:creator:a.głowacki
Dokumenty utworzone lub modyfikowane 15 lipca 2009
TYPE:"cm:content" AND PATH:"/app:company_home//*" AND (@cm\:created:2009-07-15* OR @cm\:modified:2009-07-15*)
Wyszukanie wszystkich wersji dokumentu "crystal.txt" (store workspace://version2Store)
TYPE:"cm:content" AND @cm\:name:crystal.txt

Przykłady - kategorie


Znajdź kategorię "Dokumentacja Techniczna" bez względu na położenie w hierarchii
@cm\:name:"Dokumentacja Techniczna" AND TYPE:"cm:category"
Ta sama kategoria ale po ścieżce (niebezpieczne - zmiana nazwy nie zmieni QName)
PATH:"/cm:generalclassifiable//cm:Dokumentacja_x0020_Techniczna"

Przykład WebScripts


Skrypty WebScripts to sposób na szybką i bezinwazyjną integrację z Alfresco. Plusy: bogate API, architektura REST, mnóstwo przykładów, transakcyjność. Minusy: mix JavaScript + FreeMarker = jak to debugować???

Skrypt znajduje w repozytorium dokument o podanej nazwie (audyt.doc) i przypisuje do niego konkretną kategorię (Raporty). Widok wyświetla renerencję do dokumentu. Url skryptu: http://localhost:8080/alfresco/service/searchexample

searchexample.get.js:
go();

function go() {

var fileName = "audyt.doc";
var categoryName = "Raporty";
var docNode = findDocument(fileName);
var categoryNode = findCategory(categoryName);

if (docNode == null)
return showError(400, "Document not found: "+fileName+".");

if (categoryNode == null)
return showError(400, "Category not found: "+categoryName+".");

if(!docNode.hasAspect("cm:generalclassifiable")) /* pozwol przypisac kategorie */
docNode.addAspect("cm:generalclassifiable");

if(docNode.properties["cm:categories"] == null) /* just in case */
docNode.properties["cm:categories"] = new Array();

docNode.properties["cm:categories"].push(categoryNode);
docNode.save();
model.resultset = docNode; /* kontekst widoku */
}

function findDocument(name){
var result = search.luceneSearch("TYPE:\"cm:content\" AND PATH:\"/app:company_home//*\" AND @cm\\:name:\""+name+"\"");
if(result.length>0)
return result[0];
return null;
}

function findCategory(name){
var result = search.luceneSearch("TYPE:\"cm:category\" AND @cm\\:name:\""+name+"\"");
if(result.length>0)
return result[0];
return null;
}

function showError(errorCode, msg){
status.redirect = true;
status.code = errorCode,
status.message = msg;
}
Deskryptor (searchexample.get.desc.xml):
<webscript>
<shortname>Wyszukiwanie Lucene</shortname>
<description>
Pulished on publicmethods.blogspot.com
</description>
<url>/searchexample</url>
<format default="text">argument</format>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>
Szablon widoku (searchexample.get.text.ftl):
${resultset.nodeRef.storeRef.protocol}/${resultset.nodeRef.storeRef.identifier}/${resultset.nodeRef.id}/${resultset.name?url}


Linki
http://lucene.apache.org/java/2_4_1/queryparsersyntax.html
http://wiki.alfresco.com/wiki/Search_Documentation
http://wiki.alfresco.com/wiki/Full_Text_Search_Query_Syntax
http://wiki.alfresco.com/wiki/Alfresco_Namespaces
http://wiki.alfresco.com/wiki/JavaScript_API

Wyszukiwanie węzłów w Alfresco, Część 1

20 lipca 2009 | 0 komentarze

Alfresco wspiera wyszukiwanie węzłów (nodes) przez zapytania XPath i Lucene. Implementacja pierwszego jest wymagana przez specyfikację JSR-170. Lucene daje za to większe możliwości rozbudowy języka zapytań i jest metodą rekomendowaną.

Query można uruchamiać wprost z GUI - z poziomu Node Browser (link w konsoli administracyjnej). Po uruchomieniu Browser prosi o wybranie workspace (workspace://SpacesStore).


Alfresco udostępnia API do wyszukiwania: SearchService (JAVA) i Search API (WebScripts).

Poniżej zamieszczam przegląd możliwości języka Lucene z przykładami. Wyniki zapytań mogą różnić się w zależności od wersji.

Wyszukiwanie po dowolnych polach: (@xx/:nazwa:)

Nazwy pól są w postaci QName (qualified name) - występują w formie pełnej (pełna nazwa namespace z protokołem) lub skróconej.
@cm\:userName:"admin"
@cm\:lastName:"w*"
Wyszukiwanie pełnotekstowe (TEXT)
TEXT:"izotopy węgla"
Wyszukiwanie po referencji (ID), referencji rodzica (PARENT , PRIMARYPARENT)

Referencja węzła (NodeRef) = protokół://workspace/UUID obiektu
ID:"workspace://SpacesStore/510e0049-7d3f-4f3e-823d-8314b749b4a9"
PARENT:"workspace://SpacesStore/984bffc5-ef6c-4e72-9fc6-7d353d7e5264"
Ścieżka XPath (PATH)

Ścieżka nie może zawierać znaków specjalnych - wymagane kodowanie ISO9075.
PATH:"/app:company_home"
PATH:"/app:company_home/app:guest_home"
PATH:"/app:company_home/app:user_homes/*"
PATH:"/app:company_home//*"
Według przypisanego aspektu (ASPECT, EXACTASPECT)
TYPE:"cm:content" AND ASPECT:"cm:versionable"
Typ węzła (TYPE)

Podstawowe typy węzłów: cm:content, cm:link, cm:category, cm:person, cm:folder.
TYPE:"cm:category"
Nazwa kwalifikowana (QNAME)

QName pod którym jest widoczny węzeł, bez namespace, kodowanie ISO9075.
QNAME:faktura_x0020_42812.ods
QNAME:f*
QNAME:*.ods
Stan pola (ISUNSET, ISNULL, ISNOTNULL)
ISNULL:"cm:title"
Operatory (+, -, AND, OR, NOT), wildcards (*, ?), grupowanie
TYPE:"cm:content" AND TEXT:"L?n*" - ASPECT:"cm:versionable"
(TYPE:"cm:category" AND TEXT:"k*") OR (TYPE:"cm:folder" AND QNAME:k*)
Zakresy wartości: zakmnięty [... TO ...], otwarty {... TO ...}
@cm\:lastName:[c TO e]
@cm\:lastName:{talar TO toczko}
Wyszukiwanie podobnych fraz - odległość Levenshtein'a

Parametr określa stopień podobieństwa w zakresie od 0 (małe) do 1 (duże) - domyślnie 0.5.
TEXT:alkan~
TEXT:hiduminium~0.3
Bliskość położenia frazy (proximity)

Parametr wskazuje maksymalną odległość słów od siebie.
TEXT:"endopeptydaza pepsyna"~7
Priorytet frazy (boost)

Liczba wskazuje ważność.
TEXT:"Notogea" OR TEXT:"Antarktis"^2

Więcej na stronach wiki.

JackRabbit code review '05

16 lipca 2009 | 0 komentarze

Przeglądając wiki Alfresco natrafiłem na archiwalny raport z audytu kodu Apache JackRabbit'a. Opis dotyczy wersji 0.16.2 z okolic 2005 roku - wtedy w fazie inkubacji. Panowie zastanawiają się nad użyciem implementacji Apache w architekturze powstającego ECM Alfresco. Poniżej ciekawsze fragmenty.

Andy (Hind ?):
  • No version control
  • Persistence is not synchronised and will have threading issues
  • Not sure if they have workspaces> (There is no system workspace and noy dynamic workspace. I think there is just one * workspace) - Could support this in subversion style
  • There can be multiple instantiations pointing at the same repository which will cause havoc.
  • Does authentication and authorization work – looks very simple …

David (Caruana ?):
In the bug tracking system, the following comment may back this up - "i must admit that i took no care about multihtreading until now. but of course i will do it now." - a comment from the guy who wrote the versioning piece.

When you look at the code, you get the Slide v1.0 feeling. There are going to be bugs due to the amount of in-memory data structures that need to be kept just right - in a multi-threaded context. It's not the sort of thing that's easily fixed - there will be a Slide 3.0 equivalent when a re-design of the architecture is considered.

Given all this caching, hierarchy related processing performance will still suffer. There's no indexing as far as I can see.

As Andy has pointed out, there are numerous holes and inconsistencies - I expect these will get fixed as time goes by.

What do we do with JackRabbit?

We could:
1. Use it as our basic Repository capability - build our stuff around it
2. Build our own Repository capability and use JackRabbit where possible to provide a JSR-170 facade
3. Not use it at all

Option 1 might be viable if it's a stepping stone to building a community quickly with the intention of replacing it.

But, my preference is option 2. We'd probably gut JackRabbit anyway (not very nice!!) to cater for native db implementation and subversion type capability and also to fix reliability issues. However, it would give a head-start to providing a facade to our own stuff e.g. Type Model, Query etc.


Pełna wersja raportu.