
import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.rdql.*;

import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import java.util.Enumeration;

//import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.transformation.AbstractTransformer;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.ProcessingException;
import org.apache.avalon.framework.parameters.Parameters;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

import java.util.*;
import java.io.IOException;

/**
 *
 * Class RDQLTransformer
 *
 * This transformer expects an XML document including elements of interest in
 * the following structure:
 *
 * <rdql:RDQL xmlns:rdql = "http://maria_sharapova.html">
 *  <rdql:query>
 *     <rdql:select>
 *          <rdql:param>myParam_1</rdql:param>
 *          .......
 *          <rdql:param>myParam_n</rdql:param>
 *      </rdql:select>
 *      <rdql:where>
 *          <rdql:triple subject="?x" predicate="http://a.com/ontology#hatStudent" object="?y"/>
 *           ........
 *      </rdql:where>
 *      <rdql:and operand1="?x" operand2="http://a.com/ontology#TUM" operator="eq"/>
 *      <rdql:using/>
 *  </rdql:query>
 * </rdql:RDQL>
 *
 *
 * all other elements will be returned unmodified
 *
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 *
 * @author Can Önder
 * @author Halgurt Mustafa Ali
 *
 * @version 1.0
 *
 */


public class RDQLTransformer extends AbstractTransformer {

    //ClassPath Property setzen System.getProperties().put...
    private String namespace;
    int count;

    boolean start;
    boolean readQuery;
    boolean readSelect;
    boolean readParam;
    boolean readWhere;
    boolean readTriple;
    boolean readAnd;
    boolean readUsing;

    Enumeration paramNames;
    Request request;
    String[] parameters;
    String src;
    StringBuffer query;
    StringBuffer using;
    StringBuffer param;

    String subject, predicate, object;
    String operand1, operand2, operator;

    Vector params;
    Vector triples;
    Vector ands;
    Vector reqParams;
    Vector pureParams;


    /**
     * Constructor
     */
    public RDQLTransformer () {

        namespace = "http://maria_sharapova.html";

        start         = false;
        readQuery     = false;
        readSelect    = false;
        readParam     = false;
        readWhere     = false;
        readTriple    = false;
        readAnd       = false;
        readUsing     = false;

        query      = new StringBuffer ();
        param      = new StringBuffer ();
        using      = new StringBuffer ();

        params     = new Vector ();
        triples    = new Vector ();
        ands       = new Vector ();
        pureParams = new Vector ();
        reqParams  = new Vector ();

        System.out.println ("Starting...");
    }


    /**
     *
     * no need to be implemented for the RDQLTramsformer;
     * inherited from org.apache.cocoon.transformation.AbstractTransformer
     *
     * @param resolver SourceResolver
     * @param objectModel Map
     * @param src String
     * @param par Sitemap Parameters
     * @throws ProcessingException
     * @throws SAXException
     * @throws IOException
     */
    public void setup (SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException {

        if (src == null){
        	System.out.println ("file " + resolver.resolveURI (src).getURI() + " not found");
        }
        else {
        	//absolute path to the ontology model
            this.src = resolver.resolveURI (src).getURI();
        }

        try {
            parameters = par.getNames ();
            for (int i = 0; i < parameters.length; i++) {
                parameters[i] = resolver.resolveURI (par.getParameter (parameters[i])).getURI ();
            }

            request = ObjectModelHelper.getRequest (objectModel);
            paramNames = request.getParameterNames ();

            while (paramNames.hasMoreElements ()) {
                final String s = (String)paramNames.nextElement ();
                reqParams.addElement (s);
                System.out.println (s);
                System.out.println (request.getParameter (s));
            }
        }

        catch (Exception e) {
            System.out.println ("An Error occured during Parameter instantiation ");
            e.printStackTrace ();
            reset ();
        }
    }



    /**
     *
     * Parses the incoming SAX events.
     * The RDQLTransformer reacts on tags with the namespace (<xmlns:rdql= "http://maria_sharapova.html">)
     *
     * @param namespaceURI The namespace
     * @param localName The name of the current tag
     * @param qName The qualified name
     * @param attributes The attributes of the current tag
     * @throws SAXException
     */
    public void startElement (String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException {

        //react on the following tags of interest...
        if (namespaceURI.equals (namespace) && localName.equals ("RDQL")) {
            start = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("query")) {
            readQuery = true;
        }


        else if (namespaceURI.equals (namespace) && localName.equals ("select") && readQuery) {
            readSelect = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("param") && readSelect) {
            readParam = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("where") && readQuery) {
            readWhere = true;
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("triple") && readWhere) {
            readTriple = true;
            subject   = attributes.getValue ("subject");
            object    = attributes.getValue ("object");
            predicate = attributes.getValue ("predicate");
        }

        else if (namespaceURI.equals (namespace) && localName.equals ("and") && readQuery) {
            readAnd = true;
            operand1 = attributes.getValue ("operand1");
            operand2 = attributes.getValue ("operand2");
            operator = attributes.getValue ("operator");
       }

        else if (namespaceURI.equals (namespace) && localName.equals ("using") && readQuery) {
            readUsing = true;
        }

        //do not modify any other tags...
        else {
            super.startElement (namespaceURI, localName, qName, attributes);
        }

    }





    /**
     *
     * Reads the content between the tags of interest.
     * The content is saved in StringBuffers
     *
     * @param ch The whole XML document as an character array
     * @param start The start index of the tag content
     * @param length The length of the content between the start and the end tag
     *
     */
    public void characters (char[] ch, int start, int length) {


        if (readUsing && (length != 0)) {
            using.append (ch, start, length);
            readUsing = false;
        }

        if (readParam) {
            param.append (ch, start, length);
        }
    }






    /**
     *
     * After any triple declaration, param declaration or and declaration, we compose the specified part
     * of the query (as there can be multiple triple, param or and declarations) before generating and
     * executing the query itself
     *
     * @param namespaceURI The namespace
     * @param localName The name of the current tag
     * @param qName The qualified name
     * @throws SAXException
     *
     */
    public void endElement (String namespaceURI, String localName, String qName) throws SAXException {

        //add this triple statement to the vector of all triples, belonging to this query
        if (namespaceURI.equals (namespace) && localName.equals ("triple")) {

            readTriple = false;

            //Parameter setzen, falls übergeben...
            for (int i = 0; i < reqParams.size(); i++) {

                String temp = (String)reqParams.elementAt(i);
                //System.out.println (reqParams.elementAt(i)+ " = " +subject);

                if (subject.equals (temp)) {
                    System.out.println (reqParams.elementAt(i)+ " = " + subject);
                    subject = request.getParameter (temp);
                }
                if (reqParams.elementAt(i).equals (object)) {
                    System.out.println (reqParams.elementAt(i)+ " = " + subject);
                    object = request.getParameter (temp);
                }
            }

            triples.addElement (generateTriple (subject, predicate, object));
        }

        //add this param statement to the vector of all params, belonging to this query
        else if (namespaceURI.equals (namespace) && localName.equals ("param")) {

            readParam = false;
            params.addElement (param.toString ());
            param = new StringBuffer ();
        }

        //add this and statement to the vector of all and-statements, belonging to this query
        else if (namespaceURI.equals (namespace) && localName.equals ("and")) {

            readAnd = false;
        	for (int i=0; i<reqParams.size(); i++) {

        		String temp = reqParams.elementAt(i).toString();
        		if (temp.equals (operand1)) {
                    operand1 = request.getParameter(temp);
        		}
        		if (temp.equals (operand2)) {
                    operand2 = request.getParameter(temp);
        		}
            }

            if (operand1.trim ().length() != 0 && operand2.trim ().length() != 0 && operator.trim ().length() != 0) {
                ands.addElement (generateAnd (operand1.trim (), operand2.trim (), operator.trim ()));
            }
        }

        //when we arrive at the end of the query tag, we can compose and execute the RDQL query, considering all the relevant information
        else if (namespaceURI.equals (namespace) && localName.equals ("query")) {

            //first, compose the query from "select", "where", "and" and "using" tags
            readQuery = false;
            for (int i = 0; i < params.size(); i++){
            	//System.out.println(((String)params.elementAt(i)).substring(1));
            	final String f = (params.elementAt(i).toString()).substring(1);
            	pureParams.addElement(f);
            }
            query.append ("Select ");
            for (int i = 0; i < params.size (); i++) {
                query.append ((String)params.elementAt (i) + " ");
            }

            query.append ("where ");

            for (int i = 0; i < triples.size (); i++) {
                query.append ((String)triples.elementAt (i));
            }

            for (int i = 0; i < ands.size(); i++) {
                query.append (" and " + ands.elementAt(i));
            }

            if (using.toString ().trim ().length () != 0) {
                String usingTrimmed = using.toString ().trim ();
                query.append (" using " + usingTrimmed);
            }

            //query is composed, now execute and embed the result in a "result" tag...
            OntModel m = null;
            Query q    = null;
            try {
                super.startElement ("", "result", "result", (Attributes)new AttributesImpl ());

                //constructs an empty model of the type RDFS
                m = ModelFactory.createOntologyModel (OntModelSpec.RDFS_MEM, null);

                //add the ontology and the instances to the model
                m.getDocumentManager ().addAltEntry (src, src);
                //read the instances...
                m.read (src);
                for (int i = 0; i < parameters.length; i++) {
                    m.read (parameters[i]);
                }

                //...and execute the query...
                q = new Query (query.toString ());
            }

            catch (Exception e) {
                System.out.println ("An Error occured during query instantiation ");
                e.printStackTrace ();
                reset ();
            }

            System.out.println ("Query: " + query.toString ());
            q.setSource (m);

            QueryExecution qe = new QueryEngine (q);
            QueryResults qr = qe.exec ();

            //go through the resultset until it is empty
            for (Iterator iter = qr; qr.hasNext (); ) {

                ResultBinding rbind = (ResultBinding)iter.next ();
                super.startElement ("", "item", "item", (Attributes)new AttributesImpl());
                for (int i = 0; i < params.size (); i++) {

                    Object obj = rbind.getValue ((String)pureParams.elementAt (i)).getRDFLiteral ().getValue ();
                    super.startElement ("", pureParams.elementAt (i).toString (), pureParams.elementAt (i).toString (), (Attributes)new AttributesImpl());
                    super.characters (obj.toString ().toCharArray (), 0, obj.toString ().length ());
                    super.endElement ("", pureParams.elementAt (i).toString (), pureParams.elementAt (i).toString ());
                }
                super.endElement ("", "item", "item");
            }

            //now, we have the result
            super.endElement ("", "result", "result");

            //query is executed, now reset query parameters...
            params.removeAllElements ();
            triples.removeAllElements ();
            ands.removeAllElements ();
            pureParams.removeAllElements();

            query = new StringBuffer ();
            using = new StringBuffer ();
        }


        //recycle all variables when we arrive at the end...
        else if (namespaceURI.equals (namespace) && localName.equals ("RDQL")) {
            reset();
        }

        //do nothing when these elements occur...
        else if (namespaceURI.equals (namespace) && (localName.equals ("select")    ||
                                                     localName.equals ("where")     ||
                                                     localName.equals ("and")       ||
                                                     localName.equals ("using"))) {}


        //do not modify any other elements
        else {
            super.endElement (namespaceURI, localName, qName);
        }
    }




    /**
     *
     * generates a single RDQL triple in the correct RDQL syntax, putting subject, predicate
     * and object together
     *
     * @param subject The subject
     * @param predicate The predicate
     * @param object The object
     * @return The triple
     *
     */
    public String generateTriple (String subject, String predicate, String object) {

        StringBuffer retval = new StringBuffer ();
        retval.append ("(");

        if (!subject.startsWith ("?")) {
            try {
                int zahl = Integer.parseInt (subject);
                System.out.println ("ZAHL");
                retval.append (subject);
            }
            catch (Exception e) {
                System.out.println ("STRING");
                retval.append ("\"" + subject + "\"");
            }
        }
        else {
            retval.append (subject);
        }
        retval.append (", <" + predicate + ">, ");

        if (!object.startsWith ("?")) {
            try {
                int zahl = Integer.parseInt (object);
                System.out.println ("ZAHL");
                retval.append (object);
            }
            catch (Exception e) {
                System.out.println ("STRING");
                retval.append ("\"" + object + "\"");
            }
        }
    	else {retval.append (object + ")");}

        System.out.println ("Triple: " + retval.toString ());
        return retval.toString ();
    }




    /**
     *
     * generates a single RDQL and statement in the correct RDQL syntax, putting all operands and
     * the operator together
     *
     * @param op1 First operand
     * @param op2 Second operand
     * @param operator The operator
     * @return String
     *
     */
    public String generateAnd (String op1, String op2, String operator) {

        StringBuffer retval = new StringBuffer ();

        if (!op1.startsWith ("?")) {
            try {
                int zahl = Integer.parseInt (op1);
                System.out.println ("ZAHL");
                retval.append (op1+ " " +operator+ " ");
            }
            catch (Exception e) {
                System.out.println ("STRING");
                retval.append ("\"" + op1 + "\" "+ operator + " ");
            }
        }
        else {
            retval.append (op1+ " " +operator+ " ");
        }

        if (!op2.startsWith ("?")) {
            try {
                int zahl = Integer.parseInt (op2);
                System.out.println ("ZAHL");
                retval.append (op2);
            }
            catch (Exception e) {
                System.out.println ("STRING");
                retval.append ("\"" + op2 + "\"");
            }
        }
        else {
            retval.append (op2);
        }
        return retval.toString ();
    }



    /**
     * Recycles all the variables and parameters
     */
    private void reset () {

        //boolean variables
        start = false;
        readQuery = false;
        readSelect = false;
        readParam = false;
        readWhere = false;
        readTriple = false;
        readAnd = false;
        readUsing = false;

        //StringBuffers
        query = new StringBuffer ();
        param = new StringBuffer ();
        using = new StringBuffer ();

        //Vectors
        params.removeAllElements ();
        triples.removeAllElements ();
        ands.removeAllElements ();
        reqParams.removeAllElements();
        pureParams.removeAllElements();
    }
}

