/*******************************************************************************
 * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/

package org.eclipse.persistence.internal.xr;

// Javase imports
import static org.eclipse.persistence.internal.helper.ClassConstants.STRING;
import static org.eclipse.persistence.internal.oxm.Constants.BASE_64_BINARY_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DATE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DATE_TIME_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.INT_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.TIME_QNAME;
import static org.eclipse.persistence.internal.xr.Util.DEFAULT_ATTACHMENT_MIMETYPE;
import static org.eclipse.persistence.internal.xr.Util.EMPTY_STR;
import static org.eclipse.persistence.internal.xr.Util.SLASH_CHAR;
import static org.eclipse.persistence.internal.xr.Util.SXF_QNAME;
import static org.eclipse.persistence.internal.xr.Util.TEMP_DOC;
import static org.eclipse.persistence.internal.xr.Util.sqlToXmlName;
import static org.eclipse.persistence.internal.xr.sxf.SimpleXMLFormat.DEFAULT_SIMPLE_XML_FORMAT_TAG;
import static org.eclipse.persistence.internal.xr.sxf.SimpleXMLFormat.DEFAULT_SIMPLE_XML_TAG;
import static org.eclipse.persistence.oxm.XMLConstants.SCHEMA_INSTANCE_URL;
import static org.eclipse.persistence.oxm.XMLConstants.SCHEMA_URL;
import static org.eclipse.persistence.oxm.XMLConstants.XMLNS_URL;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.Blob;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;

// Java extension imports
import javax.activation.DataHandler;
import javax.xml.namespace.QName;

// EclipseLink imports
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DBWSException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.internal.databaseaccess.OutputParameterForCallableStatement;
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.jpa.JPAQuery;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.internal.oxm.conversion.Base64;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.internal.security.PrivilegedGetDeclaredMethod;
import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.internal.xr.sxf.SimpleXMLFormat;
import org.eclipse.persistence.internal.xr.sxf.SimpleXMLFormatModel;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.AttributeAccessor;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.structures.ObjectRelationalDatabaseField;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.mappings.XMLBinaryDataMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
import org.eclipse.persistence.oxm.mappings.XMLFragmentCollectionMapping;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
import org.eclipse.persistence.oxm.schema.XMLSchemaURLReference;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Session;
import org.w3c.dom.Element;

/**
 * <p><b>INTERNAL:</b>An XR QueryOperation is an executable representation of a <tt>SELECT</tt>
 * operation on the database.
 *
 * @author Mike Norman - michael.norman@oracle.com
 * @since EclipseLink 1.x
 */
@SuppressWarnings({"serial", "unchecked"/*, "rawtypes"*/})
public class QueryOperation extends Operation {
    public static final String ORACLEOPAQUE_STR = "oracle.sql.OPAQUE";
    private static final String IORACLEOPAQUE_STR = "oracle.jdbc.OracleOpaque";
    protected static final String RESULT_STR = "result";
    protected static final String XMLTYPEFACTORY_STR = "org.eclipse.persistence.internal.platform.database.oracle.xdb.XMLTypeFactoryImpl";
    protected static final String GETSTRING_METHOD = "getString";
    protected static final String ATTACHMENT_STR = "/attachment";
    protected static final String CURSOR_OF_STR = "cursor of ";
    protected static final String DATAHANDLER_STR = "DataHandler";
    protected static final String RESULTS_STR = "results";
    protected static final String VALUEOBJECT_STR = "ValueObject";
    protected static final String VALUE_STR = "value";
    protected static final String SIMPLEXML_FORMAT_STR = "/simple-xml-format";
    protected static final String SIMPLEXML_STR = "simpleXML";
    protected static final String DATABASEQUERY_STR = "databasequery";
    protected static final String ITEMS_STR = "ITEMS";
    protected static final String XSD_STR = "xmlns:xsd";
    protected static final String XSI_STR = "xmlns:xsi";
    protected static final String XSITYPE_STR = "xsi:type";
    protected static final String BASE64_BINARY_STR = "xsd:base64Binary";

    protected Result result;
    protected QueryHandler queryHandler;
    protected boolean userDefined = true;

    public QueryOperation() {
        super();
    }

    public Result getResult() {
        return result;
    }
    public void setResult(Result result) {
        this.result = result;
    }

    public QueryHandler getQueryHandler() {
        return queryHandler;
    }
    public void setQueryHandler(QueryHandler queryHandler) {
        this.queryHandler = queryHandler;
    }

    public boolean isUserDefined() {
        return userDefined;
    }
    public void setUserDefined(boolean userDefined) {
        this.userDefined = userDefined;
    }

    @Override
    public boolean isCollection() {
        return result != null && result.isCollection();
    }

    public boolean isSimpleXMLFormat() {
        return result != null && result.getSimpleXMLFormat() != null;
    }

    public boolean isAttachment() {
        return result != null && result.getAttachment() != null;
    }

    public QName getResultType() {
        if (result != null) {
            return result.getType();
        }
        return null;
    }

    @Override
    public boolean hasResponse() {
        return result != null;
    }

    @Override
    public void validate(XRServiceAdapter xrService) {
        super.validate(xrService);
        QName resultType = result == null ? null : result.getType();
        if (resultType != null) {
            if (!resultType.getNamespaceURI().equals(SCHEMA_URL)) {
                boolean sxf = resultType.getLocalPart().equals(DEFAULT_SIMPLE_XML_FORMAT_TAG) ||
                    resultType.getLocalPart().equals(CURSOR_OF_STR + DEFAULT_SIMPLE_XML_FORMAT_TAG);
                // check descriptor for Schema's high-level element type 'resultType'
                if (!sxf && !xrService.descriptorsByQName.containsKey(resultType)) {
                        throw DBWSException.resultHasNoMapping(resultType.toString(), name);
                }
            }
        }
        if (queryHandler != null) {
            queryHandler.validate(xrService, this);
        }
    }

    // Made static final for performance reasons.
    private static final class DataHandlerInstantiationPolicy extends InstantiationPolicy {
        protected String mimeType;
        public DataHandlerInstantiationPolicy(String mimeType) {
            super();
            this.mimeType = mimeType;
        }
        @Override
        public Object buildNewInstance() throws DescriptorException {
            return new DataHandler(null, mimeType);
        }
    }

    @Override
    public void initialize(XRServiceAdapter xrService) {
        super.initialize(xrService);
        if (queryHandler == null) {
            // session query instead of named query
            DatabaseQuery dq = xrService.getORSession().getQuery(name);
            if (dq != null) {
                queryHandler = new QueryHandler(){
                    public void initializeDatabaseQuery(XRServiceAdapter xrService, QueryOperation queryOperation) {
                        // do nothing
                    }
                    public void initializeArguments(XRServiceAdapter xrService,
                        QueryOperation queryOperation, DatabaseQuery databaseQuery) {
                        // do nothing
                    }
                    public void initializeCall(XRServiceAdapter xrService,
                        QueryOperation queryOperation, DatabaseQuery databaseQuery) {
                        // do nothing
                    }
                };
                queryHandler.setDatabaseQuery(dq);
            }
        }
        if (queryHandler == null) {
            throw DBWSException.couldNotLocateQueryForSession(name,
                xrService.getORSession().getName());
        }
        queryHandler.initialize(xrService, this);
        Session oxSession = xrService.getOXSession();
        QName resultType = result == null ? null : result.getType();
        addSimpleXMLFormatModelDescriptor(xrService);
        addValueObjectDescriptor(xrService);
        if (resultType == null) {
            if (isAttachment()) {
                Attachment attachment = result.getAttachment();
                XMLDescriptor descriptor =
                    (XMLDescriptor)oxSession.getProject().getClassDescriptor(DataHandler.class);
                if (descriptor == null) {
                    descriptor = new XMLDescriptor();
                    descriptor.setAlias(DATAHANDLER_STR);
                    descriptor.setJavaClass(DataHandler.class);
                    descriptor.setInstantiationPolicy(new DataHandlerInstantiationPolicy(attachment.getMimeType()));
                    XMLBinaryDataMapping mapping = new XMLBinaryDataMapping();
                    mapping.setAttributeName(RESULTS_STR);
                    mapping.setAttributeAccessor(new AttributeAccessor() {
                        @Override
                        public Object getAttributeValueFromObject(Object object)
                            throws DescriptorException {
                            Object result = null;
                            DataHandler dataHandler = (DataHandler)object;
                            try {
                                result = dataHandler.getContent();
                                if (result instanceof InputStream) {
                                    try (InputStream is = (InputStream) result) {
                                        byte[] buf = new byte[2048];
                                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                        int bytesRead = is.read(buf);
                                        while (bytesRead >= 0) {
                                            baos.write(buf, 0, bytesRead);
                                            bytesRead = is.read(buf);
                                        }
                                        result = baos.toByteArray();
                                    }
                                }
                            } catch (IOException e) {
                                // ignore
                            }
                            return result;
                        }
                        @Override
                        public void setAttributeValueInObject(Object object, Object value)
                            throws DescriptorException {
                        }
                    });
                    mapping.setXPath(DEFAULT_SIMPLE_XML_FORMAT_TAG + SLASH_CHAR +
                        DEFAULT_SIMPLE_XML_TAG + ATTACHMENT_STR);
                    mapping.setSwaRef(true);
                    mapping.setShouldInlineBinaryData(false);
                    mapping.setMimeType(attachment.getMimeType());
                    descriptor.addMapping(mapping);
                    NamespaceResolver nr = new NamespaceResolver();
                    descriptor.setNamespaceResolver(nr);
                    oxSession.getProject().addDescriptor(descriptor);
                    ((DatabaseSessionImpl)oxSession)
                        .initializeDescriptorIfSessionAlive(descriptor);
                    xrService.getXMLContext().storeXMLDescriptorByQName(descriptor);
                }
            }
        }
    }

    protected void addValueObjectDescriptor(XRServiceAdapter xrService) {
        Session oxSession = xrService.getOXSession();
        XMLDescriptor descriptor = (XMLDescriptor)oxSession.getProject().getClassDescriptor(
            ValueObject.class);
        if (descriptor == null) {
            descriptor = new XMLDescriptor();
            descriptor.setAlias(VALUEOBJECT_STR);
            descriptor.setJavaClass(ValueObject.class);
            XMLDirectMapping mapping = new XMLDirectMapping();
            mapping.setAttributeName(VALUE_STR);
            mapping.setXPath(VALUE_STR);
            descriptor.addMapping(mapping);
            NamespaceResolver nr = new NamespaceResolver();
            descriptor.setNamespaceResolver(nr);
            oxSession.getProject().addDescriptor(descriptor);
            ((DatabaseSessionImpl)oxSession)
                .initializeDescriptorIfSessionAlive(descriptor);
            xrService.getXMLContext().storeXMLDescriptorByQName(descriptor);
        }
    }

    protected void addSimpleXMLFormatModelDescriptor(XRServiceAdapter xrService) {
        if (isSimpleXMLFormat()) {
            Session oxSession = xrService.getOXSession();
            XMLDescriptor simpleXMLFormatDescriptor = (XMLDescriptor)oxSession.
                getProject().getClassDescriptor(SimpleXMLFormatModel.class);
            if (simpleXMLFormatDescriptor == null) {
                simpleXMLFormatDescriptor = new XMLDescriptor();
                simpleXMLFormatDescriptor.setJavaClass(SimpleXMLFormatModel.class);
                simpleXMLFormatDescriptor.setAlias(DEFAULT_SIMPLE_XML_FORMAT_TAG);
                simpleXMLFormatDescriptor.setDefaultRootElement(DEFAULT_SIMPLE_XML_FORMAT_TAG);
                XMLFragmentCollectionMapping xmlTag = new XMLFragmentCollectionMapping();
                xmlTag.setAttributeName(SIMPLEXML_STR);
                xmlTag.setXPath(DEFAULT_SIMPLE_XML_TAG);
                simpleXMLFormatDescriptor.addMapping(xmlTag);
                NamespaceResolver nr = new NamespaceResolver();
                simpleXMLFormatDescriptor.setNamespaceResolver(nr);
                XMLSchemaURLReference schemaReference = new XMLSchemaURLReference(EMPTY_STR);
                schemaReference.setSchemaContext(SIMPLEXML_FORMAT_STR);
                schemaReference.setType(XMLSchemaReference.COMPLEX_TYPE);
                simpleXMLFormatDescriptor.setSchemaReference(schemaReference);
                oxSession.getProject().addDescriptor(simpleXMLFormatDescriptor);
                ((DatabaseSessionImpl)oxSession)
                    .initializeDescriptorIfSessionAlive(simpleXMLFormatDescriptor);
                xrService.getXMLContext().storeXMLDescriptorByQName(simpleXMLFormatDescriptor);
            }
        }
    }

    /**
     * Execute <tt>SELECT</tt> operation on the database
     * @param   xrService parent <code>XRService</code> that owns this <code>Operation</code>
     * @param   invocation contains runtime argument values to be bound to the list of
     *          {@link Parameter}'s.
     * @return  result - the result of the underlying <tt>SELECT</tt> operation on
     *          the database, or <code>null</code>.
     *
     * @see  {@link Operation}
     */
    @Override
    public Object invoke(XRServiceAdapter xrService, Invocation invocation) {
        DatabaseQuery query = queryHandler.getDatabaseQuery();

        if (query.getProperty(DATABASEQUERY_STR) != null) {
            query = (DatabaseQuery) query.getProperty(DATABASEQUERY_STR);
        }

        // a named query created via ORM metadata processing does not have
        // parameters set, however, the operation should
        if (query.getArguments().size() == 0) {
            int idx = 0;
            for (Parameter param : getParameters()) {
                // for custom SQL query (as configured via ORM metadata
                // processing) we add args by position
                query.addArgument(Integer.toString(++idx), Util.SCHEMA_2_CLASS.get(param.getType()));
                query.addArgumentValue(invocation.getParameter(param.getName()));
            }
        } else {
            List<Object> argVals = new ArrayList<Object>();
            // need to set argument values
            for (Parameter param : getParameters()) {
                argVals.add(invocation.getParameter(param.getName()));
            }
            query.setArgumentValues(argVals);
        }
        // for SimpleXML + DataReadQuery we need to set MAP result type
        if (isSimpleXMLFormat() && query.isDataReadQuery()) {
            ((DataReadQuery) query).setResultType(DataReadQuery.MAP);
        }

        // now execute the query
        Object value = xrService.getORSession().getActiveSession().executeQuery(query);

        if (value != null) {
            // a recent change in core results in an empty vector being returned in cases
            // where before we'd expect an int value (typically 1) - need to handle this
            if (result != null && (result.getType() == INT_QNAME || result.getType().equals(SXF_QNAME))) {
                if (value instanceof ArrayList && ((ArrayList<?>) value).isEmpty()) {
                    ((ArrayList) value).add(1);
                } else  if (value instanceof Vector && ((Vector<?>) value).isEmpty()) {
                    ((Vector) value).add(1);
                }
            }

            // JPA spec returns an ArrayList<Object[]> for stored procedure queries - will need to unwrap.
            // Note that for legacy deployment XML projects this is not the case.
            if (value instanceof ArrayList) {
                ArrayList<?> returnedList = (ArrayList<?>) value;
                if (returnedList.size() > 0 && returnedList.get(0) instanceof Object[]) {
                    Object[] objs = (Object[]) returnedList.get(0);
                    if (isCollection()) {
                        value = new ArrayList<Object>();
                        for (Object obj : objs) {
                            ((ArrayList<Object>) value).add(obj);
                        }
                    } else {
                        value = objs[0];
                    }
                }
            }

            // handle SimpleXML
            if (isSimpleXMLFormat()) {
                value = createSimpleXMLFormat(xrService, value);
            } else {
                if (!isCollection() && value instanceof Vector) {
                    // JPAQuery will return a single result in a Vector
                    if (((Vector<?>) value).isEmpty()) {
                        return null;
                    }
                    value = ((Vector<?>) value).firstElement();
                }

                QName resultType = getResultType();
                if (resultType != null) {
                    // handle binary content
                    if (isAttachment() || (!isCollection() && resultType.equals(BASE_64_BINARY_QNAME))) {
                        String mimeType = DEFAULT_ATTACHMENT_MIMETYPE;
                        if (isAttachment() && result.getAttachment().getMimeType() != null) {
                            mimeType = result.getAttachment().getMimeType();
                        }
                        // handle BLOB types
                        if (value instanceof Blob) {
                            value = ((XMLConversionManager) xrService.getOXSession().
                                    getDatasourcePlatform().getConversionManager()).
                                    convertObject(value, ClassConstants.APBYTE);
                        }
                        return AttachmentHelper.buildAttachmentHandler((byte[])value, mimeType);
                    }
                    if (resultType.getNamespaceURI().equals(SCHEMA_URL)) {
                        // handle primitive types
                        ValueObject vo = new ValueObject();
                        vo.value = value;
                        value = vo;
                    } else {
                        Object targetObject = value;
                        if (xrService.descriptorsByQName.containsKey(resultType)) {
                            XMLDescriptor xdesc = xrService.descriptorsByQName.get(resultType);
                            ClassDescriptor desc = xrService.getORSession().getDescriptorForAlias(xdesc.getAlias());
                            if (desc.isAggregateDescriptor() && !desc.isObjectRelationalDataTypeDescriptor() && !desc.isRelationalDescriptor()) {
                                if (isCollection()) {
                                    XRDynamicEntity_CollectionWrapper xrCollWrapper = new XRDynamicEntity_CollectionWrapper();
                                    Vector<AbstractRecord> results = (Vector<AbstractRecord>)value;
                                    for (int i = 0, len = results.size(); i < len; i++) {
                                        Object o = desc.getObjectBuilder().buildNewInstance();
                                        populateTargetObjectFromRecord(desc.getMappings(), results.get(i), o, (AbstractSession)xrService.getORSession());
                                        xrCollWrapper.add(o);
                                    }
                                    targetObject = xrCollWrapper;
                                } else {
                                    targetObject = desc.getObjectBuilder().buildNewInstance();
                                    populateTargetObjectFromRecord(desc.getMappings(), (AbstractRecord) value, targetObject, (AbstractSession)xrService.getORSession());
                                }
                            } else if (isCollection() && value instanceof Vector) {
                                // could be a collection of populated objects, in which case we just return it
                                if (((Vector<?>) value).size() > 0 && !(((Vector<?>) value).get(0) instanceof AbstractRecord)) {
                                    return value;
                                }
                                XRDynamicEntity_CollectionWrapper xrCollWrapper = new XRDynamicEntity_CollectionWrapper();
                                Vector<AbstractRecord> results = (Vector<AbstractRecord>)value;
                                for (int i = 0, len = results.size(); i < len; i++) {
                                    Object o = desc.getObjectBuilder().buildNewInstance();
                                    populateTargetObjectFromRecord(desc.getMappings(), results.get(i), o, (AbstractSession)xrService.getORSession());
                                    xrCollWrapper.add(o);
                                }
                                targetObject = xrCollWrapper;
                            } else if (value instanceof AbstractRecord) {
                                targetObject = desc.getObjectBuilder().buildNewInstance();
                                populateTargetObjectFromRecord(desc.getMappings(), (AbstractRecord) value, targetObject, (AbstractSession)xrService.getORSession());
                            }
                        }
                        if (value instanceof ArrayList) {
                            XMLDescriptor xdesc = xrService.descriptorsByQName.get(resultType);
                            ClassDescriptor desc = xrService.getORSession().getDescriptorForAlias(xdesc.getAlias());
                            targetObject = desc.getObjectBuilder().buildNewInstance();
                            Object[] objs = new Object[1];
                            objs[0] = ((ArrayList<?>)value).get(0);
                            DatabaseRecord dr = new DatabaseRecord();
                            dr.add(new DatabaseField(ITEMS_STR), objs);
                            populateTargetObjectFromRecord(desc.getMappings(), (AbstractRecord) dr, targetObject, (AbstractSession)xrService.getORSession());
                        }
                        value = targetObject;
                    }
                }
            }
        }
        return value;
    }

    protected void populateTargetObjectFromRecord(Vector<DatabaseMapping> mappings,
        AbstractRecord record, Object targetObject, AbstractSession session) {
        ReadObjectQuery roq = new ReadObjectQuery();
        roq.setSession(session);
        for (DatabaseMapping dm : mappings) {
            dm.readFromRowIntoObject(record, null, targetObject, null, roq, session, true);
        }
    }

    public Object createSimpleXMLFormat(XRServiceAdapter xrService, Object value) {
        XMLRoot xmlRoot = new XMLRoot();
        SimpleXMLFormat simpleXMLFormat = result.getSimpleXMLFormat();
        String tempSimpleXMLFormatTag = SimpleXMLFormat.DEFAULT_SIMPLE_XML_FORMAT_TAG;
        String simpleXMLFormatTag = simpleXMLFormat.getSimpleXMLFormatTag();
        if (simpleXMLFormatTag != null && !EMPTY_STR.equals(simpleXMLFormatTag)) {
            tempSimpleXMLFormatTag = simpleXMLFormatTag;
        }
        xmlRoot.setLocalName(tempSimpleXMLFormatTag);
        String tempXMLTag = DEFAULT_SIMPLE_XML_TAG;
        String xmlTag = simpleXMLFormat.getXMLTag();
        if (xmlTag != null && !EMPTY_STR.equals(xmlTag)) {
            tempXMLTag = xmlTag;
        }
        Vector<DatabaseRecord> records = null;
        if (value instanceof ArrayList) {
            // JPA query results in a list of raw values
            // Here we have raw values returned as opposed to DatabaseRecords - this means
            // we need to figure out the tag names based on the call's output parameters.
            // assumes JPAQuery
            JPAQuery jpaQuery = (JPAQuery) queryHandler.getDatabaseQuery();
            // to match field names with results, we need to gather the database fields from each of the Output parameters
            List<DatabaseField> paramFlds = new ArrayList<DatabaseField>();
            DatasourceCall dsCall = (DatasourceCall) jpaQuery.getDatabaseQuery().getDatasourceCall();
            for (Object obj : dsCall.getParameters()) {
                if (obj instanceof OutputParameterForCallableStatement) {
                    paramFlds.add(((OutputParameterForCallableStatement) obj).getOutputField());
                } else if (obj instanceof Object[]) {
                    Object[] objArray = (Object[]) obj;
                    for (int i = 0; i < objArray.length; i++) {
                        Object o = objArray[i];
                        if (o instanceof OutputParameterForCallableStatement) {
                            paramFlds.add(((OutputParameterForCallableStatement) o).getOutputField());
                        }
                    }
                }
            }
            // now create a record using DatabaseField/value pairs
            DatabaseRecord dr = new DatabaseRecord();
            if (paramFlds.size() > 0) {
                for (int i=0; i <  ((ArrayList<?>) value).size(); i++) {
                    dr.add(paramFlds.get(i), ((ArrayList<?>) value).get(i));
                }
            } else {
                dr.add(new DatabaseField(RESULT_STR), ((ArrayList<?>) value).get(0));
            }
            records = new Vector<DatabaseRecord>();
            records.add(dr);
        } else if (value instanceof Vector) {
            Class<?> vectorContent = ((Vector<?>)value).firstElement().getClass();
            if (DatabaseRecord.class.isAssignableFrom(vectorContent)) {
                records = (Vector<DatabaseRecord>)value;
            } else {
                records = new Vector<DatabaseRecord>();
                DatabaseRecord dr = new DatabaseRecord();
                dr.add(new DatabaseField(RESULT_STR), ((Vector<?>)value).firstElement());
                records.add(dr);
            }
        } else {
            records = new Vector<DatabaseRecord>();
            DatabaseRecord dr = new DatabaseRecord();
            dr.add(new DatabaseField(RESULT_STR), value);
            records.add(dr);
        }
        SimpleXMLFormatModel simpleXMLFormatModel = new SimpleXMLFormatModel();
        XMLConversionManager conversionManager =
            (XMLConversionManager) xrService.getOXSession().getDatasourcePlatform().getConversionManager();
        SessionLog log = xrService.getOXSession().getSessionLog();
        for (DatabaseRecord dr : records) {
            Element rowElement = TEMP_DOC.createElement(tempXMLTag);
            for (DatabaseField field : dr.getFields()) {
                // handle complex types, i.e. ones we have a descriptor for
                if (field instanceof ObjectRelationalDatabaseField) {
                    ObjectRelationalDatabaseField ordtField = (ObjectRelationalDatabaseField) field;
                    if (xrService.getOXSession().getDescriptor(ordtField.getType()) != null) {
                        xrService.getXMLContext().createMarshaller().marshal(dr.get(field), rowElement);
                        continue;
                    }
                }
                Object fieldValue = dr.get(field);
                if (fieldValue != null) {
                    if (fieldValue instanceof Calendar) {
                        Calendar cValue = (Calendar)fieldValue;
                        fieldValue = conversionManager.convertObject(cValue, STRING, DATE_TIME_QNAME);
                    }
                    if (fieldValue instanceof Date) {
                        Date dValue = (Date)fieldValue;
                        fieldValue = conversionManager.convertObject(dValue, STRING, DATE_QNAME);
                    } else if (fieldValue instanceof Time) {
                        Time tValue = (Time)fieldValue;
                        fieldValue = conversionManager.convertObject(tValue, STRING, TIME_QNAME);
                    } else if (fieldValue instanceof Timestamp) {
                        Timestamp tsValue = (Timestamp)fieldValue;
                        fieldValue = conversionManager.convertObject(tsValue, STRING, DATE_TIME_QNAME);
                    } else if (fieldValue instanceof Blob) {
                        fieldValue = conversionManager.convertObject(fieldValue, ClassConstants.APBYTE);
                    } else if (SQLXML.class.isAssignableFrom(fieldValue.getClass())) {
                        // handle XMLType case where an oracle.jdbc.driver.OracleSQLXML instance was returned
                        SQLXML sqlXml = (SQLXML) fieldValue;
                        try {
                            String str = sqlXml.getString();
                            sqlXml.free();
                            // Oracle 12c appends a \n character to the xml string
                            fieldValue = str.endsWith("\n") ? str.substring(0, str.length() - 1) : str;
                        } catch (SQLException e) {
                            log.logThrowable(SessionLog.FINE, SessionLog.DBWS, e);
                        }
                    } else if (fieldValue.getClass().getName().equalsIgnoreCase(ORACLEOPAQUE_STR)) {
                        // handle XMLType case where an oracle.sql.OPAQUE instance was returned
                        try {
                            Class<?> oracleOPAQUE;
                            Class<?> xmlTypeFactoryClass;
                            Constructor<?> xmlTypeFactoryConstructor;
                            Object xmlTypeFactory;
                            Method getStringMethod;
                            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
                                oracleOPAQUE = AccessController.doPrivileged(new PrivilegedClassForName(IORACLEOPAQUE_STR, true, this.getClass().getClassLoader()));
                                xmlTypeFactoryClass = AccessController.doPrivileged(new PrivilegedClassForName(XMLTYPEFACTORY_STR, true, this.getClass().getClassLoader()));
                                xmlTypeFactoryConstructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(xmlTypeFactoryClass, new Class[0], true));
                                xmlTypeFactory = AccessController.doPrivileged(new PrivilegedInvokeConstructor(xmlTypeFactoryConstructor, new Object[0]));
                                getStringMethod = AccessController.doPrivileged(new PrivilegedGetDeclaredMethod(xmlTypeFactoryClass, GETSTRING_METHOD, new Class[] {oracleOPAQUE}));
                                fieldValue = AccessController.doPrivileged(new PrivilegedMethodInvoker(getStringMethod, fieldValue, new Object[] {}));
                            } else {
                                oracleOPAQUE = PrivilegedAccessHelper.getClassForName(IORACLEOPAQUE_STR, false, this.getClass().getClassLoader());
                                xmlTypeFactoryClass = PrivilegedAccessHelper.getClassForName(XMLTYPEFACTORY_STR, true, this.getClass().getClassLoader());
                                xmlTypeFactoryConstructor = PrivilegedAccessHelper.getConstructorFor(xmlTypeFactoryClass, new Class[0], true);
                                xmlTypeFactory = PrivilegedAccessHelper.invokeConstructor(xmlTypeFactoryConstructor, new Object[0]);
                                getStringMethod = PrivilegedAccessHelper.getDeclaredMethod(xmlTypeFactoryClass, GETSTRING_METHOD, new Class[] {oracleOPAQUE});
                                fieldValue = PrivilegedAccessHelper.invokeMethod(getStringMethod, xmlTypeFactory, new Object[] {fieldValue});
                            }
                        } catch (RuntimeException x) {
                            throw x;
                        } catch (ReflectiveOperationException | PrivilegedActionException e) {
                            // if the required resources are not available there's nothing we can do...
                            log.logThrowable(SessionLog.FINE, SessionLog.DBWS, e);
                        }
                    }

                    String elementName;
                    if (field.getName() == null || (elementName = sqlToXmlName(field.getName())).equals(EMPTY_STR)) {
                        // return arg from stored function has no name
                       elementName = RESULT_STR;
                    }
                    Element columnElement = TEMP_DOC.createElement(elementName);
                    rowElement.appendChild(columnElement);
                    String fieldValueString = fieldValue.toString();
                    // handle binary content - attachments dealt with in invoke() above
                    if (result.getType().equals(BASE_64_BINARY_QNAME)) {
                        fieldValueString = Helper.buildHexStringFromBytes(Base64.base64Encode((byte[])fieldValue));
                        columnElement.setAttributeNS(XMLNS_URL, XSD_STR, SCHEMA_URL);
                        columnElement.setAttributeNS(XMLNS_URL, XSI_STR, SCHEMA_INSTANCE_URL);
                        columnElement.setAttributeNS(SCHEMA_INSTANCE_URL, XSITYPE_STR, BASE64_BINARY_STR);
                    }
                    columnElement.appendChild(TEMP_DOC.createTextNode(fieldValueString));
                }
            }
            simpleXMLFormatModel.simpleXML.add(rowElement);
        }
        xmlRoot.setObject(simpleXMLFormatModel);
        return xmlRoot;
    }
}
