/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Darren Marvin
//      Created date:           2003/09/16
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.transportmessaging.payload;

import eu.gemss.components.transport.payload.*;
import java.io.*;
import java.util.logging.*;
import org.jdom.output.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.payload.serialisation.basicxsd.*;
import javax.xml.namespace.QName;
import uk.ac.soton.itinnovation.gemss.transportmessaging.payload.typedescriptions.XSDTypeDescription;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

/**
 * DataItemImpl class is holder class for single atomic data value. A translator is associated with
 * a data item value for serializing an object to a DOM Element (org.w3c.dom.Element). If you
 * translator is supplied by the client then a basic XSD translator is assumed.
 */
public class DataItemImpl extends Object implements DataItem {

    private static Logger mLogger = Logger.getLogger("eu.gemss.components.transport.payload.DataItem");

    private String mName;
    private TypeDescription mTypeDescription;
    private Object mValue;
    private DataItemDOMTranslator mTranslator;
    private boolean isFileReference = false;

    /**
     * Constructor requires full initialisation
     * @param name identifying name for the data item, for a WSDL RPC call this would be the part name
     * @param typeDesc description of the data type
     * @param value value of the data item
     */
    public DataItemImpl(String name, TypeDescription typeDesc, Object value) throws PayloadException {
        if(name==null || typeDesc==null || value == null) {
            mLogger.log(Level.SEVERE,"DataItem incorrectly created, name, type description or value incorrect");
            throw new PayloadException("DataItem incorrectly created, name, type description or value incorrect");
        }
        mName = name;
        mTypeDescription = typeDesc;
        mValue = value;
        //create the default translator
        mTranslator = new SimpleXSDDataItemDOMTranslator();

    }

    /**
     * Create a DataItem instance where the value object is a file reference to a file containing the binary value
     * @param name identifying name for the data item, for a WSDL RPC call this would be the part name
     * @param typeDesc description of the data type
     * @param value value of the dataitem
     * @param isFileReference file path to the file that contains the data value.
     * @throws PayloadException
     */
    public DataItemImpl(String name, TypeDescription typeDesc, Object value, boolean isFileReference) throws PayloadException {
        this(name,typeDesc,value);
        this.isFileReference = isFileReference;
    }

    /**
     * Retrieve the identifying name for this data item
     * @return name
     */
    public String getName() {
        return mName;
    }

    /**
     * Retrieve the type description for this data item
     * @return type description
     */
    public TypeDescription getTypeDescription() {
        return mTypeDescription;
    }

    /**
     * Retrieve the data value object for this data item
     * @return value object
     */
    public Object getValue() {
        return mValue;
    }

    /**
     * Check to see if the value object is a string file reference to the actual data value.
     * @return data value file path
     */
    public boolean isValueFileReference() {
        return isFileReference;
    }

    /**
     * Releases the data represented by this data item
     * This is to make it available for garbage collection
     */
    public void releaseData() {
        mValue = null;
    }

    /**
	 * Set the data item translator for this dataitem. The passed translator will be used
	 * by the underlying infrastructure to serialize the data item value. If a client
	 * does not implement a translator and pass it to this method prior to invoking a service then the messaging infrastructure
	 * will try to use a basic XSD type translator which might not be appropriate.
	 * @param dataItemDOMTranslator special serializer for the data value
	 */
	public synchronized void setDataItemDOMTranslator(DataItemDOMTranslator dataItemDOMTranslator) {
        mTranslator = dataItemDOMTranslator;
    }

    /**
	 * Retrieve the data item translator assigned to this data item value. If a client has not
	 * assigned a translator then the the default XSD type translator will be used.
	 * @return translator
	 */
	public DataItemDOMTranslator getDataItemDOMTranslator() {
        return mTranslator;
    }

    /**
	 * Writes this object to the passed output stream
	 * @param out output stream
	 */
	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
	    org.jdom.input.DOMBuilder dBuilder;
	    org.jdom.Element e;

	    try{
            //write out the name
            out.writeObject(mName);
            //write out the type description
            out.writeObject(mTypeDescription);

            //write out the translator
            out.writeObject(mTranslator);

            //write out the value
            if(mValue!=null) {
                out.writeBoolean(true);
                dBuilder = new org.jdom.input.DOMBuilder();
                org.w3c.dom.Node node = mTranslator.outputDOM(this);
                if(node.getNodeType()==org.w3c.dom.Node.ELEMENT_NODE) {
                  e = dBuilder.build((org.w3c.dom.Element) node);
                  out.writeObject("element");
                  out.writeObject( (new org.jdom.output.XMLOutputter()).outputString(new org.jdom.Document(e)) );
                }
                else if(node.getNodeType()==org.w3c.dom.Node.TEXT_NODE) {
                  //must be using simple type
                  if(mTypeDescription instanceof XSDTypeDescription) {

                    XSDTypeDescription typeDesc = (XSDTypeDescription) mTypeDescription;
                    out.writeObject("text");
                    QName qName = typeDesc.getQName();
                    out.writeObject(qName.getLocalPart());
                    out.writeObject(qName.getNamespaceURI());
                    out.writeObject( ((org.w3c.dom.Text) node).getData());
                  }
                }
                else {
                  throw new IOException("Unable to serialise data value with type description '" + mTypeDescription.getDescription() +"'");
                }
            }
            else
                out.writeBoolean(false);


        }
        catch(PayloadException ex) {
            throw new IOException(ex.getMessage());
        }
    }

    /**
	 * Recreates a serialised instance of the object from the passed input stream
	 * @param in input stream
	 */
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
	    String str;
	    org.jdom.input.SAXBuilder sBuilder;
	    org.jdom.Document doc;
	    org.jdom.Element e;
	    org.w3c.dom.Element element;

	    try{
            //read in the name
            mName = (String) in.readObject();

            //read the type description
            mTypeDescription = (TypeDescription) in.readObject();

            //read in the translator
            mTranslator = (DataItemDOMTranslator) in.readObject();

		// JDOM XML validation is turned off for now - otherwise we get grammer errors from JDOM. It
		// might be better to find out why and write our XML without these grammer errors
		// but I feel this is not cost effective to do right now [Stuart Oct03]
            boolean shouldReadObject = in.readBoolean();
            if(shouldReadObject) {

              //read in the type field
              String typeStr = (String) in.readObject();
              if(typeStr.equals("element")) {

                //read in the value (needs JDOM to parse the XML)
                str = (String) in.readObject();
                sBuilder = new org.jdom.input.SAXBuilder(true);//must be namespace aware otherwise hardly anything works
                sBuilder.setValidation( false );
                sBuilder.setIgnoringElementContentWhitespace( false );
                doc = sBuilder.build( new ByteArrayInputStream(str.getBytes()) );
                e = doc.getRootElement();
                element = (new DOMOutputter()).output(e);
                mValue = mTranslator.outputDataItem(element,new QName(e.getName(),e.getNamespaceURI())).getValue();
              }
              else if(typeStr.equals("text")) {
                //read in the QName values
                String localPart = (String) in.readObject();
                String namespaceURI = (String) in.readObject();
                str = (String) in.readObject();
                e = new org.jdom.Element(this.getName());
                org.jdom.Text text = new org.jdom.Text(str.trim());
                org.jdom.output.DOMOutputter dOutputter = new org.jdom.output.DOMOutputter();
                //element.addContent(cdataSection);
                //element.addContent(str);
                //element.addNamespaceDeclaration(Namespace.getNamespace(Constants.NS_PREFIX_SCHEMA_XSI,Constants.URI_2001_SCHEMA_XSI));

                element = dOutputter.output(e);
                //e.setAttribute("xsi:type",typeQName.getLocalPart());

                //look for the text node value
                NodeList nL = element.getChildNodes();
                Node txtNode = null;
                for(int i=0;i<nL.getLength();i++) {
                  if(nL.item(i).getNodeType()==Node.TEXT_NODE) {
                    txtNode = nL.item(i);
                  }
                }
                mValue = mTranslator.outputDataItem(txtNode,new QName(localPart,namespaceURI));
              }
            }
            else
                mValue = null;


        }
        catch(org.jdom.JDOMException ex) {
            throw new IOException(ex.getMessage());
        }
        catch(PayloadException ex) {
            throw new IOException(ex.getMessage());
        }
    }
}
