The only valid measurement of code quality

12 lutego 2010 | 0 komentarze


http://www.osnews.com/story/19266/WTFs_m

Tag Library Documentation Generator + Maven

25 listopada 2009 | 0 komentarze

Kolejne narzędzie ułatwiające pracę, a właściwie dwa.

Tag Library Documentation Generator potrafi tworzyć javadoc'a bibliotek tagów JSP na podstawie opisów zawartych w plikach .tld i .tag. Obsługuje tagi JSP 1.2 do 2.1 i JSF.


Na dokładkę plugin do Mavena który generuje dokumentację taglibs automatycznie (korzysta z wymienionego wyżej narzędzia) i załącza do strony projektu - maven-taglib-plugin. Wystarczy wpisać do pom.xml:
<reporting>   <plugins>     <plugin>       <groupId>net.sourceforge.maven-taglib</groupId>       <artifactId>maven-taglib-plugin</artifactId>     </plugin>   </plugins> </reporting>
Plugin udostępnia cele (goals):
Uruchamianie powyższych bez prefiksu nazwy grupy i pełnej nazwy pluginu - dopisać do settings.xml:
<settings>   ...  <pluginGroups>   <pluginGroup>net.sourceforge.maven-taglib</pluginGroup>  </pluginGroups>   ... </settings>
Linki:

Cykliczne zależności komponentów w Spring'u

31 października 2009 | 1 komentarze

Cykliczna zależność to sytuacja w której komponent A jest zależny od komponentu B i jednocześnie B jest zależny od A. W przypadku kontenera Spring IoC wzajemna zależność jest wykrywana i zgłaszana w postaci wyjątku BeanCreationException podczas startu aplikacji. Aplikacja oczywiście nie wstaje.

Przykładowy konfig Spring'a z cykliczną zależnością (/WEB-INF/application-context.xml):

<bean id="userService" class="pl.codeservice.service.UserService" >
 <property name="studioService" ref="studioService" />
</bean>

<bean id="studioService" class="pl.codeservice.service.StudioService" >
 <property name="userService" ref="userService" />
</bean>

Zalecane jest pozbycie się cykliczności jako złej praktyki bo:
-zwiększa stopień skomplikowania konfiguracji (OOP principles: loose coupling)
-powoduje problemy architektoniczne: który komponent ma być zniszczony jako pierwszy, istnienie częsciowo zainicjowanych ziaren, itd.
-powiązane komponenty muszą być dystrybuowane w tym samym module/JARze
-trudniej testować
-itd.

O ile generalnie zgadzam sie z powyższym, bywają sytuacje awaryjne kiedy nie ma innego wyjścia. Nie ma czasu i pieniędzy na zmianę architektury a kopiowanie kodu nigdy nie jest dobrym pomysłem.

Rozwiązaniem jest implementacja interfejsu BeanPostProcessor przez jeden z komponentów i przechwycenie referencji do zależnego bean'a w metodzie postProcessAfterInitialization(). Trzeba pamiętać o zwróceniu w obu metodach instancji przekazanego w argumencie ziarna.


public class StudioService implements BeanPostProcessor {
 
  private UserService userService;
  //...
 
  public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
   if("userService".equals(name)){
    userService = (UserService)bean;
   }
   return bean;
  }
 
  public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
   return bean;
  }
}


Jeśli komponent będzie występował jako cross-reference więcej niż raz warto napisać, skonfigurować i używać w jego miejsce proxy - obejście zawsze będzie tylko w jednym miejscu.

Linki:
-Spring Dynamic Modules Reference Guide, 5.3.1. Listener and cyclic dependencies
-The Spring Framework - Reference Documentation, 3.7. Container extension points
-architecturerules.org

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

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.

Monitorowanie Tomcat'a i Lambda Probe

10 lipca 2009 | 0 komentarze

Dzisiaj swoją szanse dostała Lambda Probe (wcześniej Tomcat Probe) - aplikacja do administracji i monitorowania serwera Apache Tomcat. Instalacja w formie war'a, licencja GNU GPL, ładne GUI - to atuty znane przed instalacją ze strony produktu.

Najważniejsze ficzery

-działa na Tomcie od 5.0 wzwyż i na jBossie
-na dzień dobry wszystko to co daje Manager
-szczegółowe monitorowanie pamięci + wykresy
-monitorowanie obciążenia CPU
-szczegółowe monitorowanie wątków, możliwość zabicia wątku
-monitoriwanie ruchu i liczby zapytań w podziale na connectors
-przeglądanie plików JSP, re-kompilacja JSP
-lista i przeglądanie logów (Tomcat 6 => tylko LOG4J)
-lista DataSource, test połączenia, wykonanie zapytania SQL
-monitorowanie klastra + wykresy ruchu
-możliwość restartu JVM - wymagana istalacja Java Service Wrapper

Pełna lista możliwości tutaj.


Instalacja

Jest bardzo prosta. Z oficjalnej strony pobieram binarkę. Dla własnego komfortu publikuję wara w formie exploded. W środku JSP, Spring i SiteMesh - żadnych szaleństw i oto chodzi. Na JVM na której pracuje serwer konieczne jest włączenie JMX agent'a z dostępem lokalnym przez dodanie parametru:
-Dcom.sun.management.jmxremote
Po restarcie serwera aplikacja wstaje za pierwszym razem. Autentykacja przez kontener, wymagany user posiadający rolę "manager" - tak jak w Tomcat Manager (do wyboru są jeszcze role probeuser, poweruser, poweruserplus). Instalacja poszła gładko.

Pierwsze wrażenie

Bardzo ładny, czytelny, funkcjonalny interfejs. Od razu wiadomo gdzie czego szukać. Automatyczne odświeżanie wykresów i tabel w Ajax'ie (prototype + scriptaculous). Jest kilka przydatnych bajerów - każdy wykres jednym kliknięciem można powiększyć na pełny ekran. W porównaniu do Tomcat Manager'a to skok w inną epokę.


Drugie wrażenie

Nie wszystko działa idealnie. Irytuje wyskalowanie wykresów do maksymalnej aktualnie wyświetlanej wartości, zamiast do maksymalnej możliwej. Górną granicą Perm Gen powinien być MaxPermSize! Dobrze że w tabelce jest procentowy progres.

Brakuje powiadomień. Chciałbym dostać e-maila z informacją jeśli dzieje się coś niedobrego. Nie ma możliwości zachowania pełnych danych historycznych, nie mówiąc o wysłaniu ich w mailu. Domyślnie wykresy przedstawiają tylko ostatnie 2 godziny. Na szczęście można poprosić Spring'a o ustawienie innych niż domyślne triggerów Quartz (30 sekund) i zmienić długość przechowywanych serii (/WEB-INF/spring-stats.xml). Trzeba uważać żeby nie przesadzić - dane są serializowane przez XStream na bieżąco do pliku (/work/Catalina/localhost/probe/stats.xml) i przy większym rozmiarze serii może wystąpić problem wydajnościowy. Poniżej przykład zmiany długości strumienia do 720 - przy logowaniu co 30sek. daje to 6 godzin (spring-stats.xml).

<bean name="memoryStatsCollector"
class="org.jstripe.tomcat.probe.beans.stats.collectors.JvmMemoryStatsCollectorBean">
<property name="jvmMemoryInfoAccessor" ref="jvmMemoryInfoAccessor"/>
<property name="statsCollection" ref="statsCollection"/>
<property name="maxSeries" value="720"/>
</bean>


Martwi brak aktualizacji projektu przez ostatnie 3 lata - najnowsza wersja 1.7b (beta?) jest z listopada 2006. Pewnie dlatego że jest taka dobra i nie zawiera żadnych błędów. ;)

Lambda to bardzo dobre narzędzie. Praca z takim GUI to czysta przyjemność. Od dzisiaj pierwszym krokiem po instalacji Tomcata powinno być wyrzucenie Managera i instalacja w to miejsce Lambda Probe. ;)

Indeksowanie pól typu data/czas w Apache Lucene

8 lipca 2009 | 0 komentarze

Zadanie: wykonanie wyszukiwania w Lucene artykułów w określonym przedziale czasowym i wyświetlenie daty publikacji bez czytania z bazy danych. Indeksowanie pola typu data.

Ekipa Lucene zaleca zapisywanie daty jako zwykłe pole tekstowe po konwersji do typu String (klasa dedykowana do czasu DateField jest oznaczona jako przestarzała @Deprecated). Najbardziej wydajne jest indeksowanie minimalnej potrzebnej dokładności czasu - im wieksza precyzja, tym dłuższy String i większe obciążenie wydajnościowe podczas wyszukiwania. Data jest konwertowana do formatu który pozwala na sortowanie leksykalne, np. dla dokładności rzędu pojedynczego dnia: CCYYMMDD (20090503 = 3 maja 2009). Dodatkowym plusem takiego formatu ma być możliwość zapisania daty sprzed 1970 roku.

Do zamiany czasu na tekst służy klasa org.apache.lucene.document.DateTools której metody statyczne wykonają robotę w obie strony. Do wyboru są metody przyjmujące jako argument typ java.util.Date i long (milisekundy). Statyczna klasa zagnieżdżona DateTools.Resolution pozwala na określenie dokładności konwersji (YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND).

static String dateToString(Date date, DateTools.Resolution resolution)
static String timeToString(long time, DateTools.Resolution resolution)
static Date stringToDate(String dateString)
static long stringToTime(String dateString)


Przykład indeksowania daty z dokładnością do dnia:

Directory directory = getDirectory();
IndexWriter writer = getWriter(directory);
Document doc = new Document();
doc.add(new Field(”body”, text, Field.Store.YES, Field.Index.ANALYZED));
String dateStr = null;
if(content.getDate()!=null)
dateStr = DateTools.dateToString(content.getDate(), DateTools.Resolution.DAY);
doc.add(new Field(”date”, dateStr, Field.Store.YES, Field.Index.NOT_ANALYZED));



Przykład kodu wyszukującego po przedziale czasowym i odczytanie daty z indeksu:

Directory directory = getDirectory();
IndexSearcher searcher = new IndexSearcher(directory);
RangeQuery rquery = new RangeQuery(new Term("date", "20090401"), new Term("date", "20090601"), true);
TopDocs docs = searcher.search(rquery, 100);
ScoreDoc[] scoreDocs = docs.scoreDocs;
for(int d=0;d
ScoreDoc scoreDoc = scoreDocs[d];
Document doc = searcher.doc(scoreDoc.doc);

String body = doc.get(”body”);
String dateStr = doc.get(”date”);
Date date = null;
try {
date = DateTools.stringToDate(dateStr);
} catch (Exception e) { }
System.out.println(”body:”+body);
System.out.println(”data:”+date);
}
searcher.close();


JavaDoc zaleca użycie ConstantScoreRangeQuery w miejsce RangeQuery, bo ponoć jest szybsze i bezpieczniejsze (nie wyrzuca wyjątku BooleanQuery.TooManyClauses przy przektoczeniu 1024 wyników). W miejsce Query można rozważyć zastrosowanie filtra RangeFilter.


Linki:
http://wiki.apache.org/lucene-java/DateRangeQueries
niektóre wpisy w wiki Lucene są nieaktualne