query and notification API
authorAleš Křenek <ljocha@ics.muni.cz>
Wed, 3 Jun 2009 07:21:01 +0000 (07:21 +0000)
committerAleš Křenek <ljocha@ics.muni.cz>
Wed, 3 Jun 2009 07:21:01 +0000 (07:21 +0000)
org.glite.lb.client-java/Makefile
org.glite.lb.client-java/nbproject/project.properties
org.glite.lb.client-java/src/org/glite/lb/ILProtoReceiver.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/Job.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/LBCredentials.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/LBException.java
org.glite.lb.client-java/src/org/glite/lb/NotifParser.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/Notification.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/NotificationExample.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/ServerConnection.java [new file with mode: 0644]
org.glite.lb.client-java/src/org/glite/lb/ServerConnectionExample.java [new file with mode: 0644]

index 69de860..da86271 100644 (file)
@@ -11,6 +11,7 @@ jglobus_jar := $(shell ls ${jglobus_prefix}/lib/cog-jglobus-*.jar | sort | tail
 all compile:
        JAVA_HOME=${jdk_prefix} \
        ${ant_prefix}/bin/ant -Dno.deps=yes -DstageDir=${stagedir} \
+               -Dfile.reference.commons-lang.jar=${commons-lang_jar} \
                -Dreference.jobid-api-java.jar=${stagedir}/share/java/jobid-api-java.jar \
                -Dreference.trustmanager.jar=${trustmanager_prefix}/share/java/glite-security-trustmanager.jar \
                -Daxis.classpath=`ls ${axis_prefix}/lib/*.jar | tr '\012' :` 
index feaebae..1ebbb91 100755 (executable)
@@ -21,6 +21,7 @@ excludes=
 includes=**
 jar.compress=false
 javac.classpath=\
+    ${file.reference.commons-lang.jar}:\
     ${axis.classpath}:\
     ${reference.jobid-api-java.jar}:\
     ${reference.trustmanager.jar}:\
diff --git a/org.glite.lb.client-java/src/org/glite/lb/ILProtoReceiver.java b/org.glite.lb.client-java/src/org/glite/lb/ILProtoReceiver.java
new file mode 100644 (file)
index 0000000..0c775d0
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.glite.lb;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * this class handles communication with LB server (reads messages it sends)
+ *
+ * @author Kopac
+ */
+public class ILProtoReceiver {
+
+    private Socket socket = null;
+    private InputStream inStream = null;
+    private OutputStream outStream = null;
+    private static final String magicWord = "6 michal";
+
+    /**
+     * construcor initializes the class' socket, inStream and outStream attributes
+     *
+     * @param socket an SSLSocket
+     * @throws java.io.IOException
+     */
+    public ILProtoReceiver(Socket socket) throws IOException {
+        this.socket = socket;
+        inStream = this.socket.getInputStream();
+        outStream = this.socket.getOutputStream();
+    }
+
+    /**
+     * this method reads from the inpuStream of the provided socket, checks for
+     * the magic word and returns relevant info
+     *
+     * @return a String containing third line of the inputStream data, without
+     * the info about its length
+     * @throws IOException
+     */
+    public String receiveMessage() throws IOException{
+        byte[] b = new byte[17];
+        int i = 0;
+        //read in and convert size of the message
+        if(inStream.read(b, 0, 17) == -1) {
+            return null;
+        } else {
+            String test = new String(b);
+            test.trim();
+            int length = Integer.parseInt(test);
+            byte[] notification = new byte[length];
+            //read in the rest of the message
+            int j = 0;
+            while(i != length || j == -1) {
+                j = inStream.read(notification, i, length);
+                i=i+j;
+            }
+            String retString = checkWord(notification);
+            if(retString == null) return null;
+            else
+            //return
+            return retString.split(" ", 2)[1];
+        }
+    }
+
+    /**
+     * private method that checks, if the magic word is present in the notification
+     *
+     * @param notification a notification without the line specifying its length
+     * @return null, if the word is not there, the last line of the notification,
+     * again without its length specification, otherwise
+     */
+    private String checkWord(byte[] notification) {
+        int i = 0;
+        while(notification[i] != '\n') {
+            i++;
+        }
+        String word = new String(notification, 0, i+1);
+        word.trim();
+        if(!word.equals(magicWord)) {
+            return null;
+        } else {
+            return new String(notification, i+1, notification.length - i + 1);
+        }
+    }
+
+    /**
+     * this method encodes and sends a reply to the interlogger via the socket's
+     * outputStream
+     *
+     * @param errCode errCode of the calling
+     * @param minErrCode minimum available errcode
+     * @param message message for the interlogger - could be any String
+     * @throws IOException
+     */
+    public void sendReply(int errCode, int minErrCode, String message) throws IOException {
+        byte[] errByte = (new String()+errCode).getBytes();
+        byte[] minErrByte = (new String()+minErrCode).getBytes();
+        byte[] msgByte = message.getBytes();
+        int length = errByte.length + minErrByte.length + msgByte.length;
+        byte[] lengthByte = (new String()+length).getBytes();
+        int numberOfSpaces = 17 - lengthByte.length;
+        byte[] returnByte = new byte[length+17];
+        int i = 0;
+        while(i < numberOfSpaces-1) {
+            returnByte[i] = ' ';
+            i++;
+        }
+        returnByte = putByte(returnByte, lengthByte, numberOfSpaces);
+        returnByte[16] = '\n';
+        returnByte = putByte(returnByte, errByte, 17);
+        returnByte = putByte(returnByte, minErrByte, 16 + errByte.length);
+        returnByte = putByte(returnByte, msgByte, 16 + errByte.length + minErrByte.length);
+        outStream.write(returnByte);
+    }
+
+    /**
+     * appends a byte array to the end of an existing byte array
+     *
+     * @param arrayToFill array to be filled
+     * @param filler array to be appended
+     * @param start starting offset of the first array, from which the second
+     * array should be appended
+     * @return the resulting byte array
+     */
+    private byte[] putByte(byte[] arrayToFill, byte[] filler, int start) {
+        for(int i = start; i < filler.length + start; i++) {
+            int j = 0;
+            arrayToFill[i] = filler[j];
+            j++;
+        }
+        return arrayToFill;
+    }
+}
diff --git a/org.glite.lb.client-java/src/org/glite/lb/Job.java b/org.glite.lb.client-java/src/org/glite/lb/Job.java
new file mode 100644 (file)
index 0000000..e33365c
--- /dev/null
@@ -0,0 +1,168 @@
+
+package org.glite.lb;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.glite.jobid.Jobid;
+import org.glite.lb.Event;
+import org.glite.wsdl.types.lb.JobFlagsValue;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.glite.wsdl.types.lb.QueryAttr;
+import org.glite.wsdl.types.lb.QueryConditions;
+import org.glite.wsdl.types.lb.QueryOp;
+import org.glite.wsdl.types.lb.QueryRecValue;
+import org.glite.wsdl.types.lb.QueryRecord;
+
+/**
+ * Class encapsulating the job info stored in the L&B database.
+ *
+ * This class is the primary interface for getting information about
+ * job stored in the L&B database. It is constructed from known job
+ * id, which uniquely identifies the job as well as the bookkeeping
+ * server where the job data is stored. The Job class provides methods
+ * for obtaining the data from the bookkeeping server.
+ *
+ * @author Tomas Kramec, 207545@mail.muni.cz
+ */
+public class Job {
+    
+    private Jobid jobId;
+    private ServerConnection serverConnection;
+
+       /** 
+        * Constructor initializes the job as empty, not representing anything.
+        */
+       public  Job() {
+    }
+       
+       /** 
+     * Constructor from job id and bookkeeping server connection.
+        * Initializes the job to obtain information for the given job id.
+     *
+        * @param jobId The job id of the job this object wil represent.
+     * @param serverConnection ServerConnection object providing methods
+     *        for obtaining the data from the bookkeeping server.
+        */
+       public  Job(Jobid jobId, ServerConnection serverConnection) {
+        if (jobId == null) throw new IllegalArgumentException("jobId cannot be null");
+        if (serverConnection == null) throw new IllegalArgumentException("server cannot be null");
+        
+        this.jobId = jobId;
+        this.serverConnection = serverConnection;
+    }
+
+    /**
+     * Constructor from job id and bookkeeping server connection.
+        * Initializes the job to obtain information for the given job id.
+     *
+        * @param jobId The job id of the job this object wil represent.
+     * @param serverConnection ServerConnection object providing methods
+     *        for obtaining the data from the bookkeeping server.
+        */
+       public  Job(String jobId, ServerConnection serverConnection) {
+        this(new Jobid(jobId), serverConnection);
+    }
+    
+    /**
+     * Gets this job ID.
+     *
+     * @return jobId
+     */
+    public Jobid getJobId() {
+        return jobId;
+    }
+
+    /**
+     * Sets the jobId to this job.
+     *
+     * @param jobId
+     */
+    public void setJobId(Jobid jobId) {
+        if (jobId == null) throw new IllegalArgumentException("jobId");
+
+        this.jobId = jobId;
+    }
+
+    /**
+     * Gets server connection.
+     *
+     * @return serverConnection
+     */
+    public ServerConnection getServer() {
+        return serverConnection;
+    }
+
+    /**
+     * Sets server connection instance to this job.
+     * 
+     * @param serverConnection
+     */
+    public void setServer(ServerConnection serverConnection) {
+        if (serverConnection == null) throw new IllegalArgumentException("server");
+
+        this.serverConnection = serverConnection;
+    }
+
+
+    /**
+     * Return job status.
+        * Obtains the job status (as JobStatus) from the bookkeeping server.
+        * 
+     * @param flags Specify details of the query. Determines which status
+     * fields are actually retrieved.
+     * Possible values:<ol>
+     * <li type="disc">CLASSADS - Include the job description in the query result.</li>
+     * <li type="disc">CHILDREN - Include the list of subjob id's in the query result.</li>
+     * <li type="disc">CHILDSTAT - Apply the flags recursively to subjobs.</li>
+     * <li type="disc">CHILDHIST_FAST - Include partially complete histogram of child job states.</li>
+     * <li type="disc">CHILDHIST_THOROUGH - Include full and up-to date histogram of child job states.</li>
+     * </ol>
+     *
+        * @return Status of the job.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public JobStatus getStatus(JobFlagsValue[] flags) throws LBException {
+        if (serverConnection == null)
+            throw new IllegalStateException("serverConnection is null, please set it");
+        if (jobId == null)
+            throw new IllegalStateException("jobId is null, please set it");
+        try {
+            return serverConnection.jobState(jobId.toString(), flags);
+        } catch (LBException ex) {
+            throw new LBException(ex);
+        }
+    }
+  
+       /** 
+     * Return all events corresponding to this job.
+        * Obtains all events corresponding to the job that are stored
+        * in the bookkeeping server database.
+     * <p>
+     * Default value for logging level is SYSTEM. If needed, it can be changed
+     * by calling <b>setEventLoggingLevel(Level loggingLevel)</b> method
+     * on the serverConnection attribute of this class.
+        * </p>
+     * 
+     * @return events List of events (of type Event).
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<Event> getEvents() throws LBException {
+        if (serverConnection == null)
+            throw new IllegalStateException("serverConnection is null, please set it");
+        if (jobId == null)
+            throw new IllegalStateException("jobId is null, please set it");
+
+        QueryRecValue jobIdValue = new QueryRecValue(null, jobId.toString(), null);
+        QueryRecord[] jobIdRec = new QueryRecord[] {new QueryRecord(QueryOp.EQUAL, jobIdValue, null)};
+        QueryConditions[] query = new QueryConditions[]{new QueryConditions(QueryAttr.JOBID, null, null, jobIdRec)};
+        QueryRecValue levelValue = new QueryRecValue(serverConnection.getEventLoggingLevel().getInt()+1, null, null);
+        QueryRecord[] levelRec = new QueryRecord[] {new QueryRecord(QueryOp.LESS, levelValue, null)};
+        QueryConditions[] queryEvent = new QueryConditions[]{new QueryConditions(QueryAttr.LEVEL, null, null, levelRec)};
+        List<QueryConditions> queryList = new ArrayList<QueryConditions>();
+        queryList.add(query[0]);
+        List<QueryConditions> queryEventList = new ArrayList<QueryConditions>();
+        queryEventList.add(queryEvent[0]);
+
+        return serverConnection.queryEvents(queryList, queryEventList);
+    }
+}
diff --git a/org.glite.lb.client-java/src/org/glite/lb/LBCredentials.java b/org.glite.lb.client-java/src/org/glite/lb/LBCredentials.java
new file mode 100644 (file)
index 0000000..448365b
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.glite.lb;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.xml.rpc.ServiceException;
+import org.apache.axis.AxisProperties;
+import org.glite.wsdl.services.lb.LoggingAndBookkeepingLocator;
+import org.glite.wsdl.services.lb.LoggingAndBookkeepingPortType;
+
+/**
+ * 
+ */
+public class LBCredentials {
+
+    public LBCredentials(String proxy, String caFiles) {
+        if (proxy == null) throw new IllegalArgumentException("Proxy cannot be null");
+        if (caFiles == null) throw new IllegalArgumentException("caFiles cannot be null");
+
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.CREDENTIALS_PROXY_FILE, proxy);
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.CA_FILES, caFiles);
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.SSL_PROTOCOL, "SSLv3");
+        AxisProperties.setProperty("axis.socketSecureFactory","org.glite.security.trustmanager.axis.AXISSocketFactory");
+    }
+
+    public LBCredentials(String userCert, String userKey, String userPass, String caFiles) {
+        if (userCert==null || userKey==null || userPass==null || caFiles==null)
+            throw new IllegalArgumentException("One of the parameters was null");
+
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.CREDENTIALS_CERT_FILE,userCert);
+               System.setProperty(org.glite.security.trustmanager.ContextWrapper.CREDENTIALS_KEY_FILE,userKey);
+               System.setProperty(org.glite.security.trustmanager.ContextWrapper.CREDENTIALS_KEY_PASSWD,userPass);
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.CA_FILES, caFiles);
+        System.setProperty(org.glite.security.trustmanager.ContextWrapper.SSL_PROTOCOL, "SSLv3");
+        AxisProperties.setProperty("axis.socketSecureFactory","org.glite.security.trustmanager.axis.AXISSocketFactory");
+    }
+
+    protected LoggingAndBookkeepingPortType getStub(String server) throws LBException {
+        if (server == null)
+            throw new IllegalArgumentException("Server cannot be null");
+        try {
+            URL queryServerAddress = new URL(server);
+            int port = queryServerAddress.getPort();
+            if (port < 1 || port > 65535) {
+                throw new IllegalArgumentException("port");
+            }
+            if (!queryServerAddress.getProtocol().equals("https")) {
+                throw new IllegalArgumentException("wrong protocol");
+            }
+            LoggingAndBookkeepingLocator loc = new LoggingAndBookkeepingLocator();
+            return loc.getLoggingAndBookkeeping(queryServerAddress);
+        } catch (ServiceException ex) {
+            throw new LBException(ex);
+        } catch (MalformedURLException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+}
index 62786aa..00ef3fb 100644 (file)
@@ -1,7 +1,9 @@
 package org.glite.lb;
 
 public class LBException extends Exception {
-       public LBException(Throwable e) {
+       
+    public LBException(Throwable e) {
                super(e);
-       } 
+       }
+    
 }
diff --git a/org.glite.lb.client-java/src/org/glite/lb/NotifParser.java b/org.glite.lb.client-java/src/org/glite/lb/NotifParser.java
new file mode 100644 (file)
index 0000000..e18050f
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.glite.lb;
+
+import java.io.IOException;
+import java.io.StringReader;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * this class parses the received message into a readable format
+ *
+ * @author Kopac
+ */
+public class NotifParser {
+
+    Document doc = null;
+    String header = null;
+
+
+    /**
+     * constructor takes a notification in String format and parses it into a String
+     * containing the header and an XML doc
+     *
+     * @param notif the string with notification in it
+     * @throws javax.xml.parsers.ParserConfigurationException
+     * @throws org.xml.sax.SAXException
+     * @throws java.io.IOException
+     */
+    public NotifParser(String notif) throws ParserConfigurationException, SAXException, IOException {
+        String[] splitString = notif.split("DG.NOTIFICATION.JOBSTAT=\"", 2);
+        header = splitString[0];
+        doc = createXML(splitString[1]);
+    }
+
+    /**
+     * this method reads through an XML document using XPath expressions and
+     * fills an instance of JobStatus, which it then returns
+     * this is done using automatically generated code
+     *
+     * @param notification an array of bytes containing the raw notification
+     * @return a Jobstatus instance
+     */
+    public JobStatus getJobInfo()
+        throws ParserConfigurationException, SAXException, IOException {
+        JobStatus status = new JobStatus();
+        //TODO: insert automated code creation
+        status.setJobId(evaluateXPath("//jobId").item(0).getTextContent());
+        status.setOwner(evaluateXPath("//owner").item(0).getTextContent());
+        return status;
+    }
+
+     /**
+     * this method returns id of the notification
+     *
+     * @return notif id
+     */
+    public String getNotifId() {
+        String halfHeader = header.split("DG.NOTIFICATION.NOTIFID=\"")[1];
+        return halfHeader.substring(0, halfHeader.indexOf("\""));
+    }
+
+    /**
+     * a method for handling xpath queries
+     *
+     * @param xpathString xpath expression
+     * @return the result nodelist
+     */
+    private NodeList evaluateXPath(String xpathString) {
+        try {
+            XPathFactory xfactory = XPathFactory.newInstance();
+            XPath xpath = xfactory.newXPath();
+            XPathExpression expr = xpath.compile(xpathString);
+            return (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+        } catch (XPathExpressionException ex) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * this method creates an XML document out of a provided String
+     * note that the string has to be a valid escaped XML document representation,
+     * otherwise the process will fail
+     *
+     * @param body a String containing an XML document
+     * @return an XML document in the Document format
+     * @throws javax.xml.parsers.ParserConfigurationException
+     * @throws org.xml.sax.SAXException
+     * @throws java.io.IOException
+     */
+    private Document createXML(String body) throws ParserConfigurationException, SAXException, IOException {
+        String removed = body.substring(0, body.length()-1);
+        String parsed = StringEscapeUtils.unescapeXml(removed);
+        //this code removes hexadecimal references from the string (couldn't find
+        //a suitable parser for this)
+        StringBuilder build = new StringBuilder(parsed);
+        int j = build.indexOf("%");
+        while(j > 0) {
+            if(build.charAt(j-1) == '>') {
+                build.delete(j, j+3);
+                if(build.charAt(j) == '<') {
+                    j = build.indexOf("%");
+                }
+            } else {
+                j = build.indexOf("%", j+1);
+            }
+        }
+        parsed = build.toString();
+        //ends here
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        return factory.newDocumentBuilder().parse(new InputSource(new StringReader(parsed)));
+    }
+}
diff --git a/org.glite.lb.client-java/src/org/glite/lb/Notification.java b/org.glite.lb.client-java/src/org/glite/lb/Notification.java
new file mode 100644 (file)
index 0000000..e25ae3e
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.glite.lb;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.rmi.RemoteException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Calendar;
+import java.util.Date;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.rpc.holders.CalendarHolder;
+import org.glite.jobid.Jobid;
+import org.glite.lb.SSL;
+import org.glite.wsdl.services.lb.LoggingAndBookkeepingPortType;
+import org.glite.wsdl.types.lb.JobFlagsValue;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.glite.wsdl.types.lb.QueryConditions;
+import org.xml.sax.SAXException;
+
+/**
+ * This class handles all communication comming from the client toward the server.
+ * it uses methods generated from .wsdl description files.
+ * note that each instance of this class is dedicated to a single port. Different
+ * port means a new instance has to be created
+ *
+ * @author Kopac
+ */
+public class Notification {
+
+    private int port = 0;
+    private Socket socket = null;
+    private static String keyStore;
+    private String notifId;
+    private LoggingAndBookkeepingPortType stub;
+    private LBCredentials lbCredent;
+
+    /**
+     * constructor sets the local port number
+     *
+     * @param port number of the local port the notification is bound to
+     * @param lbCredent instance of class that handles SSL authentication
+     */
+    public Notification(int port, LBCredentials lbCredent) {
+        this.port = port;
+        this.lbCredent = lbCredent;
+    }
+
+    /**
+     * a private method used to create a unique ID for a new notification
+     *
+     * @param host hostname
+     * @return String containing the unique ID
+     */
+    private String makeId(String host) {
+        StringBuilder returnString = new StringBuilder();
+        returnString.append(host);
+        Jobid jobid = new Jobid(returnString.toString(), port);
+        returnString.append("/NOTIF:");
+        returnString.append(jobid.getUnique());
+        return returnString.toString();
+    }
+
+    /**
+     * returns ID of the latest received notification
+     *
+     * @return notifID
+     */
+    public String getNotifid() {
+        return notifId;
+    }
+
+    /**
+     * private method used to recover LB server address from a notif ID
+     *
+     * @param notifId notif ID
+     * @return server address
+     */
+    private String getServer(String notifId) {
+        StringBuilder ret = new StringBuilder(notifId.split("/")[2]);
+        char[] ch = new char[]{ret.charAt(ret.length()-1)};
+        int i = Integer.parseInt(new String(ch)) + 3;
+        ret.replace(ret.length()-1, ret.length()-1, new String()+i);
+        ret.insert(0, "https://");
+        return ret.toString();
+    }
+
+    /**
+     * this method sends a request to the server, to create a new notification.
+     * it's not necessary to provide all the options for this calling, thus
+     * some of the parameters can be null. in that case, the option they correspond to
+     * is not used.
+     *
+     * @param server a String containing the server address (e.g. https://harad.ics.muni.cz:9553).
+     * can't be null
+     * @param conditions an array of QueryConditions, may contain all the possible
+     * conditions for the new notification. can't be null
+     * @param flags an array of JobFlagsValue, may contain all the possible flags
+     * and their values for the new notification.
+     * @param date a Date containing the desired time, the notification will be valid for
+     * note that this option can only be used to shorten the validity span, as the server
+     * checks it and sets the final expiration date to a constant max, should
+     * the provided Date exceed it. may be null
+     * @return a Date holding info on how long the new notification will
+     * be valid for
+     * @throws LBException
+     */
+    public Date newNotif(String server, QueryConditions[] conditions, JobFlagsValue[] flags, Date date) throws LBException {
+        try {
+            CalendarHolder calendarHolder = new CalendarHolder(Calendar.getInstance());
+            if (date != null) {
+                calendarHolder.value.setTime(date);
+            } else {
+                calendarHolder.value.setTime(new Date(System.currentTimeMillis() + 86400000));
+            }
+            stub = lbCredent.getStub(server);
+            String addr = "0.0.0.0:" + port;
+            String id = makeId(server);
+            stub.notifNew(id, addr, conditions, flags, calendarHolder);
+            notifId = id;
+            return calendarHolder.value.getTime();
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+    /**
+     * this method drops an existing notification, removing it completely
+     *
+     * @param notifId id of the notification to be dropped
+     * @throws LBException
+     */
+    public void drop(String notifId) throws LBException {
+        try {
+            stub = lbCredent.getStub(getServer(notifId));
+            stub.notifDrop(notifId);
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+    /**
+     * this method is used to extend the validity of an existing notification
+     *
+     * @param notifId id of the notification to be refreshed
+     * @param date information about the desired validity duration of the notification
+     * in Date format. may be null (in this case, the maximum possible duration is used).
+     * @throws LBException
+     */
+    public void refresh(String notifId, Date date) throws LBException {
+        try {
+            stub = lbCredent.getStub(getServer(notifId));
+            CalendarHolder holder = new CalendarHolder(Calendar.getInstance());
+            if (date != null) {
+                holder.value.setTime(date);
+            } else {
+                holder.value.setTime(new Date(System.currentTimeMillis() + 86400000));
+            }
+            stub.notifRefresh(notifId, holder);
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+    /**
+     * this method is used to bind an existing notification to a different local port
+     * than previously declared
+     *
+     * @param notifId id of th notification
+     * @param date optional attribute, can be used to refresh the notification
+     * @return length of the validity duration of the notification in Date format
+     * @throws LBException
+     */
+    public Date bind(String notifId, Date date) throws LBException {
+        try {
+            stub = lbCredent.getStub(getServer(notifId));
+            String host = InetAddress.getLocalHost().getHostName() + ":" + port;
+            CalendarHolder holder = new CalendarHolder(Calendar.getInstance());
+            if (date != null) {
+                holder.value.setTime(date);
+            } else {
+                holder.value.setTime(new Date(System.currentTimeMillis() + 86400000));
+            }
+            stub.notifBind(notifId, host, holder);
+            return holder.value.getTime();
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        } catch (UnknownHostException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+    /**
+     * this method is used to tell the client to start listening for incomming
+     * connections on the local port, with the specified timeout
+     *
+     * @param timeout read timeout
+     * @return comprehensible information, pulled from the received message
+     * @throws LBException
+     */
+    public JobStatus receive(int timeout) throws LBException {
+        SSL ssl = new SSL();
+        ssl.setProxy(keyStore);
+        ILProtoReceiver receiver = null;
+        String received = null;
+        try {
+            if(socket == null) {
+                socket = ssl.accept(port, timeout);
+            }
+            receiver = new ILProtoReceiver(socket);
+            if((received = receiver.receiveMessage()) == null) {
+                socket = ssl.accept(port, timeout);
+                receiver = new ILProtoReceiver(socket);
+                received = receiver.receiveMessage();
+            }
+            receiver.sendReply(0, 0, "success");
+            NotifParser parser = new NotifParser(received);
+            notifId = parser.getNotifId();
+            return parser.getJobInfo();
+        } catch (IOException ex) {
+            throw new LBException(ex);
+        } catch (KeyManagementException ex) {
+            throw new LBException(ex);
+        } catch (KeyStoreException ex) {
+            throw new LBException(ex);
+        } catch (NoSuchAlgorithmException ex) {
+            throw new LBException(ex);
+        } catch (ParserConfigurationException ex) {
+            throw new LBException(ex);
+        } catch (SAXException ex) {
+            throw new LBException(ex);
+        }
+    }
+}
diff --git a/org.glite.lb.client-java/src/org/glite/lb/NotificationExample.java b/org.glite.lb.client-java/src/org/glite/lb/NotificationExample.java
new file mode 100644 (file)
index 0000000..6109684
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.glite.lb;
+
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.glite.wsdl.types.lb.QueryAttr;
+import org.glite.wsdl.types.lb.QueryConditions;
+import org.glite.wsdl.types.lb.QueryOp;
+import org.glite.wsdl.types.lb.QueryRecValue;
+import org.glite.wsdl.types.lb.QueryRecord;
+import org.glite.wsdl.types.lb.StatName;
+
+/**
+ * This is an example on how to use the package org.glite.lb.notif_java
+ *
+ *
+ * @author Kopac
+ */
+public class NotificationExample {
+
+    /**
+     * example method on how to use this package
+     *
+     * @param args parameters for the methods, order as follows:
+     * <p>
+     * 1. path to user's certificate
+     * <p>
+     * 2. path to a list of trusted certificates
+     * <p>
+     * 3. owner ID
+     * <p>
+     * 4. port number
+     * <p>
+     * 5. LB server address
+     */
+    public static void main(String[] args){
+        try {
+            
+            //creation of an instance of class that prepares SSL connection for 
+            //webservice, first of the two possibilities is used
+            LBCredentials credentials = new LBCredentials(args[0], args[1]);
+
+            //creation of QueryConditions instance. this one tells the notification to
+            //be associated with jobs of an owner and that don't have a tag. meh
+            QueryRecValue value1 = new QueryRecValue(null, args[2], null);
+            QueryRecord[] record = new QueryRecord[]{new QueryRecord(QueryOp.EQUAL, value1, null)};
+            QueryConditions[] conditions = new QueryConditions[]{new
+                    QueryConditions(QueryAttr.OWNER, null, StatName.SUBMITTED, record)};
+
+            //creating an instance of NotificationImpl, the argument is port number
+            Notification notif = new Notification(Integer.parseInt(args[3]), credentials);
+
+            Date date = new Date(System.currentTimeMillis()+86400000);
+
+            //registering a new notification, first parameter is LB server address in String,
+            //second previously created QueryConditions, there are no JobFlags provided
+            //and the last one tells the server to keep it active for two days
+            notif.newNotif(args[4], conditions, null, date);
+
+            //id of the newly created notification
+            String notifId = notif.getNotifid();
+
+            //refreshing the previously created notification by two days
+            notif.refresh(notifId, date);
+
+            //tells the client to read incomming notifications with a timeout of 1 minute
+            JobStatus state = notif.receive(60000);
+            //from here on, state can be used to pick the desired info using its
+            //get methods
+
+            //like this
+            System.out.println(state.getAcl());
+            System.out.println(state.getCancelReason());
+            System.out.println(state.getJobId());
+
+            //creates a new instance of NotificationImpl with the port 14342 and
+            //binds the notification to this new instance, meaning to a new local
+            //address
+            Notification notif2 = new Notification(14342, credentials);
+            notif2.bind(notifId, date);
+
+            //lastly, the notification is dropped
+            notif2.drop(notifId);
+
+        } catch (LBException ex) {
+            Logger.getLogger(NotificationExample.class.getName()).log(Level.SEVERE, null, ex);
+        }
+    }
+}
diff --git a/org.glite.lb.client-java/src/org/glite/lb/ServerConnection.java b/org.glite.lb.client-java/src/org/glite/lb/ServerConnection.java
new file mode 100644 (file)
index 0000000..2eaadf8
--- /dev/null
@@ -0,0 +1,521 @@
+
+package org.glite.lb;
+
+import holders.StringArrayHolder;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.axis.client.Stub;
+import org.apache.log4j.PropertyConfigurator;
+import org.glite.jobid.Jobid;
+import org.glite.lb.Event;
+import org.glite.lb.Level;
+import org.glite.wsdl.services.lb.LoggingAndBookkeepingPortType;
+import org.glite.wsdl.types.lb.JobFlagsValue;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.glite.wsdl.types.lb.QueryConditions;
+import org.glite.wsdl.types.lb.holders.JobStatusArrayHolder;
+
+/**
+ * This class serves for obtaining data from the bookkeeping
+ * server.
+ * The L&B service queries come in two flavors:
+ * <ol><li type="disc"> conjunctive query, as in <tt>(cond1) or (cond2)</tt></li>
+ * <li type="disc"> conjunction of disjunctive queries, as in <tt>( (cond1)
+ * or (cond2) ) and ( (cond3) or (cond4) )</tt></li></ol>
+ * Methods for both query flavors are provided.
+ * Query methods actually do communicate with the server and
+ * they are synchronous; their completion can take some time
+ * not exceeding the query timeout.
+ * 
+ * @author Tomas Kramec, 207545@mail.muni.cz
+ */
+public class ServerConnection {
+
+    private String queryServerAddress;
+    private int queryJobsLimit=0;
+    private int queryEventsLimit=0;
+    private QueryResultsFlag queryResultsFlag = QueryResultsFlag.NONE;
+    private Level eventLogLevel = Level.LEVEL_SYSTEM;
+    private LoggingAndBookkeepingPortType queryServer;
+    private LBCredentials lbCredent;
+
+
+    /**
+     * This enum represents flag to indicate handling of too large results.
+     * In case the result limit is reached:
+     * <ol>
+     * <li><b>NONE</b> - means no results are returned at all.</li>
+     * <li><b>LIMITED</b> - means a result contains at most <i>limit</i> items.</li>
+     * <li><b>ALL</b> - means all results are returned withouth any limitation.</li>
+     * </ol>
+     */
+    public enum QueryResultsFlag {
+        /**
+         * No results are returned at all.
+         */
+        NONE,
+        /**
+         * Result contains at most <i>limit</i> items. Limits for job and event
+         * results can be set by calling appropriate method on the ServerConnection
+         * instance.
+         */
+        LIMITED,
+        /**
+         * Results are returned withouth any limitation.
+         */
+        ALL
+    }
+
+    /**
+        * Constructor initializes the context and
+     * directs new instance to query the given bookkeping server.
+     *
+     * @param server String containing the server address (e.g. https://harad.ics.muni.cz:9453).
+     * @param lbCredent instance of class that holds credentials for SSL authentication
+        */
+       public ServerConnection(String server, LBCredentials lbCredent) throws LBException {
+        if (server == null) throw new IllegalArgumentException("Server cannot be null");
+        if (lbCredent == null) throw new IllegalArgumentException("Credentials cannot be null");
+
+        PropertyConfigurator.configure("log4j.properties");
+        this.lbCredent = lbCredent;
+        setQueryServerAddress(server);
+        setQueryTimeout(120);
+    }
+
+    /** 
+        * Set bookkeeping server.
+        * Directs the instance to query the given bookkeping server.
+     *
+        * @param server String containing the server address (e.g. https://harad.ics.muni.cz:9553).
+        */
+       public void setQueryServerAddress(String server) throws LBException {
+        if (server == null) throw new IllegalArgumentException("Server cannot be null");
+
+        queryServer = lbCredent.getStub(server);
+        queryServerAddress = server;
+        
+    }
+
+    /**
+     * Get address of the bookkeeping server.
+        * Returns address of the bookkeeping server this instance is
+        * bound to.
+     *
+        * @return Address (https://hostname:port).
+        */
+    public String getQueryServerAddress() {
+        return queryServerAddress;
+    }
+
+    /**
+     * Set query timeout.
+        * Sets the time interval to wait for server response.
+     * Default value is 120 seconds.
+     *
+        * @param time Time in seconds before the query expires. 0 means no timeout.
+        */
+    public void setQueryTimeout(int time) {
+        if (time < 0 || time > 1800)
+            throw new IllegalArgumentException("timeout must be between 0 and 1800");
+
+        ((Stub) queryServer).setTimeout(time*1000);
+
+    }
+
+    /**
+     * Get query timeout.
+        * Returns the time interval this instance waits for server
+        * response.
+     *
+        * @return Number of seconds to wait. 0 means no timeout.
+        */
+       public int getQueryTimeout() {
+        return ((Stub) queryServer).getTimeout()/1000;
+    }
+
+    /**
+     * Set logging level.
+     * Sets the level for logging the events.
+     * Possible values:<ol>
+     * <li type="disc">EMERGENCY</li>
+     * <li type="disc">ALERT</li>
+     * <li type="disc">ERROR</li>
+     * <li type="disc">WARNING</li>
+     * <li type="disc">AUTH</li>
+     * <li type="disc">SECURITY</li>
+     * <li type="disc">USAGE</li>
+     * <li type="disc">SYSTEM</li>
+     * <li type="disc">IMPORTANT</li>
+     * <li type="disc">DEBUG </li>
+     * </ol>
+     * Default value is SYSTEM.
+     * @param loggingLevel level for logging the events
+     */
+    public void setEventLoggingLevel(Level loggingLevel) {
+        if (loggingLevel == null) throw new IllegalArgumentException("loggingLevel");
+
+        this.eventLogLevel = loggingLevel;
+    }
+
+    /**
+     * Get logging level.
+     * Returns the level for logging the events.
+     *
+     * @return level value
+     */
+    public Level getEventLoggingLevel() {
+        return eventLogLevel;
+    }
+
+       /**
+     * Retrieve the set of single indexed attributes.
+        * Returns the set of attributes that are indexed on the
+        * server. Every query must contain at least one indexed
+        * attribute for performance reason; exception to this rule
+        * requires setting appropriate paramater on the server and is
+        * not advised.
+        *<br/>
+        * In the list returned, each element represents a query condition.
+     * Query attribute of the condition corresponds to the indexed attribute.
+        * If the query attribute is USERTAG, tagName is its name;
+        * if it is TIME, statName is state name.
+     *
+     * @return list of QueryConditions
+     * @throws LBException If some communication or server problem occurs.
+        */
+    public List<QueryConditions> getIndexedAttrs() throws LBException {
+        try {
+            QueryConditions[] cond = queryServer.getIndexedAttrs("");
+            List<QueryConditions> indexedAttrs = new ArrayList<QueryConditions>();
+            for (int i = 0; i < cond.length; i++) {
+                indexedAttrs.add(cond[i]);
+            }
+            return indexedAttrs;
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+       /**
+     * Retrieve hard result set size limit. This
+     * property is set at the server side.
+        *
+        * Returns the hard limit on the number of
+        * results returned by the bookkeeping server.
+        *
+     * @return Server limit.
+     * @throws LBException If some communication or server problem occurs.
+        */     
+    public int getServerLimit() throws LBException {
+        try {
+            return queryServer.getServerLimit("");
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+    }
+
+       /**
+     * Set the soft result set size limit.
+        * Sets the maximum number of results this instance is willing
+        * to obtain when querying for jobs.
+     * Default value is 0. It means no limits for results.
+     *
+        * @param jobsLimit Maximum number of results. 0 for no limit.
+        */
+       public void setQueryJobsLimit(int jobsLimit) {
+        if (jobsLimit < 0) throw new IllegalArgumentException("jobsLimit");
+        this.queryJobsLimit = jobsLimit;
+    }
+
+    /**
+     * Get soft result set size limit.
+     * Gets the maximum number of results this instance is willing
+     * to obtain when querying for jobs.
+     * 
+     * @return queryJobsLimit Maximum number of results.
+     */
+    public int getQueryJobsLimit() {
+        return queryJobsLimit;
+    }
+
+       /**
+     * Set the soft result set size limit.
+        * Sets the maximum number of results this instance is willing
+        * to obtain when querying for events.
+     * Default value is 0. It means no limits for results.
+     *
+        * @param eventsLimit Maximum number of results. 0 for no limit.
+        */
+       public void setQueryEventsLimit(int eventsLimit) {
+        if (eventsLimit < 0) throw new IllegalArgumentException("eventsLimit");
+        this.queryEventsLimit = eventsLimit;
+    }
+
+    /**
+     * Get soft result set size limit.
+     * Gets the maximum number of results this instance is willing
+     * to obtain when querying for events.
+     * 
+     * @return queryEventsLimit Soft limit.
+     */
+    public int getQueryEventsLimit() {
+        return queryEventsLimit;
+    }
+
+    /**
+     * Sets the flag indicating the way of handling of too large query results.
+     * Default is NONE.
+     *
+     * @param flag One of NONE, LIMITED or ALL.
+     */
+    public void setQueryResultsFlag(QueryResultsFlag flag) {
+        if (flag == null) throw new IllegalArgumentException("flag");
+
+        queryResultsFlag = flag;
+    }
+
+    /**
+     * Gest the flag indicating the way of handling of too large query results.
+     *
+     * @return queryResultsFlag
+     */
+    public QueryResultsFlag getQueryResultsFlag() {
+        return queryResultsFlag;
+    }
+    
+    /**
+     * Retrieve all events satisfying the conjunctive-disjunctive
+        * query records.
+        * Returns all events belonging to the jobs specified by
+        * <tt>jobCond</tt> and satisfying <tt>queryCond</tt>. The
+        * conditions are given in conjunctive-disjunctive form
+        * <tt>((cond1 OR cond2 OR ...) AND ...)</tt>
+     *
+        * @param jobCond List of conditions on jobs.
+        * @param eventCond List of coditions on events.
+        * @return eventList List of Event objects representing L&B events.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<Event> queryEvents(List<QueryConditions> jobCond, 
+            List<QueryConditions> eventCond) throws LBException {
+
+        if (jobCond == null) throw new IllegalArgumentException("jobCond cannot be null");
+        if (eventCond == null) throw new IllegalArgumentException("eventCond cannot be null");
+
+        org.glite.wsdl.types.lb.Event[] events = null;
+        try {
+            events = queryServer.queryEvents(jobCond.toArray(new QueryConditions[jobCond.size()]), eventCond.toArray(new QueryConditions[eventCond.size()]));
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+
+        List<Event> eventList= new ArrayList<Event>();
+        
+        if (events!= null) {
+            int queryResults;
+            //if the events limit is reached
+            if (queryEventsLimit!=0 && events.length > queryEventsLimit) {
+                queryResults = getResultSetSize(queryEventsLimit, events.length);
+            } else queryResults = events.length;
+
+            EventConvertor convertor = new EventConvertor();
+            for (int i=0;i<queryResults;i++) {
+                eventList.add(convertor.convert(events[i]));
+            }
+        }
+
+        return eventList;
+    }
+
+       /**
+     * Retrieve jobs satisfying the query.
+        * Returns all jobs (represented as JobId's) satisfying query given in
+     * conjunctive-disjunctive form.
+     *
+        * @param query Query in conjunctive-disjunctive form.
+        * @return jobList      List of job id's.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<Jobid> queryJobs(List<QueryConditions> query) throws LBException {
+        if (query == null) throw new IllegalArgumentException("query cannot be null");
+
+        StringArrayHolder jobHolder = new StringArrayHolder();
+        try {
+            queryServer.queryJobs(query.toArray(new QueryConditions[query.size()]), new JobFlagsValue[]{}, jobHolder, new JobStatusArrayHolder());
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+           
+        List<Jobid> jobList = new ArrayList<Jobid>();
+
+        if (jobHolder.value!= null) {
+            int queryResults;
+            int jobsCount = jobHolder.value.length;
+            //if the jobs limit is reached
+            if (queryJobsLimit!=0 && jobsCount > queryJobsLimit) {
+                queryResults = getResultSetSize(queryJobsLimit, jobsCount);
+            } else queryResults = jobsCount;
+
+            for (int i=0;i<queryResults;i++) {
+                jobList.add(new Jobid(jobHolder.value[i]));
+            }
+        }
+        
+        return jobList;
+    }
+
+       /** 
+        * Retrieve status of jobs satisfying the given query.
+        * Returns states (represented by JobStatus) of all jobs
+        * satisfying the query in conjunctive-disjunctive form.
+     *
+        * @param query  Condition on jobs.
+        * @param flags  Determine which status fields are actually retrieved.
+        * @return states States of jobs satysfying the condition.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<JobStatus> queryJobStates(List<QueryConditions> query,
+            JobFlagsValue[] flags) throws LBException {
+        if (query == null) throw new IllegalArgumentException("query cannot be null");
+
+        JobStatusArrayHolder jobStatusHolder = new JobStatusArrayHolder();
+        try {
+            queryServer.queryJobs(query.toArray(new QueryConditions[query.size()]), flags, new StringArrayHolder(), jobStatusHolder);
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+
+        List<JobStatus> jobStates= new ArrayList<JobStatus>();
+
+        if (jobStatusHolder.value!= null) {
+            int queryResults;
+            int jobsCount = jobStatusHolder.value.length;
+            //if the jobs limit is reached
+            if (queryJobsLimit!=0 && jobsCount > queryJobsLimit) {
+                queryResults = getResultSetSize(queryJobsLimit, jobsCount);
+            } else queryResults = jobsCount;
+
+            for (int i=0;i<queryResults;i++) {
+                jobStates.add(jobStatusHolder.value[i]);
+            }
+        }
+
+        return jobStates;
+    }
+
+    /**
+        * Retrieve status of the job with the given jobId.
+        * Returns state (represented by JobStatus) of the job.
+     *
+        * @param jobId  jobId of the job.
+        * @param flags  Determine which status fields are actually retrieved.
+        * @return state State of the given job.
+     * @throws LBException If some communication or server problem occurs.
+        */
+    public JobStatus jobState(String jobId, JobFlagsValue[] flags) throws LBException {
+        if (jobId == null) throw new IllegalArgumentException("jobId cannot be null");
+        JobStatus state = new JobStatus();
+        try {
+            state = queryServer.jobStatus(jobId, flags);
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+
+        return state;
+    }
+
+       /** 
+        * Retrieve states of all user's jobs.
+        * Convenience wrapper around queryJobStates, returns status of all
+        * jobs whose owner is the current user (as named in the X509
+        * certificate subject).
+     *
+        * @return jobStates States of jobs owned by this user.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<JobStatus> userJobStates() throws LBException {
+        JobStatusArrayHolder jobStatusHolder = new JobStatusArrayHolder();
+        try {
+            queryServer.userJobs(new StringArrayHolder(), jobStatusHolder);
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+
+        List<JobStatus> jobStates= new ArrayList<JobStatus>();
+        
+        if (jobStatusHolder.value!= null) {
+            int queryResults;
+            int jobsCount = jobStatusHolder.value.length;
+            //if the jobs limit is reached
+            if (queryJobsLimit!=0 && jobsCount > queryJobsLimit) {
+                queryResults = getResultSetSize(queryJobsLimit, jobsCount);
+            } else queryResults = jobsCount;
+
+            for (int i=0;i<queryResults;i++) {
+                jobStates.add(jobStatusHolder.value[i]);
+            }
+        }
+
+        return jobStates;
+    }
+
+       /** 
+        * Find all user's jobs.
+        * Convenience wrapper around queryJobs, returns id's of all
+        * jobs whose owner is the current user (as named in the X509
+        * certificate subject).
+     *
+        * @return jobs List of jobs(IDs) owned by this user.
+     * @throws LBException If some communication or server problem occurs.
+        */
+       public List<Jobid> userJobs() throws LBException {
+        StringArrayHolder jobHolder = new StringArrayHolder();
+        try {
+            queryServer.userJobs(jobHolder, new JobStatusArrayHolder());
+        } catch (RemoteException ex) {
+            throw new LBException(ex);
+        }
+
+        List<Jobid> jobs= new ArrayList<Jobid>();
+        
+        if (jobHolder.value!= null) {
+            int queryResults;
+            int jobsCount = jobHolder.value.length;
+            //if the jobs limit is reached
+            if (queryJobsLimit!=0 && jobsCount > queryJobsLimit) {
+                queryResults = getResultSetSize(queryJobsLimit, jobsCount);
+            } else queryResults = jobsCount;
+
+            for (int i=0;i<queryResults;i++) {
+                jobs.add(new Jobid(jobHolder.value[i]));
+            }
+        }
+
+        return jobs;
+    }
+
+    /**
+     * This private method returns result set size number according to
+     * queryResultsFlag, which indicates handling of too large results.
+     *
+     * @param queryLimit The user limit.
+     * @param results Result set size returned by LB server.
+     * @return number of results this instance is going to return
+     */
+    private int getResultSetSize(int queryLimit, int results) {
+        if (queryLimit < 0) throw new IllegalArgumentException("queryLimit");
+        if (results < 0) throw new IllegalArgumentException("results");
+
+        switch (queryResultsFlag) {
+            case ALL: return results;
+            case LIMITED: return queryLimit;
+            case NONE: return 0;
+            default: throw new IllegalArgumentException("wrong QueryResult");
+        }
+    }
+
+}
+
+
diff --git a/org.glite.lb.client-java/src/org/glite/lb/ServerConnectionExample.java b/org.glite.lb.client-java/src/org/glite/lb/ServerConnectionExample.java
new file mode 100644 (file)
index 0000000..78ef777
--- /dev/null
@@ -0,0 +1,249 @@
+package org.glite.lb;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import org.glite.jobid.Jobid;
+import org.glite.lb.Event;
+import org.glite.wsdl.types.lb.JobStatus;
+import org.glite.wsdl.types.lb.QueryAttr;
+import org.glite.wsdl.types.lb.QueryConditions;
+import org.glite.wsdl.types.lb.QueryOp;
+import org.glite.wsdl.types.lb.QueryRecValue;
+import org.glite.wsdl.types.lb.QueryRecord;
+import org.glite.wsdl.types.lb.StatName;
+import org.glite.wsdl.types.lb.Timeval;
+
+
+
+/**
+ * This is a demonstration class for query API.
+ * It contains all possible methodes that can be called on ServerConnection
+ * and Job objects.
+ * @author Tomas Kramec, 207545@mail.muni.cz
+ */
+public class ServerConnectionExample {
+
+
+    /**
+     * This method serves for formating output information about given job status.
+     * It is only an example of how the data can be presented. It can be changed
+     * by user's needs.
+     *
+     * @param status Job status
+     * @return text representation of the given status
+     */
+    private static String jobStatusToString(JobStatus status) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("State:      "+status.getState()+"\n");
+        sb.append("Job ID:     "+status.getJobId()+"\n");
+        sb.append("Owner:      "+status.getOwner()+"\n");
+        sb.append("Job type:   "+status.getJobtype()+"\n");
+        sb.append("Destination:   "+status.getLocation()+"\n");
+        sb.append("Done code:  "+status.getDoneCode()+"\n");
+        sb.append("User tags:  ");
+        //if there are some user tags write it out.
+        if (status.getUserTags() != null) {
+            for (int i=0;i<status.getUserTags().length;i++) {
+                if (i==0) {
+                    sb.append(status.getUserTags()[i].getTag()+" = "+
+                            status.getUserTags()[i].getValue()+"\n");
+                } else sb.append("            "+status.getUserTags()[i].getTag()+
+                        " = "+ status.getUserTags()[i].getValue()+"\n");
+            }
+        } else sb.append("\n");
+        //Write the time info in a readable form.
+        Calendar calendar = new GregorianCalendar();
+        DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+        calendar.setTimeInMillis(status.getStateEnterTime().getTvSec()*1000);
+
+        sb.append("Enter time: "+ df.format(calendar.getTime())+"\n");
+        calendar.setTimeInMillis(status.getLastUpdateTime().getTvSec()*1000);
+        sb.append("Last update time: "+df.format(calendar.getTime())+"\n");
+        return sb.toString();
+    }
+
+    /**
+     * @param args the command line arguments
+     */
+    public static void main(String[] args) throws LBException {
+
+        if (args.length != 8) {
+            System.out.println("To use this class you have to run it with these arguments," +
+                    "in this order: \n" +
+                    "1. path to the trusted CA's files, default is /etc/grid-security/certificates/\n"+
+                    "2. -p for proxy based authentication\n" +
+                    "   -c for certificate based authentication\n"+
+                    "3. proxy certificate\n"+
+                    "4. user certificate\n"+
+                    "5. user key\n"+
+                    "6. user password\n"+
+                    "7. LB server addres in format \"https://somehost:port\"\n"+
+                    "8. jobid for queries in format \"https://somehost:port/uniquePart\"\n"+
+                    "Enter \"\" for empty option.");
+
+        } else {
+
+            /**
+             * create new instance of LBCredentials that holds credentials data
+             * used for authentication
+             */
+            LBCredentials lbCredent = null;
+
+            //if -p option is choosed set the proxy file for authentication
+            //if -c option is choosed set user certificate, key and password
+            if (args[1].equals("-p")) {
+                lbCredent = new LBCredentials(args[2], args[0]);
+            } else if (args[1].equals("-c")) {
+                lbCredent = new LBCredentials(args[3], args[4], args[5], args[0]);
+            } else {
+                System.err.println("Wrong option: "+args[1]+". Only -p and -c is allowed.");
+                System.exit(-1);
+            }
+         
+            //create new instance of ServerConnection with the given LB server address
+            ServerConnection sc = new ServerConnection(args[6], lbCredent);
+            
+            /**
+             * It is also possible to set these attributes of ServerConnection instance:
+             * timeout for server response
+             * limit for job results
+             * limit for event results
+             * flag indicating the way of handling too large results
+             * loggin level for events
+             * LB server addres
+             */
+//            sc.setQueryTimeout(60);
+//            sc.setQueryJobsLimit(100);
+//            sc.setQueryEventsLimit(50);
+//            sc.setQueryResultsFlag(ServerConnection.QueryResultsFlag.LIMITED);
+//            sc.setEventLoggingLevel(Level.LEVEL_DEBUG);
+//            sc.setQueryServerAddress("https://changedHost", 1234);
+
+            //Get the server limit for query results
+            System.out.println("Limit: " + sc.getServerLimit());
+
+            System.out.println();
+            /**
+             * Print all indexed attributes from LB database.
+             * If the attribute is USERTAG then print its name,
+             * if it is TIME then print state name.
+             */
+            System.out.println("Indexed attributes:");
+            List<QueryConditions> idx = sc.getIndexedAttrs();
+            for (int i=0;i<idx.size();i++) {
+                String name = idx.get(i).getAttr().getValue();
+                System.out.print(idx.get(i).getAttr().getValue());
+                if(name.equals("USERTAG"))
+                    System.out.print(" "+ idx.get(0).getTagName());
+                if(name.equals("TIME"))
+                    System.out.print(" "+ idx.get(0).getStatName());
+                System.out.println();
+            }
+
+
+            /**
+             * Print all user's jobs and their states.
+             */
+            System.out.println();
+            System.out.println("-----ALL USER's JOBS and STATES----");
+            //get user jobs
+            List<Jobid> jobs = sc.userJobs();
+            //get their states
+            List<JobStatus> js = sc.userJobStates();
+            Iterator<Jobid> it = jobs.iterator();
+            Iterator<JobStatus> itJs = js.iterator();
+            while (it.hasNext()) {
+                System.out.println(it.next());
+                System.out.println(jobStatusToString(itJs.next()));
+            }
+
+            //Demonstration of Job class
+            System.out.println();
+            System.out.println("----------------JOB----------------");
+
+            //create new Job
+            Job myJob = new Job(args[7], sc);
+            //print job state info
+            System.out.println();
+            System.out.println("Status: " + jobStatusToString(myJob.getStatus(null)));
+
+            //print info about job's events
+            System.out.println();
+            List<Event> events = myJob.getEvents();
+            System.out.println("Found "+events.size()+" events:");
+            for (int i=0;i<events.size();i++) {
+                System.out.println("Event: " + events.get(i).info());
+            }
+
+            /**
+             * This is demonstration of creating and using query conditions.
+             * It prints information about all user's jobs registered in LB
+             * submitted in last 3 days.
+             */
+            if (!jobs.isEmpty()) {
+                System.out.println();
+                System.out.println("-------------CONDITIONS------------");
+
+                //create query record for job ids from the previous query on all user's jobs.
+                List<QueryRecord> recList = new ArrayList<QueryRecord>();
+                int port = Integer.parseInt((args[6].split(":"))[2]);
+                for (int i=0;i<jobs.size();i++) {
+                    if(jobs.get(i).getPort() == port) {
+                        QueryRecValue jobIdValue = new QueryRecValue(null, jobs.get(i).toString(), null);
+                        recList.add(new QueryRecord(QueryOp.EQUAL, jobIdValue, null));
+                        System.out.println(jobIdValue.getC());
+                    } 
+                }
+                QueryRecord[] jobIdRec = new  QueryRecord[]{};
+                jobIdRec = recList.toArray(jobIdRec);
+
+                //crete QueryConditions instance this formula:
+                //(JOBID='jobId1' or JOBID='jobId2 or ...) where jobId1,... are ids of user's jobs
+                QueryConditions condOnJobid = new QueryConditions(QueryAttr.JOBID, null, null, jobIdRec);
+
+                //create query record for time
+                long time = System.currentTimeMillis()/1000;
+                QueryRecValue timeFrom = new QueryRecValue(null, null, new Timeval(time-259200, 0));
+                QueryRecValue timeTo = new QueryRecValue(null, null, new Timeval(System.currentTimeMillis()/1000, 0));
+                QueryRecord[] timeRec = new QueryRecord[]{new QueryRecord(QueryOp.WITHIN, timeFrom, timeTo)};
+                //create QueryConditions instance representing this formula:
+                //(TIME is in <timeFrom, timeTo> interval)
+                QueryConditions condOnTime = new QueryConditions(QueryAttr.TIME, null, StatName.SUBMITTED, timeRec);
+
+                //create QueryConditions list representing this formula:
+                //(JOBID='jobId1' or JOBID='jobId2 or ...) AND (TIME is in <timeFrom, timeTo> interval)
+                //where jobId1,... are ids of user's jobs
+                List<QueryConditions> condList = new ArrayList<QueryConditions>();
+                condList.add(condOnJobid);
+                condList.add(condOnTime);
+
+                //get all jobs matching the given conditions
+                List<Jobid> jobResult = sc.queryJobs(condList);
+                //get all their states
+                List<JobStatus> jobStatesResult = sc.queryJobStates(condList, null);
+
+                //Print information about results
+                Calendar calendar = new GregorianCalendar();
+                DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+                System.out.println();
+                System.out.print("Jobs registered ");
+                calendar.setTimeInMillis(timeFrom.getT().getTvSec()*1000);
+                System.out.print("from "+ df.format(calendar.getTime())+" ");
+                calendar.setTimeInMillis(timeTo.getT().getTvSec()*1000);
+                System.out.print("to "+ df.format(calendar.getTime())+"\n");
+                Iterator<Jobid> jobsit = jobResult.iterator();
+                Iterator<JobStatus> statusit = jobStatesResult.iterator();
+                while (jobsit.hasNext()) {
+                    System.out.println(jobsit.next());
+                    System.out.println(jobStatusToString(statusit.next()));
+                }
+            }
+
+        }
+    }
+}