Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JSR-352 - Ein Standard für die Batchentwicklung

tobiasflohre
December 01, 2013

JSR-352 - Ein Standard für die Batchentwicklung

German article on the Java batch specification JSR-352.

tobiasflohre

December 01, 2013
Tweet

More Decks by tobiasflohre

Other Decks in Programming

Transcript

  1. magazin Java • Architekturen • Web • Agile www.javamagazin.de Java

    Mag 12.2013 Neo4j-Tutorial: Graphdatenbanken zum Anfassen 87 Mehr Themen: Active Annotations mit Xtend 28 | JSR 352: Ein Standard für die Batchent- wicklung 69 | HBase – NoSQL-Lösung mit großer Zukunft 10 OSGi at your Service Generisches Ressourcenmodell  34 OSGi-Tests mit Groovy Integrationstests implementieren  80 © iStockphoto.com/aleksandarvelasevic IT im Wandel Warum wir jetzt müssen. Java 8 Lambda-Ausdrücke und Methodenreferenzen  21 Crowd­- governance für agile Teams 54 Sind Sie ein Gewinner des ­Wirtschafts- ­darwinismus? 40 Was der IT-Wandel für die ­Java-Community bedeutet 48 umdenken Sonderdruck für www.codecentric.de
  2. Agile Sonderdruck von Tobias Flohre Der Java Community Process (JCP)

    dient dazu, Stan- dards im Java-Bereich zu etablieren. Jeder kann daran teilnehmen [1]. Wenn jemand einen neuen Standard etablieren möchte, reicht er einen JSR ein. Das Execu­ tive Committee, bestehend aus Firmen und Einzelperso- nen [2], entscheidet dann, ob der JSR generell zugelassen wird. Wenn das der Fall ist, wird eine Expertengruppe gegründet, die den Standard ausarbeitet. Am Ende ent- scheidet das Executive Committee, ob der JSR in der ausgearbeiteten Fassung akzeptiert wird. Der JSR 352 enthält die Standardisierung der Batch- verarbeitung im Java-Bereich [3]. Eingereicht wurde er im Oktober 2011 von IBM und dann auch angenom- men. Chris Vignola wurde Specification Lead. In der Expertengruppe sind IBM, Credit Suisse, Oracle, VM- ware und Red Hat sowie Simon Martinelli und Michael Minella als Einzelpersonen vertreten, wobei Michael Minella seit etwa einem Jahr Project Lead von Spring Batch ist. Seit Mai dieses Jahres ist die Spezifikation nun final und Teil von Java EE 7. Obwohl IBM einen star- ken Enterprise-Hintergrund hat, ist das Ziel des JSRs allerdings nicht nur die Java Enterprise Edition, sondern explizit auch die Standard Edition. Das Ergebnis orientiert sich stark an Spring Batch. So wurden die Begrifflichkeiten der Domäne und das Programmiermodell fast eins zu eins übernommen, und auch die Konfiguration eines Jobs in XML ähnelt einer Spring-Batch-Konfiguration stark. Der Ablauf eines Jobs ist ebenfalls identisch. Spring Batch wird die Spezifikation in der Version 3.0 (angekündigt für Herbst/Winter 2013) implementieren. Bisher gibt es bereits eine Implementierung im GlassFish 4 Application Server. Die Spezifikation schreibt übri- gens keine bestimmte Art der Dependency Injection vor, sodass eine Implementierung einerseits CDI, aber ande- rerseits auch Spring DI verwenden kann. Ganz pragmatisch werde ich zunächst einen einfachen Job vorstellen, der nichts anderes macht, als Daten auf die Konsole zu loggen, und die entsprechenden JSR- 352-Artefakte bei ihrem Vorkommen erläutert. Die Job-XML Die Job-XML beschreibt den Ablauf unseres Batchjobs (Listing 1). Dabei kann ein Job mehrere Steps enthalten. Über das Attribut next kann dabei die Reihenfolge der Steps bestimmt werden. Es gibt Batchlet- und Chunk- basierte Steps, wobei die Batchlet-basierte Verarbeitung alle Freiheiten lässt. Das referenzierte Batchartefakt (myBatchlet in Listing 1) muss nur das Interface javax. batch.api.Batchlet implementieren, und bei Start des Steps wird einmal die dort vorhandene Methode process aufgerufen. Diese Art der Verarbeitung eignet sich für vorbereitende oder abschließende Tätigkeiten wie das Kopieren und Löschen von Dateien oder für die Anbin- dung von Legacy-Code, der nicht Chunk-basiert ausge- führt werden kann. Sobald es um die Verarbeitung einer großen Menge von Datensätzen geht, ist die Chunk-basierte Verarbei- tung die richtige Wahl. Ein Datensatz wird dabei als Item bezeichnet. Das Attribut item-count bestimmt die Anzahl der Datensätze, die innerhalb einer Transaktion Artikelserie Teil 1: Java Config, Spring-Data-Support und Co. Teil 2: JSR 352 – Batch Applications for the Java Platform Teil 3: Spring XD – Ordnung für Big-Data-Datenströme JSR 352 – Batch Applications for the Java Platform Ein Standard für die Batchentwicklung Die Batchverarbeitung gehört zu den ältesten Verarbeitungsformen in der IT überhaupt, und doch gab es im Java-Bereich bisher keinen Standard dafür. Das hat sich nun mit dem finalen Release des Java Specification Requests 352 (Batch Applications for the Java Platform, JSR 352) geändert. Dieser zielt nicht nur auf die Enterprise Edition und ist dort Teil von Java EE 7, sondern explizit auch auf die Standard Edition. Eine erste Implemen- tierung gibt es bereits im GlassFish 4, weitere werden bald folgen. Höchste Zeit, sich die Spezifikation genauer anzusehen. © Software & Support Media GmbH 2 www.JAXenter.de javamagazin 4 | 2014
  3. Agile Sonderdruck readItem-Methode ist. Diese wird vom Framework auf- gerufen,

    um jeweils ein Item zu lesen. Der Reader signa- lisiert mit der Rückgabe von null, dass alle Items gelesen wurden und der Step abgeschlossen werden kann. Lis- ting 2 zeigt eine Implementierung, die eine feste Anzahl von Items zurückgibt. Im Processor werden die eingelesenen Items nun be- liebig fachlich verarbeitet. Der Processor muss dafür das Interface javax.batch.api.chunk.ItemProcessor mit der Methode processItem implementieren (Listing 3). Das Framework übergibt das eingelesene Item an diese Me- thode, die ein beliebiges Objekt als Ergebnis der Verar- beitung zurückgeben kann. Gibt sie null zurück, so wird das Item herausgefiltert und nicht an den Writer gegeben. Der Writer schreibt die verarbeiteten Daten in eine be- liebige Ressource. Er implementiert das Interface javax. batch.api.chunk.ItemWriter, dessen wichtigste Metho- de writeItems ist. Listing 4 zeigt ein Beispiel, das alle zu schreibenden Items loggt. Ablauf einer Chunk-basierten Verarbeitung Der Writer erhält im Gegensatz zum Reader und Pro- cessor eine Liste von Items. Wie kommt es dazu? Abbil- dung 1 zeigt die Verarbeitung eines Chunk-orientierten Steps. Sie stammt direkt aus der Spezifikation. Es wer- den immer abwechselnd ItemReader und ItemProcessor aufgerufen, bis item-count-Datensätze gelesen und ver- arbeitet wurden. Die verarbeiteten Datensätze werden Listing 1 <job id="simpleJob"> <step id="batchletStep" next="chunkStep"> <batchlet ref="myBatchlet"/> </step> <step id="chunkStep"> <chunk item-count="2"> <reader ref="dummyItemReader"/> <processor ref="logItemProcessor"/> <writer ref="logItemWriter"/> </chunk> </step> </job> verarbeitet werden sollen, und die Menge dieser Da- tensätze wird als Chunk bezeichnet. Drei Komponen- ten betreiben die Verarbeitung der Datensätze: Reader, Processor und Writer. Dependency Injection und Scoping für Batchartefakte Wie schon erwähnt, schreibt die Spezifikation nicht vor, auf welche Art und Weise die referenzierten Kompo- nenten erzeugt werden. Während es im Java-EE-Umfeld vermutlich meistens per CDI geregelt werden wird, wird Spring Batch sicherlich weiterhin Spring DI verwenden. Die in diesem Artikel gezeigten Beispiele zeigen Kompo- nenten für einen CDI-Container. Im Gegensatz zur DI schreibt die Spezifikation aller- dings Scopes für Batchartefakte vor. Alle innerhalb eines <job />-Tags referenzierten Artefakte haben Job-Scope, werden also für jeden Joblauf neu erzeugt. Alle innerhalb eines <step />-Tags referenzierten Artefakte haben Step Scope, werden also für den Step erzeugt und danach wie- der entfernt. Das sind insbesondere Reader, Processor und Writer, und im Beispiel in Listing 1 sind das auch alle referenzierten Artefakte. Zusätzlich gibt es noch den Scope Partition Step für die parallelisierte Verarbeitung. Reader, Processor und Writer Ein Reader ist dafür verantwortlich, Datensätze einzu- lesen. Dafür implementiert er das Interface javax.batch. api.chunk.ItemReader, dessen wichtigste Methode die Listing 2 @Named public class DummyItemReader extends AbstractItemReader { private String[] input = {"1","2","3","4","5",null}; private int index = 0; @Override public Object readItem() throws Exception { return input[index++]; } } Listing 3 @Named public class LogItemProcessor implements ItemProcessor { private static final Logger log = Logger.getLogger(LogItemProcessor.class); @Override public Object processItem(Object item) throws Exception { log.info("ItemProcessor: "+item); return item; } } Listing 4 @Named public class LogItemWriter extends AbstractItemWriter { private static final Logger log = Logger.getLogger(LogItemWriter.class); @Override public void writeItems(List<Object> items) throws Exception { log.info("ItemWriter: "+items.toString()); } } © Software & Support Media GmbH 3 www.JAXenter.de javamagazin 4 | 2014
  4. Agile Sonderdruck nun gesammelt an den ItemWriter übergeben, damit dieser

    sie batchoptimiert schreiben kann. Danach wird ein Commit für die Transaktion ausgelöst, eine neue Transaktion eröffnet und erneut gelesen und verarbeitet. Start eines Jobs Zur Interaktion mit einer Job-Runtime definiert die Spe- zifikation das Interface javax.batch.operations.JobOp­ er­ator. Mithilfe von Implementierungen dieses Interface können Jobs gestartet und gestoppt werden. Zusätzlich definiert die Spezifikation die Laufzeitobjekte javax.batch. runtime.JobInstance, javax.batch.runtime.JobExecution und javax.batch.runtime.StepExecution, die Daten zu be- stimmten Läufen halten. Das Verhältnis von Job, JobIn- stance und JobExecution ist in Abbildung 2 dargestellt: Während ein Job den fachlichen Typ einer Verarbeitung darstellt, wird eine JobInstance für jeden geplanten Lauf erzeugt. Eine JobExecution stellt dann die tatsächliche Ausführung dar, von der es in der Regel genau eine pro JobInstance gibt. Falls aber eine JobExecution fehlschlägt und der geplante Lauf erneut gestartet wird, wird eine weitere JobExecution zur Job­ Instance erzeugt. Der JobOperator kann über die Factory-Klasse javax. batch.runtime.BatchRuntime ermittelt werden. Der fol- gende Codeausschnitt zeigt den Start unseres Beispiel- jobs. Dabei ist simpleJob der Name der Job-XML-Datei ohne Dateityperweiterung: JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties props = new Properties(); jobOperator.start("simpleJob", props); Jobparameter und Properties Der JSR 352 führt ein interessantes Property-Konzept ein. Praktisch auf jeder Ebene der Job-XML können Properties definiert werden, die dann im Folgenden zur Ersetzung in weiteren Property-Definitionen verwendet werden können, wenn sich diese in der gleichen Hier- archie befinden. Listing 5, direkt aus der Spezifikation, zeigt, wie das geht. Die Property infile.name wurde hier zu postings.txt aufgelöst. Der Zugriff auf die Property in Batchartefakten (in diesem Fall sinnvollerweise ein ItemReader) erfolgt über Injizieren per Annotation: @Inject @BatchProperty(name="infile.name") String fileName; Insgesamt definiert die Spezifikation noch weitere Quel- len für Properties: • #{jobParameters['xxx']}: Zugriff auf Jobparameter, die beim Start des Jobs mitgegeben wurden. • #{systemProperties['xxx']}: Zugriff auf System- Properties. • #{partitionPlan['xxx']}: Bei paralleler Verarbeitung Zugriff auf Daten, die speziell für diesen Thread erstellt wurden. Restartfähigkeit Ein wichtiges Feature bei der Batchverarbeitung ist die Wiederaufsetzbarkeit von fehlgeschlagenen Jobs. In der Regel sind die Daten dann zum Teil verarbeitet, und ein kompletter Neustart kommt nicht in Frage, da dann Da- ten doppelt verarbeitet werden. Die Komponenten, die beim Restart besonders aktiv werden müssen, sind Item- Reader und ItemWriter. Dafür besitzen sie noch weitere Methoden (Listing 6). Klar wird das am besten an einem konkreten Beispiel: Wir haben einen ItemReader, der Datensätze anhand eines SQL-Statements über einen CURSOR aus der Datenbank liest. Das SQL-Statement definiert eine feste Reihenfolge (ORDER BY). Die Methode checkPoint- Info wird immer kurz vor dem Commit einer Chunk- Transaktion aufgerufen. Wir geben in diesem Fall die Anzahl der schon gelesenen Items als Checkpoint zu- rück. Die Methode open wird beim Start des Steps ein- mal aufgerufen. Im Restartfall wird ihr der in der letzten Abb. 1: Chunk- orientierte Verarbei- tung Abb. 2: Job, JobInstance und JobExe- cution Listing 5 <job id="job1"> <properties> <property name="filestem" value="postings"/> </properties> <step id="step1"> <chunk> <properties> <property name="infile.name" value="#{jobProperties['filestem']}.txt"/> </properties> </chunk> </step> </job> © Software & Support Media GmbH 4 www.JAXenter.de javamagazin 4 | 2014
  5. Agile Sonderdruck erfolgreichen Transaktion ermittelte Checkpoint über- geben. In unserem

    Beispiel wäre das also die Zahl der schon gelesenen Items. In der open-Methode wird nun das Statement ausgeführt, der CURSOR geöffnet, und falls es sich um einen Restart handelt und ein Check- point übergeben wurde, werden entsprechend viele Items übersprungen, damit die Verarbeitung mit noch nicht verarbeiteten Items beginnen kann. Restartfähigkeit hängt also immer an der Implemen- tierung des ItemReaders bzw. ItemWriters, es kann sie nicht generisch Out of the Box geben. Skip und Retry Im Normalfall schlägt ein Batchlauf fehl, sobald eine Exception durch die Batchartefakte geworfen wird. Mit der Skip-Funktionalität können bestimmte Exceptions als skippable markiert werden, sodass der Batchlauf nicht abbricht, sondern das Item überspringt. Die Ma- ximalanzahl der zu überspringenden Items wird im At- tribut skip-limit auf dem Tag chunk definiert: <chunk item-count="2" skip-limit="25"> <skippable-exception-classes> <include class="java.lang.Exception" /> <exclude class="java.io.IOException" /> </skippable-exception-classes> </chunk> Mit der Retry-Funktionalität können Exceptions als retryable markiert werden, sodass der Batchlauf nicht direkt abbricht, sondern zunächst versucht wird, die Verarbeitung erneut durchzuführen. Die Maximal­ anzahl der erneuten Versuche wird im Attribut retry- limit auf dem Tag chunk definiert: <chunk item-count="2" retry-limit="25"> <retryable-exception-classes> <include class="java.lang.Exception" /> <exclude class="java.io.IOException" /> </retryable-exception-classes> </chunk> Listener Der JSR 352 definiert eine Reihe von Listenern, die auf bestimmte Ereignisse im Lebenszyklus eines Jobs reagie- ren. Diese können auf Job- und auf Step-Ebene regist- riert werden (Listing 7). Auf Jobebene kann der JobListener registriert werden, der mit den beiden Methoden beforeJob und afterJob entsprechend bei Start bzw. Ende des Joblaufs aufgeru- fen wird. Auf Step-Ebene gibt es folgende Listener: • Der StepListener hat die beiden Methoden beforeStep und afterStep. • Der ChunkListener hat die Methoden beforeChunk, onError und afterChunk. Dieser Listener bezieht sich auf den Chunk, also die Menge der in einer Transakti- on verarbeiteten Datensätze. Alle Methoden dieses Lis- teners werden innerhalb der Transaktion durchgeführt. • ItemReadListener, ItemProcessListener und Item- WriteListener beziehen sich jeweils auf den Aufruf von Reader, Processor und Writer und besitzen jeweils eine before*-, eine after*- und eine on*Error- Methode. • RetryReadListener, RetryProcessListener und Retry- WriteListener werden aktiv, wenn eine retryable Ex- ception geworfen wurde und haben demnach jeweils eine onRetry*Exception-Methode. • SkipReadListener, SkipProcessListener und Skip­ Write­Listener werden aktiv, wenn eine skipp­ able Ex- ception geworfen wurde, und haben demnach jeweils eine onSkip*Item-Methode. Parallelisierung Die Spezifikation sieht eine Art der lokalen parallelen Verarbeitung vor, bei der die Verarbeitung auf mehrere Threads aufgeteilt werden kann. Dabei werden die Da- ten partitioniert, und jeder Thread arbeitet nur auf der ihm zugewiesenen Partition. Ein Beispiel: Wir gehen wie- der von einem Job aus, der über einen Cursor Daten aus einer Datenbank liest und dann weiterverarbeitet. Die Daten sind anhand eines Spartenkürzels geclustert. Statt Listing 6 public interface ItemReader { public void open(Serializable checkpoint) throws Exception; public void close() throws Exception; public Object readItem() throws Exception; public Serializable checkpointInfo() throws Exception; } public interface ItemWriter { public void open(Serializable checkpoint) throws Exception; public void close() throws Exception; public void writeItems(List<Object> items) throws Exception; public Serializable checkpointInfo() throws Exception; } Listing 7 <job id="simpleJob"> <listeners> <listener ref="myJobListener"/> </listeners> <step id="chunkStep"> <listeners> <listener ref="myStepListener"/> </listeners> <chunk item-count="2"> <reader ref="dummyItemReader"/> <processor ref="logItemProcessor"/> <writer ref="logItemWriter"/> </chunk> </step> </job> © Software & Support Media GmbH 5 www.JAXenter.de javamagazin 4 | 2014
  6. Agile Sonderdruck alle Daten sequenziell zu verarbeiten, bilden wir eine

    Partition pro Spartenkürzel. Jede Partition nutzt ein eige- nes JDBC-Statement, das nach dem Spartenkürzel filtert, sodass sich die verschiedenen Threads nicht behindern. Listing 8 zeigt einen statisch partitionierten Job mit zwei Partitionen, eine für die Sparte mit dem Spartenkür- zel „L“ und eine für die Sparte mit dem Spartenkürzel „K“. Über die Attribute partitions und threads kann die Anzahl der Partitionen sowie die Anzahl der Threads, die diese Partitionen verarbeiten sollen, bestimmt werden. Die definierten Properties können in anderen Kompo- nenten verwendet werden, indem über das Schlüsselwort partitionPlan auf sie zugegriffen wird. Listing 9 zeigt ei- nen Teil des partitionedJdbcItemReader. Je nach Partiti- on wird in das Feld sparte „K“ oder „L“ injiziert. Anstatt eines statischen Partition-Plans kann auch eine Mapper-Komponente eingebunden werden, die die Partitionen erzeugt. Ein Mapper, der im obigen Beispiel funktioniert, wird in Listing 10 gezeigt. Der folgende Codeausschnitt zeigt die Job-XML: <partition> <mapper ref="spartePartitionMapper" /> </partition> Neben dem PartitionMapper sieht die Spezifikation noch drei weitere Komponententypen vor, mit denen ein partitionierter Step kontrolliert werden kann. Der PartitionCollector ist je Thread aktiv und bietet die Möglichkeit, Daten über den Verlauf der Partition an eine zentrale Instanz zu schicken. Der PartitionAnalyzer ist im Eltern-Thread einmal aktiv und stellt die zentrale Instanz dar, die die Daten der verschiedenen Partition- Collectors erhält und verarbeiten kann. Der Partition- Reducer wird aktiv, wenn Partitionen fehlschlagen bzw. erfolgreich beendet werden, und kann dann Kompensa- tions- bzw. Aufräumarbeiten durchführen. Spring Batch oder JSR 352? Diese Frage ist so nicht korrekt, denn der JSR 352 ist eine Spezifikation, Spring Batch eine (zukünftige) Im- plementierung dieser Spezifikation. Korrekterweise muss man Implementierungen vergleichen, und da gibt es nun einmal noch nicht so viele. Der GlassFish zumin- dest implementiert tatsächlich nur die Spezifikation und Listing 8 <job id="partitionedJdbcJob"> <step id="chunkStep"> <chunk item-count="2"> <reader ref="partitionedJdbcItemReader"> <properties> <property name="spartenKuerzel" value="#{partitionPlan['sparte']}"/> </properties> </reader> <processor ref="logItemProcessor"/> <writer ref="logItemWriter"/> </chunk> <partition> <plan partitions="2" threads="2"> <properties partition="0"> <property name="sparte" value="L"/> </properties> <properties partition="1"> <property name="sparte" value="K"/> </properties> </plan> </partition> </step> </job> Listing 10 @Named public class SpartePartitionMapper implements PartitionMapper { @Override public PartitionPlan mapPartitions() throws Exception { PartitionPlan partitionPlan = new PartitionPlanImpl(); partitionPlan.setPartitions(2); partitionPlan.setThreads(2); Properties[] propertiesArray = new Properties[2]; Properties properties = new Properties(); properties.put("sparte","L"); propertiesArray[0] = properties; properties = new Properties(); properties.put("sparte","K"); propertiesArray[1] = properties; partitionPlan.setPartitionProperties(propertiesArray); return partitionPlan; } } Listing 9 @Named public class PartitionedJdbcItemReader extends AbstractItemReader { @Inject @BatchProperty(name="spartenKuerzel") private String sparte; @Override public void open(Serializable checkpoint) throws Exception { // Cursor mit Jdbc-Statement öffnen, dabei sparte verwenden. } @Override public void close() throws Exception { // Cursor schließen. } @Override public Object readItem() throws Exception { // Item über Cursor lesen und zurückgeben. } } © Software & Support Media GmbH 6 www.JAXenter.de javamagazin 4 | 2014
  7. Agile Sonderdruck kann so nicht mit Spring Batch mithalten, da

    die Kom- ponenten, die vielen ItemReader und ItemWriter, wei- tergehende Managementmöglichkeiten und vieles mehr fehlen. Deswegen bin ich auch sehr gespannt auf IBMs Implementierung, da IBM ja treibende Kraft war. Dass sich mit IBM und den Machern von Spring Batch die beiden wichtigsten Player im Batchmarkt auf einen Standard geeinigt haben, der Spring Batch sehr nahe kommt, ist eine gute Sache – aus beiden Blickwinkeln. Diejenigen, die bisher auf Spring Batch setzen, haben die Sicherheit, dass es in Zukunft andere Implementierun- gen mit dem gleichen Programmiermodell gibt, und dass man im Notfall wechseln kann. Diejenigen, die auf jetzt entstehende Implementierungen in Java-EE-Servern set- zen, haben die Sicherheit, dass das Programmiermodell schon jahrelang erfolgreich ist. Fazit Die Spezifikation JSR 352 wirkt rund mit kleinen Ab- strichen. (Ich verstehe beispielsweise nicht, warum ItemReader und ItemWriter nicht über Generics para- metrisierbar sind. Dass ein Item vom Typ Object ist, wirkt wie aus einer fernen, alten Zeit). Ab jetzt ist es möglich, anbieterneutrale Batchkomponenten zu ent- wickeln, die sowohl in Spring Batch als auch direkt in jedem Application Server verwendet werden können. Auch das Vermischen von Implementierungen geht nun, man kann also die Laufzeitumgebung des WebSpheres und Komponenten von Spring Batch verwenden, natür- lich erst, sobald beide den JSR implementieren. Würde ich in Zukunft Jobs streng nach dem JSR 352 entwickeln? Vermutlich nicht, da ich Konfigurationen in Java denen in XML vorziehe. Aber es ist gut zu wissen, dass ich im Notfall nicht viel tun muss, um meine Jobs darauf umzustellen. Tobias Flohre arbeitet als Senior Softwareentwickler bei der code- centric AG. Seine Schwerpunkte sind Java-Enterprise-Anwendun- gen und Architekturen mit JEE/Spring, häufig mit Fokus auf Spring Batch. Er spricht regelmäßig auf Konferenzen und bloggt auf blog. codecentric.de. [email protected]. Links & Literatur [1] http://jcp.org/en/participation/membership [2] http://jcp.org/en/participation/committee#SEEE [3] http://jcp.org/en/jsr/detail?id=352 codecentric AG Kölner Landstraße 11 40591 Düsseldorf Tel: +49 (0) 211.9941410 Fax: +49 (0) 211.9941444 E-Mail: [email protected] www.codecentric.de blog.codecentric.de