HTML Inhalte säubern
Ich musste heute eine Lösung finden, um sicherzustellen, dass Inhalte, die auf öffentlich zugänglichen Formularen erstellt werden und dann öffentlich sichtbar werden nicht irgendwelche bösen HTML Hacks enthalten können. Zunächst wollte ich die Sache 'händisch' mit ein paar Regexen lösen, aber dann dacht ich mir, dass das ineffizient wäre und habe ein bisschen recherchiert. Dabei sind mir zwei Tools in die Hände gefallen:
- htmlcleaner und
- ein alter Bekannter: NekoHtml
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.html.dom.HTMLDocumentImpl;
import org.apache.xerces.xni.parser.XMLDocumentFilter;
import org.cyberneko.html.filters.ElementRemover;
import org.cyberneko.html.parsers.DOMFragmentParser;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.html.HTMLDocument;
import org.xml.sax.InputSource;
org.apache.commons.lang.StringUtils
…
/**
* cleans up (user provided) string input and makes sure no dangerous markup is left.
*
* Allows b, p, br, i, ol, ul, li, a (with href) and img (with srw, width, height, title) tags
* All others are removed with their text content left intact
*
* script and iframe tags are removed entirely (including textual content
*
* @param input
* @return
*/
public static String cleanupHtmlFragment(String input) {
if(isBlank(input))
return "";
try {
// create element remover filter
ElementRemover remover = new ElementRemover();
// set which elements to accept
remover.acceptElement("b", null);
remover.acceptElement("p", null);
remover.acceptElement("br", null);
remover.acceptElement("i", null);
remover.acceptElement("ol", null);
remover.acceptElement("ul", null);
remover.acceptElement("li", null);
remover.acceptElement("a", new String[] { "href", "title" });
remover.acceptElement("img", new String[] { "src", "width", "height", "title" });
// completely remove script and iframe elements
remover.removeElement("iframe");
remover.removeElement("script");
// create writer filter
org.cyberneko.html.filters.Writer writer = new org.cyberneko.html.filters.Writer();
// setup filter chain
XMLDocumentFilter[] filters = { remover, writer, };
DOMFragmentParser parser = new DOMFragmentParser();
parser.setProperty("http://cyberneko.org/html/properties/filters",filters);
parser.setProperty("http://cyberneko.org/html/properties/default-encoding","UTF-8");
parser.setProperty("http://cyberneko.org/html/properties/names/elems","lower");
parser.setProperty("http://cyberneko.org/html/properties/doctype/pubid","-//W3C//DTD XHTML 1.0 Transitional//EN");
HTMLDocument document = new HTMLDocumentImpl();
DocumentFragment fragment = document.createDocumentFragment();
parser.parse(new InputSource(new StringReader(input)), fragment);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(fragment);
transformer.transform(source, result);
return result.getWriter().toString();
} catch (Exception e) {
log.warn("Couldn't parse string fragment with nekohtml", e);
return "";
}
}
Posted at 04:07PM Mai 28, 2009 by joerg in Allgemein | Kommentare[0]
Maven rocks
Ich gestehe: Ich bin ein grosser Maven Fan. Maven ist eines jener Tools, die mein (Entwickler-)Leben wirklich einfacher und besser gemacht haben. Ich habe mich am Anfang schwer getan mit der dynamischen Natur von Maven fertig zu werden (am Anfang ist es schwierig zu verstehen, warum es kein Äquivalent zu 'ant -projecthelp' geben kann) - aber mit der Zeit will man maven nicht mehr missen - zu gut ist das dependency management (vor allen im Zusammenspiel mit dem vorzüglichen Nexus Repository) und der Komfort vollautomatischer Releases mit dem release plugin. Letztens habe ich ein Kommandozeilen Tool gebaut und mich gewundert, wie ich alle Dependencies komfortabel gepackt bekomme- und siehe da - es gibt mehrere Möglichkeiten:
- Das Assembly Plugin generiert ein Über-Jar, welches alles Klassen aller Dependencies erhält und sich dann sehr simpel über java -jar starten lässt (wenn man die entsprechenden Mainfest Einträge vornimmt). Nachteil: sollte ich viele Dependencies haben gestaltet sich das Deployment beim Vorhandensein dünner Uplinks langwierig.
- Das Dependency Plugin kopiert mir alle Dependency Jars nach target/dependency. Diese kann ich dann bequem in ein lib Verzeichnis o.ä. kopieren
Posted at 03:58PM Mai 28, 2009 by joerg in Allgemein | Kommentare[0]
Google Streetview car
Gerade hat einen Google Streetview Kamera Auto vor meinem Bürofenster geparkt (Kennzeichen GG-HH 2257). Es fühlt sich immer noch irgendwie komisch an wenn die virtuelle Welt in die reale vorstösst.
Karte
Posted at 01:16PM Mai 25, 2009 by joerg in Allgemein | Kommentare[0]
Dissonanzen mit der Exekutive
Heute morgen, auf dem Weg in den Kindergarten, sind meine Tochter und ich von einem Polizisten zurechtgewiesen worden, der dafür extra seinen Wagen wendete und 200 Meter zurückfuhr, weil wir auf dem falschen Gehweg fuhren. Der Herr hielt mir vor, ich würde meine Kind zu gefährlichem Verkehrsverhalten erziehen. Nun ja, wir fahren immer so, weil wir so eine gefährliche Strassenquerung vermeiden können. Ich war ganz schön in Rage und habe dem Herrn Polizisten auch gesagt, das ich die Maßregelung völlig unangemessen finde und mir viele andere Dinge einfielen, um die er sich besser kümmern sollte. Allerdings ist mein Ärger schnell verflogen, da er einerseits sehr freundlich war und sich dann auch sehr nett mit meiner Tochter unterhalten hat. Außerdem muss man sich ja manchmal freuen, wenn sich Dritte einmischen und nicht wegschauen - lieber einmal zuviel eingemischt als einmal zuviel weggeschaut.
Was bleibt ist ist zum einen die Frage, ob ich in Zukunft die gefährliche Strasse quere, um dann auf der richtigen Seite zu fahren (so wie ich es ohnehin tue, wenn ich ohne Tochter unterwegs bin) und ein gewisses Gefühl des "ungerecht behandelt seins" - fast täglich erlebe ich Autofahrer, die sich regelwidrig verhalten (teilweise in sehr gefährdender Weise) und fast noch nie habe ich erlebt, dass einer von diesen von der Polizei gemaßregelt worden wäre. Irgendwie fehlt mir da die Verhältnismäßigkeit der Mittel …
Posted at 09:44AM Apr 02, 2009 by joerg in Allgemein | Kommentare[0]
PostgreSQL Log Dateien analysieren
Vor einiger Zeit bin ich über pgfouine gestolpert und es hat sich zu einem völlig unersätzlichen Tool in meinem Werkzeugkasten entwickelt. Es gibt einem einen sehr guten Überblick welche Queries die Trödler sind und optimiert werden sollten. Mit seiner Hilfe habe ich es mit relativ wenig Aufwand geschafft ein paar heftig frequentierte Webapplikationen um einen Faktor 2-3 - was die requests/s angeht - zu beschleunigen.
Posted at 10:33AM Mrz 26, 2009 by joerg in Allgemein | Kommentare[0]
Safari 4
Ich konnte natürlich der Versuchung Safari 4 beta zu installieren nicht widerstehen. Es funktioniert auch gut und ist unauffällig was etwaige noch vorhandene Bugs betrifft. Zwei Dinge sind mir denn doch aufgefallen
- Safari 4 ersetzt den 'alten' Safari 3 und installiert sich nicht wie die WebKit Nightly builds parallel zum Standard Safari - wer also den alten zurückhaben will, muss Safari 4 wieder deinstallieren soweit ich sehen kann.
- Mir geht ein vernünftiger Refresh und Stop Load Button ab - mir gefällt es nicht, daß ich das jetzt in der URL Zeile machen muss
Posted at 04:06PM Feb 26, 2009 by joerg in Allgemein | Kommentare[0]
Time Machine Status in OSX Leopard kaputt
Ich habe jetzt schon seit einigen Monaten ein Problem mit dem Stattus von Time Machine unter OSX Leopard. Klicke ich auf das Time Machine Status Symbol in der Menüleiste ist die Angabe 'Letzted Backup' immer leer. Auch in Systemeinstellungen > Time Machine wird weder ein Wert für 'Ältestes Backup' noch für 'Letztes Backup' angezeigt. Ansonsten scheint Time Machine tadellos zu funktionieren. Aber die fehlende Status Information hat mich nervös gemacht. Recherchen im Web haben keine Lösungen aufgezeigt und so habe ich heute noch einmal herumprobiert - und siehe da - es ist gelöst. Ich habe in Systemeinstellungen > Time Machine unter 'Volume wechseln …' 'Ohne' ausgewählt und bestätigt. Dann habe ich noch einmal 'Volume wechseln …' ausgeführt und wieder das Backup Volume ausgewählt, bestätigt und jetzt geht es wieder. So weit ich das sehen kann ist mein Backup weiterhin voll funktionsfähig - allerdings übernehme ich dafür keinerlei Verantwortung ;-)
Posted at 03:03PM Feb 05, 2009 by joerg in Allgemein | Kommentare[0]
@Transactional und @Controller in SpringMVC
Ich verwende schon seit einiger Zeit SpringMVC für einige meiner Projekte. Besonders gut gefällt mir das praktisch konfigurationslose Schreiben von Controllern, die schlicht und einfach durch das Annotieren mit @Controller, @RequestMapping und @RequestParam 'zum Leben erweckt' werden. Ich bin jetzt schon zweimal in Versuchung geraten eine Controller Methode durch das Annotieren mit @Transactional transaktional über mehrere DAO Methoden zu machen - doch dabei gibt es ein paar Dinge zu beachten. Zunächst einmal erbt der DispatcherServletContext nicht automatisch ein etwaiges $lt;tx:annotation-driven /> von seinem RootApplicationContext. Hat man das gelöst, aber keinen Bytecode Generator an Bord, der sich auch auf das Modifizieren von Klassen an Stelle von Interfaces verstehet (wie z.B. CGLib 2), so muß man einen solchen als weitere Abhängigkeit aufnehmen oder für @Transactional Controller auch noch Interfaces erzeugen. Nach einigem Überlegen bin ich zu dem Schluss gekommen, dass transaktionale Controller Methoden einen Holzweg darstellen und man anstatt dessen lieber eine Service oder Manager Klasse im Business Logik Layer einführen sollte, die die DAO Aufrufe, welche in einer gemeinsamen Transaktion ablaufen sollten, bündelt.
Posted at 02:56PM Feb 03, 2009 by joerg in Allgemein | Kommentare[0]
Rasender Stillstand
lautet der Titel eines Buches des Philosophen Paul Virilio. Vor fast 20 Jahren war ich einmal so fasziniert von einem Interview mit diesem Paul Virilio, dass ich mir das Buch zugelegt habe - nur um festzustellen, dass ich der Lektüre intellektuell nicht gewachsen war (soviel zu meinen philosophischen Fähigkeiten).
Vor 2 Wochen nun hat unsere Firma 10-jähriges Firmenjubiläum gefeiert und ich habe in den letzten Wochen des öfteren diese 10 Jahre Revue passieren lassen und versucht in irgendeiner Weise ein Resümee zu ziehen - dabei ist mir der rasende Stillstand eingefallen (auch wenn das vielleicht nicht für jedermann nachvollziehbar ist).
Kurzum, was mir irgendwie bemerkenswert erscheint ist das Paradoxe, dass die technische Entwicklung sich immer weiter zu beschleunigen scheint, gleichzeitig aber viele Dinge nach wie vor unendlich lange brauchen. Ich möchte das an ein paar Beispielen erläutern
Zu Beginn unserer kleinen Firmenkarriere war ich auf einem Nokia Workshop zum Thema WAP. 'Mobile' schien damals 'the next big thing' zu sein. Heute wissen wir, dass WAP 'dead on arrival' war - und es sollte bis Ende 2007 dauern (über diesen Zeitpunkt lässt sich trefflich streiten, aber ich glaube da liege ich ziemlich richtig), dass die mobile Nutzung der Datennetze im Mainstream ankam. Gleichzeitig denke ich, dass es erstaunlich ist, wie viel von der Leistung, die ein damaliger Schreibtisch Computer besaß heute in einem iPhone zu finden ist - man denke nur daran wie wackelig Videos (und ich rede nicht von Streaming Videos) sich auf den Computer von 1999 anfühlte
Auch ist es unglaublich wie wichtig das Web in dieser Dekade geworden ist, wie sehr es das Alltagsleben so vieler Menschen wenn nicht bestimmt, so doch zumindest berührt. Aber wir Entwickler schlagen uns immer noch mit sehr ähnlichen Problemen bei der Entwicklung desselben herum - man denke nur an Browser Inkompatibilitäten (IE6?) etc.
Gerade erleben wir den zweiten Zusammenbruch der 'New Economy' und man kann sich des Gefühls nicht erwehren, dass die Probleme ziemlich ähnlichen denen vor knapp 10 Jahren sind: Es geht vielen der Web 2.0 Größen immer noch vorrangig um Reichweite und wenige haben ein wirklich nachvollziehbares Geschäftsmodell gefunden. Auch auf praktikables Micropayment warten wir immer noch vergeblich - und ich glaube das Warten wird nie enden
Spam ist ein riesiges und mit unglaublicher Geschwindigkeit gewachsenes Problem aber potentielle Lösungen, die das Problem an der Wurzel packen sind auch nach 10 Jahren nicht wirklich in Sicht. Zwar wird wahrscheinlich mit Windows 7 in 4-5 Jahren den Mainstream bestimmen, welches wohl endgültig das hirntote Sicherheitsmodell der ersten Windowsversionen beerdigt - aber warum hat das so lange gedauert? Ein besseres Modell haben die Väter aller *NIX Systeme schon im IT-Pleistozän erdacht.
Ein bisschen ernüchtert bin ich nach 10 Jahren woerd und fast 15 Jahre nach dem Erstellen meiner ersten Website schon. Andererseits habe ich auch täglich Spass daran ein Teil des IT Ökosystems zu sein, dass neben vielem Überflüssigen auch ganz viele wundervolle und Nützliche Dinge hervorbringt. Und als überzeugter Verwender von Open Source Werkzeugen kann ich nur immer wieder staunen wie gut diese geworden sind, ja dass man wohl sagen kann, dass es sich bei vielen um die besten im Markt handelt - auch wenn wir wohl auf den Durchbruch von Linux auf dem Desktop noch bis zum letzten Tag warten müssen./p>
Angst, dass mein Wissen und meine Fähigkeiten in Bälde nicht mehr nachgefragt werden könnten habe ich jedenfalls nicht.
Posted at 11:02AM Jan 23, 2009 by joerg in Allgemein | Kommentare[0]
Trac als Issue Tracker
Wir haben vor einiger Zeit angefangen für unser Issue Tracking Trac einzusetzen. Und nach ein paar Wochen damit muß ich sagen, daß es mir recht gut gefällt. Es bietet die maximal erträgliche Funktionalität, mit der ich leben kann. Außerdem lässt es sich recht komfortabel mit Eclipse Mylyn inetgrieren, wenn man das Trac Plugin installiert und den Anweisungen hier folgt. Dann nur noch in den Task Repository Properties als Access Type das XML-RPC plugin wählen. Ich benutze vor allem auch gerne den Source Browser - das ist manchmal einfach bequemer und schneller als in Eclipse.
Posted at 10:55AM Jan 14, 2009 by joerg in Allgemein | Kommentare[0]
OSX DNS Cache löschen
Der Mac kann manchmal ein ziemliches Elephanten Gedächtnis für inkorrekte DNS Einträge haben. Besonder wenn man DNS Änderungen im eigenen Netzwerk testen will ist das ärgerlich - aber es gibt Abhilfe: ein
dscacheutil -flushcache
im Terminal löscht den DNS Cache und beim erneuten Aufruf eines geänderten DNS Eintrags wird wieder neu nachgeschaut.
Posted at 03:08PM Dez 15, 2008 by joerg in Allgemein | Kommentare[0]
Tomcat, Apache, mod_proxy, SSL
Ein weiteres Thema, was mich im Laufe des angesprochenen Projektes beschäftigte war das Thema Tomcat Hosting. Ich betreue schon einige kleinere Tomcat Seiten, habe mich aber nie so recht mit mod_jk anfreunden können (obwohl es funktioniert). Nun bin ich via Webinar von Springsource auf mod_proxy gestossen und empfand die Konfiguration von mod_proxy als wirklich simpel. Die Aufgabenstellung war die folgende: Ich wollte zwei Tomcat Knoten hinter einem Apache haben - nicht so sehr wegen höherer Performance sondern um mir die Möglichkeit zu geben einen Knoten bei einem Applikations-Update zu restarten ohne notwendigerweise die ganze Webseite abzuschalten - wobei alle SSL Arbeit der Apache/openssl erldedigen sollte. Ich habe also zwei Tomcats mit je zwei ajp Konnektoren aufgesetzt:
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" URIEncoding="UTF-8" scheme="http" secure="false" proxyName="host.com" proxyPort="80" />
<!-- Define an AJP 1.3 Connector on port 8019 for 'secure' requests -->
<Connector port="8019" protocol="AJP/1.3" URIEncoding="UTF-8" scheme="https" secure="true" proxyName="host.com" proxyPort="443" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
Dabei empfangen beide Konnektoren unverschlüsselte Requests, da der Apache ja
die SSL Entschlüsselung vornimmt, allerdings wird einer der Konnektoren als
'secure' definiert, so daß ich dann im Applikationcode wissen kann ob der
Request via SSL kam oder nicht.
Weiterhin ist zu beachten, daß das jvmRoute Attribut im Engine Tag gesetzt
werden muß (auf unterschiedliche Werte in den beiden Tomcats).
Nun definiere ich zwei virtuelle Hosts im Apache, einen normalen und einen SSL
Host, dabei unterscheidet sich die Konfiguration mit Ausnahme der SSL
spezifischen Direktiven praktisch überhaupt nicht. Diese sieht ungefähr so aus:
# Cluster configuration
<Proxy balancer://tomcatcluster>
BalancerMember ajp://localhost:8009 route=node1
BalancerMember ajp://localhost:8010 route=node2
</Proxy>
# Pass all requests except the manager
# and some static resources to the cluster
ProxyPass /balancer-manager !
ProxyPass /css !
ProxyPass /js !
ProxyPass /images !
ProxyPass / balancer://tomcatcluster/ nofailover=On stickysession=JSESSIONID|jsessionid
# Configure the manager
<Location /balancer-manager>
SetHandler balancer-manager
Order Allow,Deny
Allow from localhost
</Location>
Die Module mod_proxy, mod_proxy_balancer und mod_proxy_ajp müssen dazu geladen
sein. Alternativ könnte man auch via http anstatt über ajp gehen und müsste
dann anstatt mod_proxy_ajp mod_proxy_http laden und die Konfiguration
entsprechend anpassen.
Dabei muß ich natürlich aufpassen, daß der nicht-SSL Host auch mit den als
nicht secure definierten Konnektoren spricht. Der Balancer Name
(tomcatcluster) kann frei gewählt werden.
Dann werden noch ein paar URIs vom proxying ausgespart (/balancer-manager,
/css, /js, /images) und die Arbeit ist getan.
Jetzt kann ich wenn ich auf http(s)://hostname/balancer-manager gehe die
einzelnen Cluster Mitglieder temporär vom Cluster ausschliessen.
Funktioniert bei mir wunderbar (Hosting Plattform ist openSUSE 11.0).
Allerdings habe ich nicht das bei openSUSE mitgelieferte Tomcat Paket
verwendet, sondern Vanilla Tomcats von tomcat.apache.org mit selbst
geschriebenen Init Skripten, da SuSE mehrere Tomcat Instanzen zwar vorsieht
aber das ganze nicht so wirklich funktionierend umgesetzt hat.
Posted at 06:03PM Dez 03, 2008 by joerg in Allgemein | Kommentare[2]
Compass - oder - Suchen einfach gemacht
Eine der wirklich wichtigen Dinge in einer modernen Webapplikation ist eine gut funktionierende Suche. Um eine solche zu implementieren gibt es in Java-Land natürlich das fantastische Lucene - allerdings ist Lucene ziemlich low-level und es erfordert einiges an Arbeit und Lines of code um sein Domänen- Modell in Lucene Dokumente zu überführen, einen Such Controller zu basteln und dann aus den gefundenen Dokumenten wieder die ursprünglichen Domänen-Modell Objekte zu machen - nicht zu vergessen die Mühen die es bereitet den Index dann up-to-date zu halten wenn die Modell-Objekte geändert werden. Doch ich bin natürlich nicht der einzige, der dieses Problem kennt und so hat sich Shay Banon sehr kundig dieses Problems angenommen und das Compass Framework entwickelt. Was bietet das Compass Framework? Compass lässt sich wohl grob in zwei Hauptbereiche aufteilen Einen deklarativen Mechanismus um seine Domänen-Modell-Objekte (DMO) auf Lucene Dokumente zu mappen - entweder über Annotations oder über XML Mapping Dateien. Eine Mechanismus, der sich in den verwendeten DataAccess Layer einklinkt und automatisch den Lucene Index up-to-date hält. Dazu ein (nicht 100% vollständiges) Beispiel. Es handelt sich bei meiner Webapplikation im übrigen um eine Spring/SpringMVC Applikation, bei der Hibernate als Persistenz/ORM Schicht zur Verwendung kommt. Zunächst ein simple DMO
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableComponent;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
@Entity
@Table
@SequenceGenerator(name = "newsSequence", sequenceName = "news_seq")
@Searchable()
public class News {
private Long id;
private String title;
private String summary;
@Id
@GeneratedValue(generator = "newsSequence")
@SearchableId
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column
@SearchableProperty
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Column(length = 500)
@SearchableProperty
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
Es handelt sich hier um ein POJO News Objekt, welches mit Hilfe von
JPA/Hibernate persistiert wird. Die @Searchable Annotation auf Klassenebene
zeigt an, daß dieses Objekt indiziert werden soll. @SearchableId teilt Compass
mit, über welche Eigenschaft eine Instanz dieses Objekts eindeutig identifiert
werden kann und die @SearchableProperty Annotationen mappen die zu
indizierenden Eigenschaften.
So weit so einfach. Jetzt muß Compass konfiguriert werden. Dazu fügt man einer XML basierten Spring Applikationskontext-Konfiguration folgendes hinzu:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:compass="http://www.compass-project.org/schema/spring-core-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.compass-project.org/schema/spring-core-config http://www.compass-project.org/schema/spring-compass-core-config-2.1.xsd">
<compass:compass name="compass" txManager="transactionManager">
<compass:connection>
<compass:file path="${compass.index.path}" /><!-- A system property or environment variable of that name needs to be specified at runtime, defaults to '/[temp_dir]/compass-index' -->
</compass:connection>
<compass:transaction factory="org.compass.spring.transaction.SpringSyncTransactionFactory" />
<compass:mappings>
<compass:class name="de.woerd.aktionsbund.model.News"/>
</compass:mappings>
</compass:compass>
<bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps" init-method="start" destroy-method="stop">
<property name="compass" ref="compass" />
<property name="gpsDevices">
<list>
<bean class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
<property name="gpsDevice">
<bean class="org.compass.gps.device.hibernate.HibernateGpsDevice">
<property name="sessionFactory" ref="sessionFactory" />
<property name="nativeExtractor"><bean class="org.compass.spring.device.hibernate.SpringNativeHibernateExtractor" /></property>
</bean>
</property>
<property name="name" value="hibernateGpsDevice"/>
</bean>
</list>
</property>
</bean>
</beans>
Dabei sind transactionManager und sessionFactory die entsprechend für
Hibernate konfigurierten Beans.
Von nun an lauscht Compass an allen Transaktion und aktualisiert den Index
entsprechend.
Nun zur Suche. Man kann den Lucene Index direkt mit Lucene durchsuchen aber
Compass stellt noch ein paar kleine Helfer dazu zur Verfügung. Ich habe eine
dieser Helfer nach einem Tip im Compass Forum noch etwas erweitert, da ich die
Suche auf bestimmte DMO begrenzen können wollte.
import org.compass.core.Compass;
import org.compass.core.CompassQuery;
import org.compass.core.CompassQueryBuilder;
import org.compass.core.CompassSession;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchHelper;
public class CompassAliasSearchHelper extends CompassSearchHelper {
private String[] aliases = {};
public CompassAliasSearchHelper(Compass compass, Integer pageSize) {
super(compass, pageSize);
}
public CompassAliasSearchHelper(Compass compass) {
super(compass);
}
public CompassAliasSearchHelper(Compass compass, Integer pageSize, String[] aliases)
{
this(compass, pageSize);
this.aliases = aliases;
}
@Override
protected CompassQuery buildQuery(final CompassSearchCommand searchCommand, final CompassSession session) {
CompassQueryBuilder queryBuilder = session.queryBuilder();
CompassQuery compassQuery = queryBuilder.queryString(searchCommand.getQuery()).toQuery().setAliases(this.aliases);
return compassQuery;
}
}
Dann kann man sehr schön mit Code ähnlich dem folgenden Fragment suchen
import org.compass.core.Compass;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;
...
int PAGE_SIZE=10;
int PAGE_NO = 1;
String query = "eine such eingabe";
CompassAliasSearchHelper searchHelper = new CompassAliasSearchHelper(compass, PAGE_SIZE, new String[] {"News"});
CompassSearchResults result = searchHelper.search(new CompassSearchCommand(query, PAGE_NO));
Dabei ist 'compass' eine Instanz von org.compass.core.Compass, die gewöhnlich
von Spring in die suchende Klasse injiziert wird.
Es hat mich etwas Zeit gekostet, mich in Compass einzuarbeiten. Jetzt möchte
ich es nicht mehr missen. Compass ist einfach da im Hintergrund und verrichtet
zuverlässig seinen Dienst.
Zudem lässt sich jedes Problem relativ schnell über einen Post an das Compass
Forum lösen, in dem der Master himself sehr viele Fragen schnell und
freundlich beantwortet.
Der nächste Schritt ist jetzt den Index über mehrere Applikationsserver zu
clustern, evtl. mit Terracotta - wünscht mir Glück!
P.S.: Noch ein Wort der Warnung: Wenn man mit den Suchergebnissen arbeitet, hat man Zugriff auf 'echte' Instanzen der DMOs - allerdings sind in jenen natürlich nur die Eigenschaften mit Werten befüllt, die Compass auch indiziert hat, d.h. man sollte ich gut überlegen, was man alles indiziert und wie man mit den Ergebnissen arbeitet - die Objekte verhalten sich einfach nicht ganz genau so wie jene, die aus einer Hibernate Session kommen.
Posted at 04:39PM Dez 03, 2008 by joerg in Allgemein | Kommentare[0]
Wenn man zu beschäftigt ist zu bloggen
Die letzten Monate war ich mit einem der größten Projekte, die wir bisher verwirklicht haben (noch nicht ganz) beschäftigt und habe das bloggen völlig vernachlässigt - dabei habe ich viele bloggenswerte Dinge gelernt. Daher will ich das jetzt nachholen und mir selber helfen, mich der Lösungen die ich gefunden habe zu erinnern.
Posted at 03:43PM Dez 03, 2008 by joerg in Allgemein | Kommentare[0]
Ein paar Tips für (Java-) Entwickler auf Mac OSX
-
Verschiedene Java Versionen
Auf einem aktuellen Mac sind immer mehrere Java Versionen zu finden. Wenn es sich um einen halbwegs aktuellen 64-bit Intel Mac handelt sollte die Softwareaktualisierung mittlerweile auch Java 6 installiert haben. Einen Überblick bekommt man mit einem
ls -l /System/Library/Frameworks/JavaVM.framework/Versions/. Doch welche Version wird nun verwendet? Antwort: Das kommt darauf an.
Startet man eine Java Desktop Applikation wie z.B. Eclipse oder DbVisualizer so kommt die Java Version zum Einsatz auf die der symlink 'CurrentJDK' in oben genanntem Verzeichnis zeigt.
Eine Überraschung kann man erleben wenn manjava -versioneingibt. Die Variante die dabei zum Zuge kommt ist u.U. eine andere und wird über ein kleines Programm in Programme > Dienstprogramme > Java > Java-Einstellungen kontrolliert. Und zwar kommt die Version zur Verwendung, die in der Liste 'Java-Programm Laufzeit-Einstellungen' zuerst steht.
Schliesslich kann natürlich auch noch jedes Programmm selber versuchen eine geignete JVM zu finden. Maven besipielsweise tut das. Um Maven z.B. zu zwingen Java 6 zu verwenden kann man die Umgebungsvariable JAVA_VERSION auf 1.6 setzen -
Subversion und ssh
Ich habe ein simples Subversion Repository auf meinem MacPro, auf das ich von einer weiteren Maschine via ssh darauf zugreifen. Allerdings habe ich mittels MacPorts Subversion 1.5 installiert und damit das Repository angelegt. Versuche ich jetzt via ssh zuzugreifen bekomme ich irgendwelche Fehlermeldungen, daß die Repository Versionen inkompatibel sind. Das liegt daran, daß OSX ein svn binary mitbringt und zwar in der Version 1.4.x und dieses in Kontext einer secure shell bevorzugt benutz wird. Dies lässt sich ändern indem man auf dem Server in die Datei ~/.ssh/environment folgendes einträgt
PATH=/opt/local/bin:/usr/bin:/bin:/usr/sbin:/sbinund damit den macport binaries Vorrang gibt. Außerdem muss man dann noch die /etc/sshd_config ändern und dort PermitUserEnvironment auf yes setzen (WARNUNG: ich weiß nicht ob das irgendwelche potentiellen Sicherheitslöcher aufreißt bitte nur nachmachen, wenn man in einer geschützen Umgebung arbeitet) -
PostgreSQL mit MacPorts
Ich hatte Probleme mit der Initailisierung von PostgreSQL. Beim Aufruf von initdb gab es irgendwelche access denied Probleme beim Zugriff auf /dev/null. Dies scheint daran zu liegen, daß der postgres user mit dem Wert
/dev/nullfür die UserShell property angelegt wird. Dies lässt sich ändern indem man folgenden Befehl ausführtsudo dscl . -create /Users/postgres UserShell /usr/bin/false
Posted at 05:19PM Jul 30, 2008 by joerg in Allgemein |