- URI templates - wsparcie dla REFTful URLs typu /owners/{ownerId}/pets/{petId}
- Content negotiation - ContentNegotiatingViewResolver -> delegacja obsługi widoków do dedykowanego Resolver'a w zależności od rozszerzenia pliku w URI lub nagłówka Accept: HTML, ATOM, RSS, PDF, XML, etc.
- HTTP method conversion - wsparcie dla GET, PUT, POST, DELETE
- ETag - obsługa ETag headers czyli jak graniczyć ruch w sieci za pomocą jednego filtra
- RestTemplate - wsparcie wywołań usług RESTful po stronie klienta
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:
Jakub Woźniakowski
Etykiety:
Spring
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:
Kod tagu /WEB-INF/tags/widgets/tree.tag:
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}:
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:

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;
}
Jakub Woźniakowski
Etykiety:
JSP
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.
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. :)
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. :)
Jakub Woźniakowski
Etykiety:
iReport,
Jasper Reports
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
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.
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.
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. :-)

Linki:
The Java EE 5 Tutorial, JSP Declarations
JavaServer Pages (JSP) v2.0 Syntax Reference
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
Jakub Woźniakowski
Etykiety:
JSP
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"
Przykłady - kategorie
Znajdź kategorię "Dokumentacja Techniczna" bez względu na położenie w hierarchii
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:
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
Znajdź dokumenty utworzone przez "a.głowacki"
TYPE:"cm:content" AND PATH:"/app:company_home//*" AND @cm\:creator:a.głowackiDokumenty 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();Deskryptor (searchexample.get.desc.xml):
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;
}
<webscript>Szablon widoku (searchexample.get.text.ftl):
<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>
${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