Hallo community,
für eine komponentenbasierte Entwicklung bin ich gerade dabei ein technisches Konzept zu erarbeiten. Mitunter ist eine Notes-Komponente, die den Zugriff auf Domino kapselt ein Thema.
Mein Ansatz (denke da liegt mein Fehler) ist folgender:
1. Konfiguration der Komponente über ein property-file
# WeberT: properties for the domino-access component.
# generally set the type of access. Following types are available
# IIOP - using CORBA to access domino-server.
# local - using locally installed notes-client to access domino-server
domino.access.type=IIOP oder local
domino.access.host=serverIP oder HostName
domino.access.user=vollständiger benutzer
domino.access.password=passwort
2. Eine "generische" Factory entscheidet, welche Factory (Corba oder local) die Komponente verwendet
import java.util.ResourceBundle;
import lotus.domino.Session;
public class GenericDominoFactory {
// WeberT: the name of the property-file
private static final String DOMINO_FACTORY_RESOURCE_NAME = "dominofactory";
// WeberT: the key to get access-type from property-file
private static final String DOMINO_ACCESS_TYPE_KEY = "domino.access.type";
// WeberT: this are the available access-types
private static final String DOMINO_ACCESS_IIOP = "IIOP";
private static final String DOMINO_ACCESS_LOCAL = "LOCAL";
private static DominoFactory factory;
private GenericDominoFactory() {
}
public static Session getDominoSession() {
if (factory==null) {
ResourceBundle resources = ResourceBundle.getBundle(DOMINO_FACTORY_RESOURCE_NAME);
String accessType = resources.getString(DOMINO_ACCESS_TYPE_KEY);
if (accessType.equalsIgnoreCase(DOMINO_ACCESS_IIOP)) {
factory = new RemoteCorbaDominoFactory();
} else if (accessType.equalsIgnoreCase(DOMINO_ACCESS_LOCAL)) {
factory = new LocalDominoFactory();
} else {
System.out.println("DOMINO: no valid access-type was given, please check " + DOMINO_FACTORY_RESOURCE_NAME + ".properties for key " + DOMINO_ACCESS_TYPE_KEY);
}
}
return factory!=null ? factory.getDominoSession() : null;
}
}
3. Die beiden konkreten Factories folgen dem Interface "DominoFactory"
import lotus.domino.Session;
public interface DominoFactory {
public abstract Session getDominoSession();
}
4a. Entweder übernimmt die Corba-Implementierung das Erzeugen einer Session
import java.util.ResourceBundle;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.Session;
public class RemoteCorbaDominoFactory implements DominoFactory {
private static final String DOMINO_FACTORY_RESOURCE_NAME = "dominofactory";
private static final String DOMINO_HOST_KEY = "domino.access.host";
private static final String DOMINO_USER_KEY = "domino.access.user";
private static final String DOMINO_PASSWORD_KEY = "domino.access.password";
@Override
public Session getDominoSession() {
ResourceBundle resources = ResourceBundle.getBundle(DOMINO_FACTORY_RESOURCE_NAME);
Session session = null;
try {
session = NotesFactory.createSession( resources.getString(DOMINO_HOST_KEY),
resources.getString(DOMINO_USER_KEY),
resources.getString(DOMINO_PASSWORD_KEY));
} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return session;
}
}
..anschließend kann ich die session verwenden und muss nur an "recycle" aller erzeugten Objekte und der Session selbst kümmern.
4b. Oder die Local-Implementierung erzeugt die Session
import java.util.ResourceBundle;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
public class LocalDominoFactory implements DominoFactory {
@Override
public Session getDominoSession() {
NotesThread.sinitThread();
Session session = null;
try {
session = NotesFactory.createSession();
} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// NotesThread.stermThread();
return session;
}
}
Neben dem Problem, dass ich eigentlich gerne die Session über die überladene static-Methode NotesFactory.createSession((String)null, user, pwd) erzeugen würde (da bekomme ich user is not a server?!? - dazu habe ich aber einen weiteren Thread hier eröffnet), stehe ich vor der Problematik, wie ich das mit dem stermThread() am besten handhabe.
Wenn ich das bereits in der Implementierung aufrufe, erhalte ich logischerweise später die Meldung:
Object has been removed or recycled
wenn ich es nicht aufrufe, besteht die Gefahr, dass Notes crashed.
Meine Vermutung ist, dass mein Ansatz für Notes nicht praktikabel ist?
Welche Ansätze gibt es, um eine solche Komponente zu entwickeln (einen globalen Thread der die Session aufrecht hält? Einen finally-Block? etc...).
Bin für jeglichen Input dankbar.
Grüße Thomas :-)
Hallo zusammen,
ich brauche nochmal einen kleinen Gedankenanstoß für die Implementierung eines Thread-Pools.
Für das Java Concurrent-Framework habe ich mir einige Testklassen geschrieben, die soweit auch funktionieren:
- eine generische Pool-Factory die entscheidet ob Single-, Fixed- oder Cached-Pooling verwendet wird
- eine Test-Klasse MyNotesThread
- eine NotesThread-Factory für die initialisierung von MyNotesThread
- 2 Interfaces (MyNotesRunnable und MyNotesCallable für Rückmeldungen)
soweit so gut...
Wenn die Factory jetzt einen Thread erzeugt, kann ich dort beispielsweise die Session erzeugen und den Thread vorhalten bis ein "Worker" Arbeit liefert.
Das Concurrent-Framework übernimmt die Initialisieung und Wiederverwendbarkeit der Threads. Die Wiederverwendbarkeit der Threads ist gerade meine Denkblokade :-)
Folgender exemplarischer Code:
ExecutorService executor = Executors.newFixedThreadPool(2, new NotesThreadFactory());
List<Future<Boolean>> list = new ArrayList<Future<Boolean>>();
for (int i = 0; i < 6; i++) {
Future<Boolean> submit = executor.submit(new NotesCallable() {
private Random generator = new Random();
@Override
public Boolean call() throws Exception {
long sleepTime = generator.nextInt( 10000 );
System.out.println("sleeping for " + sleepTime + " milliseconds");
Thread.sleep(sleepTime);
return true;
}
});
list.add(submit);
}
erzeugt über die NotesThreadFactory initial 2 Threads der Klasse "MyNotesThread". Der Konstruktur der Klasse übernimmt hier exemplarisch die initialisierung:
public NotesThreadData(Runnable r) {
super(r);
try {
NotesThread.sinitThread();
session = NotesFactory.createSession();
System.out.println("Session initialized: " + session.hashCode());
} catch (NotesException e) {
e.printStackTrace();
}
}
und hält die session vor für die kommenden Worker.
Im obigen Beispiel werden 6 NotesCallable erzeugt, die parallel (immer 2 aktiv) abgearbeitet werden.
Wie schaffe ich es jetzt, dem Thread mitzuteilen nach jedem abgearbeiteten Worker die session zu recyclen, sTermTerm() aufzurufen und sich neu zu initialisieren?
Hab da gerade ne komplette Denkblockade und komme nicht weiter.
Freue mich auf eure Antworten.
PS: Wenn ich einen brauchbaren Stand habe, kann ich ihn auch gerne zum Test zur Verfügung stellen.
Grüße Thomas :-)
Noch was am Rande: Welche Funktionalität bietet die Klasse:
lotus.notes.addins.util.NotesThreadPool;
finde leider nichts brauchbares was diese Klasse betrifft?!?
Du könntest unter das Interface direkt eine Abstrakte Implemtierung hängen, die eine Templatemethode (Müsste eines der GoF-Patterns sein) enthält: (Ich glaube, dass es geschickter ist, die Inititialisierung vielleicht nicht im Konstruktor schon zu machen)
public void run() {
initSession()
doWork()
terminateSession()
}
public void initSession() {
// initialisiere NotesSession
}
public terminateSession() {
// terminate NotesSession usw.
}
public abstract doWork()
In der Methode doWork passiert dann die eigentliche Arbeit.
Das Ganze aus dem Bauch heraus, ohne deinen Code im Detail zu kennen.
Ich versuche das ganze an einem "out-of-the-box" beispiel klarzumachen.
Ist stark vereinfact, hoffe es ist alles drin und wird deutlich:
NotesThreadData sieht folgendermaßen aus:
package out.of.the.box;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
public class NotesThreadData extends Thread {
private Session session = null;
public NotesThreadData(Runnable r) {
super(r);
try {
NotesThread.sinitThread();
session = NotesFactory.createSession();
System.out.println("Session initialized: " + session.hashCode());
} catch (NotesException e) {
e.printStackTrace();
}
}
}
Diese werden von dieser Factory erzeugt:
package out.of.the.box;
import java.util.concurrent.ThreadFactory;
public class NotesThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new NotesThreadData(r);
}
}
das ganze wird über einen ThreadPool gesteuert:
package out.of.the.box;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NotesThreadPool {
private static final int NTHREDS = 2;
private ExecutorService executorService = null;
public NotesThreadPool() {
executorService = Executors.newFixedThreadPool(NTHREDS, new NotesThreadFactory());
}
public void doSomeNotesWork(Runnable r) {
executorService.execute(r);
}
public void shutDown() {
// WeberT: make the executor accept no new threads and finish all existing threads in the queue
executorService.shutdown();
// WeberT: wait until all threads are finished
while (!executorService.isTerminated()) {}
System.out.println("Finished all threads");
}
}
Zum starten was passiert:
package out.of.the.box;
public class Main {
public static void main(String[] args) {
NotesThreadPool pool = new NotesThreadPool();
for (int i = 0; i < 10; i++) {
pool.doSomeNotesWork(new Runnable() {
@Override
public void run() {
System.out.println("Runnable run");
}
});
}
pool.shutDown();
}
}
Beim Start werden für beide Threads eine Session initialisiert. Ich möchte, dass dies nach jedem Worker passiert (In diesem Beispiel sind die Worker simple Runnables).
Hoffe es wird deutlich.
Danke für die Unterstützung und verschneite Grüße
Thomas :-)
Okay, jetzt wird es ein wenig klarer. Vermutlich macht es Sinn die Methode run(), von der ich meine, dass sie run() auf dem im Konstruktor übergebenen Runnable aufruft, zu überschreiben: (nur eine grobe Skizze/Idee ohne zu probieren)
@Override
public void run() {
super.run();
//session recycling and reinit
}
Meinst du in etwa sowas:
package out.of.the.box;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
public class NotesThreadData extends Thread {
private Session session = null;
public NotesThreadData(Runnable r) {
super(r);
try {
NotesThread.sinitThread();
session = NotesFactory.createSession();
System.out.println("Session initialized: " + session.hashCode());
} catch (NotesException e) {
e.printStackTrace();
}
}
@Override
public synchronized void start() {
System.out.println("start was called with session: " + session.hashCode());
super.start();
}
@Override
public void run() {
System.out.println("run was called with session: " + session.hashCode());
super.run();
}
}
das Problem hierbei ist, dass die run() Methode ebenfalls nur einmal aufgerufen wird, output beim ausführen:
Session initialized: 16112134
start was called with session: 16112134
Session initialized: 18508170
start was called with session: 18508170
run was called with session: 18508170
Runnable run
Runnable run
Runnable run
Runnable run
Runnable run
Runnable run
Runnable run
Runnable run
Runnable run
run was called with session: 16112134
Runnable run
Finished all threads
ne idee?
vielleicht ein eigener ExecutorService implementieren, um die Threads dort zu initialisieren?
Danke und Grüße Thomas :-)
Edit: Ich ziehe mir gerade die Java-Sourcen und schaue mir die Implementierung des ExecutorService an - der könnte dann auch gleich zu beginn n-Threads initial erstellen bevor die ersten Worker vorbeischauen.
Update: Generische Factory ist fertig für IIOP und local - jetzt ist der Zeitpunkt mich mit meinem Problem "user is not a server" für internet-sessions zu befassen :-)
Grüße Thomas :-)
------------8<---------(snip)-------------------------------
Edit: den generischen teil werde ich noch ein wenig umstricken...
Kurz was anderes: Ich bekomme ab und an den Fehler:
Notes initialization failure - err 41728
ist bei IBM auch schon durch einen anderen Benutzer gemeldet. Kennt jemand diesen Fehler?
------------8<---------(snip)-------------------------------
Edit: Suche jemanden, der Bock hat, die kleine Komponente zu testen. Mich würde interessieren, ob der Fehler auch bei euch auftritt. JUnit-Testklassen für den Verbindungstest sind vorhanden für:
- GenericSessionFactory (über property konfigurierbar)
- LocalSessionFactory
- RemoteCorbaSessionFactory
- InternetSessionFactory
..eine Test-Suite für alle Tests gemeinsam.
Die InternetSessionFactory wirft aktuell leider den Fehler (user is not a server) bei mir (Notes 8.5.2). Wäre für einen Tester dankbar :-)
Weiter bekomme ich wie gesagt ab und an den Fehler:
Notes initialization failure - err 41728
Wäre auch interessant, wie sich das bei euch verhält?
Grüße Thomas :-)
------------8<---------(snip)-------------------------------
Update: Zwischenstand für potentielle Tester :-)
Die Komponenten ist bisher ein Geflecht aus 16 Klassen und bitet folgende Features:
- NotesThreadPooling über Concurrent-Framework
- 3 Zugriffsmodi (IIOP, http, local)
- Vorhalten einer neuen Session für jeden eintreffenden task
- Recycle der Session nach abgearbeitetem Task
- Konfiguration über property-file
- Junit-Tests (siehe oben)
- Testlauf mit zufälliger ABM :-)
Konfigurierbar sind bisher folgende Optionen:
# access type (IIOP, http, local)
domino.access.type=local
# connection-properties
domino.access.fullAccess=false
domino.access.host=172.30.41.115
domino.access.sslActive=false
domino.access.user=easyxbase easyxbase/comdms
domino.access.password=topS3cr3t
domino.access.inetPassword=topS3cr3t
# Thread-Pool config
#
# domino.threadpool.size - set maximal count of workers for incoming tasks
#
domino.threadpool.size=2
Aktuell in Arbeit:
- generische Rückmeldung von Tasks nach getaner Arbeit über "Callable" statt "Runnable"
ich halte euch auf dem laufenden.
Grüße Thomas :-)
------------8<---------(snip)-------------------------------
Update: callables sind nun auch möglich, bin mir aber noch nicht sicher, wie ich mit der Rückmeldung der Future-Objekte umgehe?
Habe mal testweise 10.000 Tasks über den Thread-Pool mit 50 Threads abarbeiten lassen - sieht soweit gut aus.
------------8<---------(snip)-------------------------------
Update: sessionAchieve hinzugefügt. Über properties kann bestimmt werden, ob die worker (threads) eine erstellte session beibehalten oder nach jedem Task recyclen und neu initialisieren.
Die Komponente deckt jetzt folgende Szenarien ab (exemplarisch):
Single-Threaded mit einer Session:
domino.threadpool.size=1
domino.threadpool.sessionAchieve=true
Single-Threaded mit jeweils neuer Session:
domino.threadpool.size=1
domino.threadpool.sessionAchieve=false
Multi-Threaded mit einer session / thread für alle tasks:
domino.threadpool.size=50
domino.threadpool.sessionAchieve=true
Multi-Threaded mit jeweils neuer session / thread für alle tasks:
domino.threadpool.size=50
domino.threadpool.sessionAchieve=false
Muss mich jetzt anderen Themen widmen. Denke in der kommenden Woche werde ich folgende Themen angehen:
- Fehlerbehandlung über uncheckedExceptions
- Logging (evtl. über SLF4J)
Habe kein Feedback mehr erhalten, daher bin ich mir unsicher, ob das Thema von Interesse ist.
Falls nicht, bitte diesen Thread schließen.
Grüße Thomas :-)