Vielen Dank für die Hinweise, scheinbar ist Notes so nicht in der Lage mit imported java umzugehen.
Einige Anwender bekommen auch die Fehlermeldung:
JVM: Attempt to retrieve Java agent attachments failed
Die jar für Openview ist 3.7MB groß, das ist bestimmt nicht gut, die im Agenten zu importieren.
Ich denke, ich muss die Openview-Logik auf dem Server zentralisieren und die Aktionen der Anwender dann asynchron verarbeiten. Wo sollte die große jar-Datei dann liegen, auf dem Server mit JavaUserClasses in der Notes.ini (bzw. Configuration-Dokument) oder geht es auch als Skriptlibrary in der Datenbank?
@Ralf: Wenn ich eine eigene VM für das Programm nehmen würde, wie würde ich das dann einbinden? Per Programmdokument kann ich das ja nicht ereignisgesteuert laufen lassen, oder? TriggerHappy von Damien Katz könnte ich als Trigger nehmen aber das erscheint mir irgendwie zu krumm...
Vielleicht habt ihr noch ein paar Ideen, wenn ich die Anforderung konkretisiere: Ich verwalte in der Notesdatenbank Empfangsbestätigungen für Hardware, die dann digital signiert werden. Der jeweilige Zustand der Bestätigung muss dann mit Openview synchronisiert werden.
hier noch mal ein paar Details zu meinem Recycling, vielleicht ist hier ja auch ein Wurm drin:
public void NotesMain() {
oli = new OpenLogItem();
try {
session = getSession();
AgentContext agentContext = session.getAgentContext();
DocumentCollection dc = agentContext.getUnprocessedDocuments();
Agent agent = agentContext.getCurrentAgent();
db = agentContext.getCurrentDatabase();
profileDoc = db.getProfileDocument("abc", "");
ov = new OVTool();
if (ov != null) {
...
Document doc = dc.getFirstDocument();
while (doc != null) {
doc = dc.getNextDocument();
}
ov.disconnect();
ov = null;
}
profileDoc.recycle();
db.recycle();
agent.recycle();
agentContext.recycle();
session.recycle();
}
catch (Exception e) {
oli.logError(e);
System.gc();
}
oli.recycle();
System.gc();
}
Und noch ov.disconnect() im Detail:
public void disconnect() {
oli.recycle();
s.closeConnection(); //beendet die Openview Session
s = null;
}
In meiner Iteration der DocumentCollection könnte ich noch ein tempDoc deklarieren und damit bei jedem Durchlauf das momentane doc recyclen (habe ich bei notes.net als aggressives oder so ähnlich Recycling gesehen aber der Sinn hat sich mir nicht erschlossen da das Recyclen der DocumentCollection doch eigentlich auch alle docs mitnehmen sollte, die dadrin sind, oder? Gleiches müsste dann natürlich auch für die db und die session gelten. Ist also alles nicht 100%ig verstanden von mir...
es ist immer schön, mal wieder was dazuzulernen :)
Die Recycling-Geschichte werde ich nochmal aufgreifen.
Zu oli: das ist das OpenLogItem (OpenLog von openntf.org), für mich gehört das schon selbstverständlicherweise in jede Datenbank rein. (Ich hatte es zum Testen mal komplett rausgelassen, hatte aber keinen Einfluss auf den cleaning up-Mist).
Das agent-Objekt nutze ich, um den Namen des Agents ins Log zu schreiben, ist natürlich Blödsinn, weil das Log sowieso den Namen automatisch logged...
Was sagst du dazu?
public void NotesMain() {
oli = new OpenLogItem();
try {
session = getSession();
AgentContext agentContext = session.getAgentContext();
DocumentCollection dc = agentContext.getUnprocessedDocuments();
db = agentContext.getCurrentDatabase();
profileDoc = db.getProfileDocument("setup", "");
ov = new OVTool();
if (ov != null) {
Document doc = dc.getFirstDocument();
Document temp = null;
while (doc != null) {
...
temp = dc.getNextDocument(doc); // get the next one
doc.recycle(); // recycle the one we're done with
doc = temp;
}
ov.disconnect();
ov = null;
}
profileDoc.recycle();
db = null;
agentContext = null;
session = null;
}
catch (Exception e) {
e.printStackTrace();
oli.logError(e);
}
oli.recycle();
}
ich versuche das mal mit Socket-Programmierung.
Ausgehend vom Webserver-Example aus Javabuch (4. Auflage, Kap. 45.3.3) erstelle ich eine simple Server-Anwendung
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
/**
* @param port
* the port which the server is listening to
*/
public static void main(String[] args) {
{
if (args.length != 1) {
System.err.println("Usage: java Server <port>");
System.exit(1);
}
try {
int port = Integer.parseInt(args[0]);
System.out.println("Listening to port " + port);
int calls = 0;
ServerSocket httpd = new ServerSocket(port);
while (true) {
Socket socket = httpd.accept();
(new ClientThread(++calls, socket)).start();
}
} catch (IOException e) {
System.err.println(e.toString());
System.exit(1);
}
}
}
}
die auf eingehende ClientThreads wartet:
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ClientThread extends Thread {
private Socket socket;
private int id;
private PrintStream out;
private InputStream in;
public ClientThread(int id, Socket socket) {
this.id = id;
this.socket = socket;
}
public void run() {
try {
System.out.println(id + ": Incoming call...");
out = new PrintStream(socket.getOutputStream());
in = socket.getInputStream();
readRequest();
createResponse();
socket.close();
System.out.println(id + ": Closed.");
} catch (IOException e) {
System.out.println(id + ": " + e.toString());
System.out.println(id + ": Aborted.");
}
}
private void createResponse() {
}
private void readRequest() throws IOException {
}
}
Nun würde ich in readRequest() Befehle parsen, mit denen ich Transaktionen in Openview ausführe. Die Ergebnisse der Transaktionen gebe ich dann per createResponse() wieder an den Caller zurück. Der Caller wäre in diesem Fall ein Notes Java-Agent.
Irgendwelche Vorschläge, wie man am besten Befehle parsed oder Objekte zwischen readRequest() und createResponse() austauscht?
Im Webserverbeispiel wird natürlich Text ausgelesen da ja normalerweise URLs geparsed werden. Ich könnte Klartextbefehle ('Create Servicecall', 'Axel Janssen', 'Netzwerkprobleme, kein Anschluß ans Netz') nehmen, aber ich möchte möglichst wenig selber parsen. Leider habe ich da kaum Erfahrungen, was in Java so gebräuchlich ist. Mit StringTokenizer oder Vector() oder so kann man das ja recht flexibel machen.
irgendwie bin ich zu blöd für die Socket-Geschichte...
Ich habe nun einen Server, der bei einem eingehenden Aufruf einen Clientthread startet, den ich von einem Notesagenten starte. Leider klappt der Ablauf Client schickt was, Server antwortet irgendwie nicht.
Im Clientthread lese ich den InputStream mit
StringBuffer sb = new StringBuffer(255);
int c;
while ((c = in.read()) != -1) {
sb.append((char) c);
}
Dies ist komischerweise eine Endlosschleife wenn ich vom Client aus dies sende:
String s = "argument1#argument2";
out.write(s.getBytes());
Wenn ich aber out.close() mache dann läuft mein ClientThread weiter ???
Zum parsen zerlege ich den StringBuffer.ToString mit dem StringTokenizer. Beim Prüfen der Werte .equals etc hänge ich auch noch fest >:D >:D >:D
Evt. fehlt
out.write('\r');
out.write('\n');
bei meinem Client?
euer Engagement ist großartig, ihr habt wahrscheinlich beide eine fertige Lösung bevor das bei mir läuft :-[
Im Prinzip nehme ich ja das KlopfKlopf-Protokoll, nur dass ich das nicht in einer extra Klasse ausgelagert habe. Ich möchte natürlich auch gerne verstehen, was ich tue. Und in allen Beispielen, die ich irgendwo gefunden habe, wird ein InputStream in einer while-Schleife ausgelesen:
while ((c = in.read()) != -1) {
sb.append((char) c);
}
Wieso ist das dann bei mir eine Endlos-Schleife? Sind alle Beispiele so stark vereinfacht? Oder läuft das nur, wenn die Gegenseite den dazugehörigen Outputstream mit einem Textprompt füllt und nicht programmatisch?
Natürlich läuft es bei mir, wenn ich auf der Gegenseite (im Notesagenten) outputStream.close() mache, aber das habe ich nirgendwo so gesehen.
Die Notesdokument-UID möchte ich übrigens nicht übergeben, da ich meine Mittelschicht gern unabhängig von Notes bauen möchte. D.h. der Server kennt bestimmte Befehle, die dazugehörige Anzahl von Parametern verlangen und gibt dann passende Antworten zurück. So kann ich das Tool dann auch von Nicht-Notesanwendungen nutzen.
und multithreaded brauch ich natürlich auch, sollte bereits drin sein. Ich muss aber noch die maximale Anzahl von parallelen Threads einbauen da Openview nicht beliebig viele Verbindungen mit dem gleichen User erlaubt.
Multithreading:
ServerSocket httpd = new ServerSocket(port);
while (true) {
Socket socket = httpd.accept();
(new ClientThread(getOVSession(ovserver, ovuser, ovpw), ++calls, socket)).start();
ich habe jetzt eine Schleife im ClientThread erstellt:
while ((inputLine = in.readLine()) != null) {
outputLine = ovp.processInput(inputLine);
out.println(outputLine);
if (outputLine.equals("bye"))
break;
}
In meinem Protocol liefere ich "bye" sobald kein bekannter Befehl gesendet wird
Mein Client liefert nun beliebig viele Befehle (aber ohne Schleife sondern sequentiell) :
ovSocket = new Socket("myhostname", 3000);
out = new PrintWriter(ovSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(ovSocket
.getInputStream()));
out.println("set_ackreceipt#Key1");
System.out.println(in.readLine());
out.println("set_ackreceipt#Key2");
System.out.println(in.readLine());
out.println("remove_ackreceipt#Key1");
System.out.println(in.readLine());
out.println("remove_ackreceipt#Key2");
System.out.println(in.readLine());
out.println("bye");
out.close();
in.close();
ovSocket.close();
Scheint zu klappen. Vorhin hatte ich ein paar Mal einen Socketabbruch (Software caused connection abort: recv failed) aber seit dem habe ich den Code schon wieder an einigen Stellen verändert und momentan läuft es stabil. Auch von Notes aus.
Hm. Du solltest diese Exception auf dem Server in eine RemoteException packen. Man kann ja seit 1.3 Exceptions chainen. Der Server ist Client von OpenView
try { } catch(TimeoutException te) {
throw new RemoteException ("OpenViewConnection failed", te); }
oder du machst eine neue Klasse OpenViewConnectionException extends RemoteException
so ungefähr.
Meine Idee mit dem Wrapper war Quatsch. Eine zentrale ConnectionFactory ist ok würd ich jetzt sagen.
meine Aufrufe nach Openview will ich nun in einer einzigen Session bündeln. Dafür erstelle ich eine Klasse, die bei jeder Verbindung genutzt werden soll, damit nie mehr als eine Session geöffnet ist. Daher wollte ich das als Singleton machen, aber irgendwie ist mir das wieder etwas verwässert. Haut das so hin? Erstmal der Code:
public class OV {
private static OV instance = null;
private ApiSDSession session;
private String srv;
private String usr;
private String pwd;
public OV(String srv, String usr, String pwd) {
this.srv = srv;
this.usr = usr;
this.pwd = pwd;
instance = this;
}
public static ApiSDSession getSession() {
try {
// check if session is usable
IBrandHome dummy = instance.session.getBrandHome();
return instance.session;
} catch (Exception e) {
// dummy not available that means no valid session
try {
instance.session = ApiSDSession.openSession(instance.srv,
instance.usr, instance.pwd);
return instance.session;
} catch (Exception ex) {
e.printStackTrace();
return null;
}
}
}
public static void disconnect() {
instance.session.closeConnection();
}
public static void connect() {
try {
instance.session = ApiSDSession.openSession(instance.srv,
instance.usr, instance.pwd);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Die Nutzung ist folgende: Wenn der Server hochfährt übergibt er die Anmeldedaten an diese Klasse:
OV ov = new OV(args[0], args[1], args[2]);
ohne hier mehr damit zu machen.
Bei jedem Zugriff auf Openview wird in meinem Protocol nun folgendes aufgerufen:
OV.connect();
Nutzung der session über OV.getSession()
OV.disconnect();
Ich erzeuge also im Server eine statische Instanz und nutze diese dann später über statische Aufrufe der Klasse. Macht man das so oder ist das irgendwie komisch? Beim Singleton holt man sich ja eigentlich eine Instanz, die innerhalb des Singletons immer das gleiche statische Objekt liefert!?
(hab noch mal den Konstruktor geändert...)
Wenn ich dich richtig verstehe, dann würde dies zur Folge haben, dass du multithreading vergessen kannst ???
Singleton implementierst du mit Java ungefähr so:
public class OV {
private static OV inst = null;
// constructors sind private.
private OV() {}
private OV(String srv, String usr, String pwd) {
this.srv = srv;
this.usr = usr;
this.pwd = pwd;
}
// hierüber bekommt man das Singleton.
public static OV getInst(String srv, String usr, String pwd) {
if (OV.inst = null) {
OV.inst = new OV(srv, usr, pwd);
}
return OV.inst;
}
}
Gibt noch ein paar gotchas mit multithreading und serialisierten Objekten, die z.B. in Joshua Blochs, Effective Java stehen.
Aber, warum?
Die Factory für die Session Objekte kann man vielleicht als Singleton erstellen. Aber von den Connections selber können doch wohl mehrere gleichzeitig aktiv sein, oder?
Btw. sind IOC frameworks (z.B. spring) wirklich viel besser als klassische Architekturen, die immer darauf hinauslaufen, dass es da diese ganzen Singleton-Factories gibt, die über den Source Code verstreut aufgerufen werden.
Und dann ändere mal den Construktor der Singleton-Factory... (ist mir oft passiert. macht keinen Spaß).
Aber beim ersten Aufruf sind diese Parameter notwendig.
Du kannst so was hinzufügen:
Es muß über die getInst mit Parametern initialisiert werden, kann aber ohne Parameter geholt werden.
public static OV getInst() throws InstantiationException {
if (OV.inst == null) {
throw new InstantiationException ("Der Singleton OW kann von aussen nur über den Aufruf Ov.getInst(String, String, String) initialisiert werden.")
}
return OV.inst;
}
Aber Singleton in Java ist wirklich so.
meine 'Serialisierung' mache ich durch aneinanderketten von Strings und einem Trennzeichen ('#' im Moment).
StringTokenizer splitter = new StringTokenizer(result, SEP);
Vector v = new Vector(splitter.countTokens());
while (splitter.hasMoreTokens()) {
v.addElement(splitter.nextToken());
}
mit v.elementAt(0) z.B. hole ich mir dann die Werte. Das ist aber irgendwie blöd, da bei null-Werten (also zwei ## hintereinander) der Tokenizer nix liefert und dann meine Anzahl von Elementen variiert. Kann Java keinen split() wie in Javascript oder Lotusscript? Ich finde irgendwie nix dazu pbwohl das doch sicher jeder ständig benutzt...
das ist doch genau das, was ich gesucht habe...
Leider hab ich natürlich vergessen, dass ich erst nächste Woche die Server auf Domino 7 upgrade und dass die Clients auf 6.0.3 bleiben :-(
error message: java.lang.NoSuchMethodError: java.lang.String: method split(Ljava/lang/String;)[Ljava/lang/String; not found
Mein Objekt Server läuft zwar in einer eigenen VM, aber die Clients (also Notesagenten) müssen ja die Antwort auswerten...
Nun nehme ich wohl dieses hier (Quelle: http://forum.java.sun.com/thread.jspa?threadID=646861&messageID=3808970
/**
* Split string into multiple strings
* @param original Original string
* @param separator Separator string in original string
* @return Splitted string array
*/
private String[] split(String original, String separator) {
Vector nodes = new Vector();
// Parse nodes into vector
int index = original.indexOf(separator);
while(index>=0) {
nodes.addElement( original.substring(0, index) );
original = original.substring(index+separator.length());
index = original.indexOf(separator);
}
// Get the last node
nodes.addElement( original );
// Create splitted string array
String[] result = new String[ nodes.size() ];
if( nodes.size()>0 ) {
for(int loop=0; loop<nodes.size(); loop++)
result[loop] = (String)nodes.elementAt(loop);
}
return result;
}
eigentlich läuft es nun. Aber schon im ersten Belastungstest komme ich an irgendwelche Socketgrenzen >:D
while (true) {
Socket socket = httpd.accept();
(new ClientThread(socket)).start();
}
nach etwa 6 mal Thread starten und wieder beenden bleibt er bei httpd.accept() endlos stehen und auch meine beiden Testclients warten nun endlos. Keine Fehlermeldung, aber auch keine neue Socketverbindung :'(
Gibts da ne Begrenzung inWindows oder in TCP oder im ServerSocket-Objekt?
Das geht natürlich auch Clientseitig.
Hier ist jedenfalls Beispielcode für httpClient:
Attachment in Zip umbenennen. Beinhaltet 4 Klassen.
verwendete jars:
commons-codec-1.3.jar
commons-httpclient-3.0.jar
commons-logging-jar
commons-logging-api.jar (?)
dom4j-1.6.1.jar
jaxen-1.1-beta-6.jar (im lib des dom4j downloads).
Einfach ExchangeRateService starten (hat eine main-Methode).
Falls du hinter einer Firewall sitzt, musst du noch das ReverseProxyBean initialisieren. In der main von ExchangeRateService die 2 neuen Zeilen einfügen:
public static void main (String args[]) {
ExchangeRateService exchRateService = new ExchangeRateService();
// neu
ReverseProxyBean revProxyBean = new ReverseProxyBean("host", 8080, "username", "password");
exchRateService.setReverseProxyBean(revProxyBean);
// neu ende
Dieser code sollte eine Zeile der aktuellen Referenzwechselkurse der europ. Zentr.bank zurückgegeben. (als Map wenn ich mich recht erinnere).
dom4j ist btw auch eine wirklich einfach zu benutzende xml api.
Das funktioniert noch nicht gegen einen Server der Authentifizierung verlangt. Das kommt morgen.
Gruß Axel
Hab auf die schnelle doch noch was gefunden.
Versuche vielleicht das hier:
Credentials defaultcreds = new UsernamePasswordCredentials(
usernameServer, passwordServer);
client.getState().setCredentials(AuthScope.ANY, defaultcreds);
in die Methode
buildRequestHeader() von ExchangeRateService zu packen (am Schluss. Ist aber egal.
empfohlene Schritte:
1. Eclipse Projekt aufsetzen
2. jars runterladen.
3. In Projekt Classpath die genannten jars (aus: commons-httpclient, commons-codec-1.3, commons-logging-1.0.4 sowie dom4j-1.6.1) importieren.
4. Das oben angehängte .jpg runterladen und in zip umbenennen.
5. Die 4 Dateien im zip in das src-Verzeichnis des Projektes importieren.
6. Gegebenfalls oben angesprochene Firewall-Änderung durchführen.
7. Testen d.h. ExchangeRateService starten.
8. An deine Gegebenenheiten anpassen (evtl. mit oben angesprochener Änderung mit Authentifzierung).
Ich hoffe das ist klar.
Der Teil ist btw. ein Beispiel für definitiv übles Errorhandling von mir:
org.w3c.dom.Document doc = db.parse(is);
return parseTodaysExchangeRateBeansFromXml (doc);
} catch (Exception e) {
String errMsg = "Die URL ist nicht erreichbar:" + urlEcbDaily;
throw new WebRetrievalException(errMsg, e);
}
Ich hab jetzt mal eine SocketServer Umgebung durchgecodet.
Client von CMD-Console und Server von Eclipse starten macht keine Probleme. Dh. läuft.
Morgen packe ich den Client in einen Notes Agenten.
Falls sich jemand dafür interessiert:
1. Das Error-Handling ist sicher nicht ausgereift.
2. Das Command Framework ist schlecht. Ich wollte es jetzt nicht direkt neu programmieren. Mach das irgendwann noch besser.
3. Was tun?
a) das attachte File in clientObjects.jar umbenennen und in ein Verzeichnis tun (z.B. C:\temp)
b) Die einzelnen Klassen des jars in ein Eclipse Projekt importieren.
c) In Eclipse die Klasse de.aja.server.SocketServer starten. (in clientObjects.jar sind sowohl die Client als auch die Server-Klassen)
d) in Windows (z.B.) clientObjects.jar von der Kommandozeile starten:
(man hat dann quasi 2 Java Programme 1 Client und 1 Server)
C:\TEMP>java -jar clientObjects.jar
cba
C:\TEMP>java -jar clientObjects.jar
cba
C:\TEMP>java -jar clientObjects.jar
cba
Man kann es mehrmals aufrufen.
Im Sourcecode steht, was passiert.
1. Der Client erzeugt ein Objekt.
2. Der Client schickt das Objekt als serialisierten Stream an den Server.
3. Der Server verarbeitet das Objekt weiter. (ruft Methode excecute auf).
4. Der Server sendet ein Antwort-Objekt an den Client zurück.
5. Der Client ruft eine Methode des empfangenen Objekts auf.
Wie gesagt: Marc-> Auf die Art kannst du (oder jemand anders) Objekte in Notes erzeugen, an den hier enthaltenen SocketServer schicken. Der SocketServer kann dann irgendwelche Klassen aufrufen (z.B. dieses openView Zeugs) und ein Response Objekt zurücksenden
... und das kann man in Notes wieder empfangen.
DAS IST ZWAR ALLES EIN BISCHEN SCHLUDRIG RUNTERGESCHRIEBEN.
ABER: Wär schön wenn sich jemand für interessieren würde. Ich hab die Idee verschiedene Remote-Aufrufe aus Notes in Java sauber durchzuprogrammieren und in Show-Code-on Thursday (oder wie immer diese Rocky Oliver Initiative heisst) publizieren.
ja sie liefert einen Fehler ;D Der Inhalt ist in etwa so:
Your session has timed out. Please click OK to continue
Das entspricht auch dem Verhalten im normalen Client von Openview Servicedesk. Dort erscheint dann auch ein Popup, wo man OK klickt und danach erneut Username und PW angeben kann.
Ich hatte probiert im Falle eines Fehlers eine neue Session zu erstellen, dies klappte aber nicht vernünftig:
try {
// check if session is usable
@SuppressWarnings("unused")
IBrandHome dummy = OV.session.getBrandHome();
return OV.session;
} catch (Exception e) {
// dummy not available that means no valid session
try {
System.out
.println("OVSD session not available, maybe timed out..."); //$NON-NLS-1$
OV.session.finalize();
OV.session = ApiSDSession.openSession(OV.SRV, OV.USR, OV.PWD);
System.out.println("Created new session " //$NON-NLS-1$
+ OV.session.getWorkflowSession());
return OV.session;
} catch (Exception ex) {
e.printStackTrace();
return null;
} catch (Throwable ex) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
Laut Log wurde hierdurch auch 1-2 Mal eine neue Session aufgebaut. Wenn ich aber einen Tag warte bekomme ich keine neue Session mehr hin mit diesem Code.
Ich habe mir das mal mit JAD angeschaut, die ganze Session-Verwaltuing ist recht komplex, mit zur Laufzeit kompilierten Proxies etc.