Prezentacja: Implementing REST Web Application Architectures

17 września 2009 | 0 komentarze

Na InfoQ pojawiła się prezentacja by Arjen Poutsma zarejestrowana podczas springOne dotycząca obsługi REST w nowym Springu 3.0. Jeśli ominęło was kwietniowe spotkanie organizowane przez warszawski JUG i SpringSource warto poświęcić godzinę i obejrzeć materiał (video + slajdy). Tematem przewodnim jest wsparcie dla RESTful URLs w Spring MVC:
Link do prezentacji: http://www.infoq.com/presentations/rest-web-application-architectures

Kontrolka Drzewka w Tagu JSP

31 sierpnia 2009 | 0 komentarze

Począwszy od JSP 2.0 do dyspozycji programistów dostępne są tagi użytkownika. Tagi posiadają kilka nowych, bardzo ważnych funkcjonalności, których brakowało w starym JSP. Wśród nich są możliwość przekazania referencji obiektów jako atrybuty (nie przez kontekst) i wykonywanie przekazanych w atrybutach fragmentów (fragments).


Napisanie rozsądnej kontrolki drzewka w HTML'u w tagu JSP nie jest banalne ze względu na rekurencję - podstawowym założeniem jest brak ograniczenia liczby poziomów. Dla uproszczenia kodu nie będzie możliwości rozwijania i zwijania gałęzi. Po stronie przeglądarki wykorzystam elementy listy <ul> i <li>. Wnętrze elementu listy będzie przekazywane jako fragment, co umożliwi wykonanie dowolnego zdarzenia javascript lub podpięcie linka w zależności od sposobu wykorzystania drzewka. Model to zwykły POJO:

package pl.publicmethods.model;

public class Group {

private Set<Group> children;

public Set<Group> getChildren() {
return children;
}
public void setChildren(Set<Group> children) {
this.children = children;
}
}


Kod tagu /WEB-INF/tags/widgets/tree.tag:

<%@ tag display-name="" description="" import="pl.publicmethods.model.*"%>
<%@ attribute name="groups" required="true" type="java.util.Collection" %>
<%@ attribute name="level" required="false" type="java.lang.Integer" %>
<%@ attribute name="name" required="true" fragment="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="w" tagdir="/WEB-INF/tags/widgets"%>

<c:if test="${empty level}">
<c:set value="0" var="level" />
</c:if>

<c:if test="${level eq 0}">
<c:set value="class=\"tree\"" var="ulclass" />
</c:if>

<ul ${ulclass}>
<%
for(Object o : groups){
Group group = (Group)o;
request.setAttribute("group",group);
%>
<li>
<jsp:invoke fragment="name"/>
</li>
<w:tree groups="${group.children}" level="${level+1}" name="${name}" />
<%
}
%>
</ul>

Konkretny przypadek użycia na stronie JSP - atrybut name zawiera fragment który zostanie wykonany i wyświetlony dla każdego elementu w kolekcji ${groups}:

<w:tree groups="${groups}">
<jsp:attribute name="name">
<c:url value="/groupform.do?id=${group.id}" var="url" />
<a href="${url}">
<c:out value="${group.name}" />
</a>
</jsp:attribute>
</w:tree>

Czy można przekazać fragment JSP rekurencyjnie w postaci atrybutu? Okazuje się że można. Tag zawiera pętlę rekurencyjną w formie Scriptletu. Alternatywnie można ją napisać w JSTL, pamiętając o przekazaniu zmiennej group do zakresu request - musi być widoczna dla fragmentu.

Atrybut level to nic innego jak piętro drzewka - na zerowym element <ul> dostaje oddzielną klasę stylu. Łatwo można wprowadzić sprawdzanie zapętlonych powiązań - wystarczy wstawić warunek dla level < 1000.

I jeszcze style CSS:

ul.tree {
margin: 0;
padding: 0;
background-color: #f8f8f8;
border-top: 1px solid #888;
border-bottom: 1px solid #444;
}
ul.tree ul {
padding: 0;
margin: 0;
margin-left: 24px;
}
ul.tree li {
list-style-type: none;
border-bottom: 1px dotted #888;
padding: 4px;
}
ul.tree li:hover {
background-color: #f0f0f0;
}

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