All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.base.rest.spi.RestParser Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Core, http://www.openmdx.org/
 * Description: REST Parser
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2010-2014, OMEX AG, Switzerland
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * ------------------
 * 
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.base.rest.spi;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.resource.ResourceException;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.Record;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.openmdx.base.accessor.rest.spi.ControlObjects_2;
import org.openmdx.base.exception.RuntimeServiceException;
import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.io.ObjectInputStream;
import org.openmdx.base.json.stream.JSONReader;
import org.openmdx.base.mof.cci.ModelElement_1_0;
import org.openmdx.base.mof.cci.ModelHelper;
import org.openmdx.base.mof.cci.Model_1_0;
import org.openmdx.base.mof.cci.Multiplicity;
import org.openmdx.base.mof.cci.PrimitiveTypes;
import org.openmdx.base.mof.spi.Model_1Factory;
import org.openmdx.base.naming.Path;
import org.openmdx.base.resource.Records;
import org.openmdx.base.resource.cci.ExtendedRecordFactory;
import org.openmdx.base.rest.cci.MessageRecord;
import org.openmdx.base.rest.cci.ObjectRecord;
import org.openmdx.base.rest.cci.QueryRecord;
import org.openmdx.base.rest.cci.ResultRecord;
import org.openmdx.base.rest.spi.RestSource.Format;
import org.openmdx.base.text.conversion.Base64;
import org.openmdx.base.text.conversion.UUIDConversion;
import org.openmdx.base.wbxml.WBXMLReader;
import org.openmdx.base.xml.spi.LargeObjectWriter;
import org.openmdx.kernel.collection.ArraysExtension;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.exception.Throwables;
import org.openmdx.kernel.log.SysLog;
import org.w3c.cci2.BinaryLargeObject;
import org.w3c.cci2.BinaryLargeObjects;
import org.w3c.cci2.CharacterLargeObject;
import org.w3c.cci2.CharacterLargeObjects;
import org.w3c.format.DateTimeFormat;
import org.w3c.spi2.Datatypes;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * REST Parser
 */
public class RestParser {

    /**
     * Constructor
     */
    private RestParser() {
        // Avoid instantiation
    }
    

    /**
     * 
     */
    private static final String ITEM_TAG = "_item";
    private static final String OBJECTS_TAG = "objects";
    
    /**
     * The XML Readers
     */
    private static final ThreadLocal xmlReaders = new ThreadLocal() {

        @Override
        protected XMLReader initialValue() {
            try {
                SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
                XMLReader reader = parser.getXMLReader();
                try {
                    reader.setFeature("http://xml.org/sax/features/namespaces", true);
                } catch(SAXException e) {
                    SysLog.info("Unable to set SAXReader feature", e.getMessage());
                }
                try {
                    reader.setFeature("http://xml.org/sax/features/validation", false);
                } catch(SAXException e) {
                    SysLog.info("Unable to set SAXReader feature", e.getMessage());
                }
                try {
                    // Prevent XML eXternal Entity injection (XXE)
                    // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
                    reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
                } catch(SAXException e) {
                    SysLog.info("Unable to set SAXReader feature", e.getMessage());
                }
                return reader;
            } catch (Exception e) {
                throw new RuntimeServiceException(e);
            }
        }
    };

    /**
     * The WBXML Readers
     */
    private static final ThreadLocal wbxmlReaders = new ThreadLocal() {

        @Override
        protected XMLReader initialValue() {
            try {
                return new WBXMLReader(new WBXMLPlugIn());
            } catch (Exception e) {
                throw new RuntimeServiceException(e);
            }
        }
    };
    
    /**
     * The JSON Readers
     */
    private static final ThreadLocal jsonReaders = new ThreadLocal() {

        @Override
        protected XMLReader initialValue() {
            try {
                return new JSONReader();
            } catch (Exception e) {
                throw new RuntimeServiceException(e);
            }
        }
    };
    
    /**
     * Retrieve an XML Reader instance
     * 
     * @param source
     * 
     * @return an XML Reader instance
     * 
     * @throws SAXNotSupportedException 
     * @throws SAXNotRecognizedException 
     */
    private static XMLReader getReader(
        RestSource source
    ) throws SAXNotRecognizedException, SAXNotSupportedException {
        final Format format = source.getFormat();
        switch(format) {
            case WBXML:
                XMLReader xmlReader = RestParser.wbxmlReaders.get();
                xmlReader.setFeature(WBXMLReader.EXHAUST, source.isToBeExhausted());
                return xmlReader;
            case XML:
                return RestParser.xmlReaders.get();
            case JSON:
                return RestParser.jsonReaders.get();
        }
        throw new SAXNotSupportedException("Unsupported input format: " + format);
    }

    /**
     * Parse a record
     * 
     * @param source
     *            the source providing the record
     * 
     * @param xri
     *            optional request path. If null the path is derived from source
     * 
     * @return either a structure or an object record
     * 
     * @throws ServiceException
     */
    @SuppressWarnings("unused")
    public static MappedRecord parseRequest(
        RestSource source, 
        Path xri
    ) throws SAXException {
        try {
            StandardHandler handler = new StandardHandler(source);
            XMLReader reader = RestParser.getReader(source);
            reader.setContentHandler(handler);
            if(false) {
                Reader is = source.getBody().getCharacterStream();
                if(is != null) {
                    int c;
                    StringBuilder s = new StringBuilder();
                    while((c = is.read()) != -1) {
                        s.append((char)c);
                    }
                    SysLog.detail("Request", s);
                    source.getBody().setCharacterStream(new StringReader(s.toString()));
                }
            }
            reader.parse(source.getBody());
            source.close();
            return handler.getValue(xri);
        } catch (IOException exception) {
            throw new SAXException(exception);
        } catch (RuntimeException exception) {
            throw new SAXException(exception);
        }
    }

    /**
     * Parse a collection
     * 
     * @param target
     * @param source
     * 
     * @throws ServiceException
     */
    @SuppressWarnings("unused")
    public static void parseResponse(
        Record target, 
        RestSource source
    ) throws SAXException {
        try {
            StandardHandler handler = new StandardHandler(target, source);
            XMLReader reader = RestParser.getReader(source);
            reader.setContentHandler(handler);
            if(false) {
                InputStream is = source.getBody().getByteStream();
                if(is != null) {
                    int c;
                    StringBuilder s = new StringBuilder();
                    while((c = is.read()) != -1) {
                        s.append((char)c);
                    }
                    System.out.println(s);
                    source.getBody().setCharacterStream(new StringReader(s.toString()));
                }
            }
            reader.parse(source.getBody());
            source.close();
        } catch (IOException exception) {
            throw new SAXException(exception);
        } catch (SAXException exception) {
            throw new SAXException(exception);
        } catch (RuntimeServiceException exception) {
            throw new SAXException(exception);
        }
    }

    /**
     * Parse an exception stack
     * 
     * @param source
     *            the source providing the exception stack
     * 
     * @return a BasicException
     * 
     * @throws SAXException in case of failure
     */
    public static BasicException parseException(
        RestSource source
    ) throws SAXException {
        try {
            ExceptionHandler handler = new ExceptionHandler();
            XMLReader reader = RestParser.getReader(source);
            reader.setContentHandler(handler);
            reader.parse(source.getBody());
            source.close();
            return handler.getValue();
        } catch (IOException exception) {
            throw new SAXException(exception);
        } catch (RuntimeException exception) {
            throw new SAXException(exception);
        }
    }

    /**
     * Tells whether the given MIME type requires a binary stream
     * 
     * @param mimeType
     * 
     * @return true if the given MIME type requires a binary stream
     */
    public static boolean isBinary(
        String mimeType
    ){
        return "application/vnd.openmdx.wbxml".equals(mimeType);
    }
    
    /**
     * Provide a parse() source
     * 
     * @param input the ObjectInput
     * 
     * @return a Source
     */
//  @SuppressWarnings("resource")
    public static RestSource asSource(
        ObjectInput input
    ){
        return new RestSource(
            new InputSource(
                input instanceof InputStream ? (InputStream) input : new ObjectInputStream(input)
            )
        );
    }

    // ------------------------------------------------------------------------
    // Class StandardHandler
    // ------------------------------------------------------------------------

    /**
     * Content handler which maps an XML encoded values to JCA records
     */
    static class StandardHandler extends AbstractHandler {

        /**
         * Constructor to parse an object or a record
         * 
         * @param source
         */
        StandardHandler(RestSource source) {
            this.target = null;
            this.source = source;
        }

        /**
         * Constructor to parse a collection
         * 
         * @param source
         * @param target
         */
        StandardHandler(Record target, RestSource source) {
            this.target = target;
            this.source = source;
        }

        /**
         * The model accessor
         */
        private final Model_1_0 model = Model_1Factory.getModel();
        private static final ExtendedRecordFactory recordFactory = Records.getRecordFactory();
        private final RestSource source;
        private final Record target;
        private final Deque values = new ArrayDeque();
        private String featureName = null;
        private final Deque featureNames = new ArrayDeque();
        private final Deque featureTypes = new ArrayDeque();
        private String previousEndElement = null;
        private String href = null;
        private String version = null;
        private String id = null;
        private String index = null;
        private Record value = null;
        private Multiplicity multiplicity = null;
        private String featureType = null;
        private boolean nestedStructFieldStart = false;
        private boolean nestedStructEnd = false;

        /**
         * Retrieve the interaction's value record
         * 
         * @return the interaction's value record
         * 
         * @throws ServiceException
         */
        MappedRecord getValue(
            Path xri
        ) throws SAXException {
            String typeName = this.values.peek().getRecordName();
            if (this.isQueryType(typeName)) {
                return this.getQuery(xri);
            } else if (this.isStructureType(typeName)) {
                return (MappedRecord) this.values.peek();
            } else if (xri.isTransactionalObjectId()){
                return this.getObject(null);
            } else {
                return this.getObject(xri);
            }
        }

        /**
         * Retrieve the interaction's object record
         * 
         * @param xri 
         * 
         * @return the interaction's object record
         */
        ObjectRecord getObject(
            Path xri
        ) throws SAXException {
            final Class recordInterface = ObjectRecord.class;
            ObjectRecord object = newMappedRecord(recordInterface);
            object.setResourceIdentifier(
                xri == null ? this.source.getXRI(this.href) : xri
            );
            object.setValue((MappedRecord) this.values.peek());
            if (this.version != null) {
                object.setVersion(Base64.decode(this.version));
            }
            if(UUIDConversion.isUUID(this.id)) {
                object.setTransientObjectId(UUIDConversion.fromString(this.id));
            }
            return object;
        }

        private  T newMappedRecord(
            final Class recordInterface
        ) throws SAXException {
            try {
                return recordFactory.createMappedRecord(recordInterface);
            } catch (ResourceException exception) {
                throw new SAXException(exception);
            }
        }

        private MappedRecord newMappedRecord(
            String typeName
        ) throws SAXException {
            try {
                return recordFactory.createMappedRecord(typeName);
            } catch (ResourceException exception) {
                throw new SAXException(exception);
            }
        }

        private MappedRecord newMappedRecord(
            final Multiplicity multiplicity
        ) throws SAXException {
            return newMappedRecord(multiplicity.code());
        }
        
        private IndexedRecord newIndexedRecord(
            final Multiplicity multiplicity
        ) throws SAXException {
            try {
                return recordFactory.createIndexedRecord(multiplicity.code());
            } catch (ResourceException exception) {
                throw new SAXException(exception);
            }
        }
        
        /**
         * Retrieve the interaction's query record
         * 
         * @return the interaction's query record
         */
        MappedRecord getQuery(
            Path xri
        ){
            QueryRecord query = (QueryRecord) this.values.peek();
            if(xri != null) {
                query.setResourceIdentifier(xri);
            }
            return query;
        }

        @SuppressWarnings({
            "unchecked", "cast"
        })
        @Override
        public void endElement(
            String uri, 
            String localName, 
            String name
        ) throws SAXException {
            boolean nestedStructEnd = false;
            try {
                if ("org.openmdx.kernel.ResultSet".equals(name)) {
                    // Nothing to do
                } else if (name.indexOf('.') > 0) {
                    // Object or struct
                    if (this.target instanceof IndexedRecord) {
                        ((IndexedRecord)this.target).add((MappedRecord) this.getObject(null));
                        this.values.pop();
                    } else if (this.target instanceof MessageRecord) {
                        ((MessageRecord)this.target).setResourceIdentifier(this.source.getXRI(this.href));
                        ((MessageRecord)this.target).setBody((MappedRecord) this.values.peekLast());
                        nestedStructEnd = isStructEnding();
                        this.values.pop();
                    } else if(isStructEnding()) {
                        // pop struct
                        nestedStructEnd = true;
                        this.values.pop();
                    }
                } else if(ITEM_TAG.equals(name)) { 
                    propagateData();
                } else if(OBJECTS_TAG.equals(name)) {
                    // nothing to do
                } else {
                    if(this.peekMultivaluedMultiplicity() != null) {
                        this.values.pop();
                    } else if(name.equals(this.featureName) && !ITEM_TAG.equals(this.previousEndElement)) {
                        if(this.isStructureType(this.featureType)) {
                            if(!this.nestedStructEnd) {
                                this.values.pop();
                                // Reset struct in case of null-tag
                                if(this.nestedStructFieldStart && !this.values.isEmpty()) {
                                    MappedRecord holder = (MappedRecord)this.values.peek();
                                    Object featureValue = holder.get(this.featureName);
                                    if(featureValue instanceof IndexedRecord) {
                                        ((IndexedRecord)featureValue).clear();
                                    } else {
                                        holder.put(this.featureName, null);
                                    }
                                }
                            }
                        } else {
                            propagateData();
                        }
                    }
                    if(!this.featureNames.isEmpty()) {
                        this.featureName = this.featureNames.pop();
                        this.featureType = this.featureTypes.pop();
                    } else {
                        this.featureName = null;
                    }
                }
                this.previousEndElement = name;
            } catch (Exception e) {
                throw new SAXException(e);
            }
            this.nestedStructEnd = nestedStructEnd;
            this.nestedStructFieldStart = false;
        }
        
        /**
         * Tells whether struct has been added by startElement()
         * 
         * @return true if struct has been added by startElement()
         * @throws SAXException
         */
        private boolean isStructEnding(
        ) throws SAXException {
            return this.values.size() > 1 && isStructureType(this.values.peek().getRecordName());
        }

        /**
         * @throws ServiceException
         */
        @SuppressWarnings("unchecked")
        private void propagateData(
        ) throws SAXException {
            if (hasData()) {
                java.lang.Object data = getData();
                if(data instanceof String) {
                    //
                    // Map value
                    //
                    String text = (String) data;
                    data =
                        PrimitiveTypes.STRING.equals(featureType) ? text : 
                        PrimitiveTypes.SHORT.equals(featureType) ? Datatypes.create(Short.class, text.trim()) :
                        PrimitiveTypes.LONG.equals(featureType) ? Datatypes.create(Long.class, text.trim()) : 
                        PrimitiveTypes.INTEGER.equals(featureType) ? Datatypes.create(Integer.class, text.trim()) : 
                        PrimitiveTypes.DECIMAL.equals(featureType) ? Datatypes.create(BigDecimal.class,text.trim()) : 
                        PrimitiveTypes.BOOLEAN.equals(featureType) ? Datatypes.create(Boolean.class, text.trim()) : 
                        PrimitiveTypes.OBJECT_ID.equals(featureType) ? Datatypes.create(Path.class, text.trim()) :
                        PrimitiveTypes.DATETIME.equals(featureType) ? Datatypes.create(Date.class,text.trim()) : 
                        PrimitiveTypes.DATE.equals(featureType) ? Datatypes.create(XMLGregorianCalendar.class, text.trim()) : 
                        PrimitiveTypes.ANYURI.equals(featureType) ? Datatypes.create(URI.class, text.trim()) : 
                        PrimitiveTypes.BINARY.equals(featureType) ? Base64.decode(text.trim()) : 
                        featureType != null && isClassType() ? new Path(text.trim()) : text;
                }
                if(data == null && this.multiplicity != Multiplicity.SINGLE_VALUE && this.multiplicity != Multiplicity.OPTIONAL) {
                    SysLog.warning(
                        "Null feature for the given multiplicity ignored",
                        multiplicity
                    );
                } else {
                    switch(this.multiplicity) {
                        case SINGLE_VALUE: case OPTIONAL:
                            ((MappedRecord) this.values.peek()).put(this.featureName, data);
                            break;
                        case LIST: case SET:  
                            ((IndexedRecord) this.value).add(data); // TODO honour index
                            break;
                        case SPARSEARRAY:
                            ((MappedRecord) this.value).put(
                                Integer.valueOf(this.index), 
                                data
                            );
                            break;
                        case STREAM:
                            ((MappedRecord) this.values.peek()).put(
                                this.featureName,
                                PrimitiveTypes.BINARY.equals(featureType) ? BinaryLargeObjects.valueOf((byte[]) data) : 
                                PrimitiveTypes.STRING.equals(featureType) ? CharacterLargeObjects.valueOf((String) data) : 
                                data
                            );
                            break;
                        default:
                            SysLog.warning(
                                "Unsupported multiplicity, feature ignored",
                                multiplicity
                            );
                    }
                }
            }
        }

        private boolean isClassType() throws SAXException {
            try {
                return model.isClassType(featureType);
            } catch (ServiceException exception) {
                throw new SAXException(exception);
            }
        }

        private Multiplicity peekMultivaluedMultiplicity(
        ){
            String type = this.values.peek().getRecordName();
            if(type.indexOf(':') < 0) {
                for(Multiplicity candidate : Multiplicity.values()) {
                    if(candidate.code().equalsIgnoreCase(type)){
                        return candidate;
                    }
                }
            }
            return null;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public void startElement(
            String uri,
            String localName,
            String qName,
            Attributes attributes
        ) throws SAXException {
            super.startElement(uri, localName, qName, attributes);
            boolean nestedStructFieldStart = false;
            try {
                String typeName = null;
                ModelElement_1_0 featureDef = null;
                if(qName.indexOf('.') > 0) {
                    typeName = qName.replace('.', ':');
                } else if(!ITEM_TAG.equals(qName) && qName.indexOf('.') < 0 && !this.values.isEmpty()) {
                    final String recordName = this.values.peek().getRecordName();
                    featureDef = getFeatureDef(
                        recordName,
                        localName
                    );
                    typeName = getFeatureType(featureDef);
                }
                if ("org.openmdx.kernel.ResultSet".equals(qName)) {
                    if (this.target instanceof ResultRecord) {
                        ResultRecord target = (ResultRecord) this.target;
                        String more = attributes.getValue("hasMore");
                        if (more != null) {
                            target.setHasMore(Boolean.parseBoolean(more));
                        }
                        String total = attributes.getValue("total");
                        if (total != null) {
                            target.setTotal(Long.parseLong(total));
                        }
                    }
                } else if(qName.indexOf('.') > 0) {
                    // Begin object or struct
                    MappedRecord mappedRecord = this.newMappedRecord(typeName);
                    if(!this.nestedStructFieldStart) {
                        if(this.isStructureType(typeName)) {
                            // nested struct
                            if(!this.values.isEmpty()) {
                                // multi-valued org:w3c:anyType
                                if(this.values.peek() instanceof IndexedRecord) {
                                    this.values.pop();
                                    this.featureType = typeName;
                                    this.value = mappedRecord;
                                }
                                Object values = ((MappedRecord)this.values.peek()).get(this.featureName);
                                if(values instanceof IndexedRecord) {
                                    IndexedRecord list = (IndexedRecord)values;
                                    if(attributes.getValue("index") != null) {
                                        this.index = attributes.getValue("index");
                                        int index = Integer.parseInt(this.index);
                                        if(index != list.size()) {
                                            throw new ServiceException(
                                                BasicException.Code.DEFAULT_DOMAIN,
                                                BasicException.Code.NOT_SUPPORTED,
                                                "List indices must be ascending and without holes",
                                                new BasicException.Parameter("multiplicity", this.multiplicity),
                                                new BasicException.Parameter("index", this.index),
                                                new BasicException.Parameter("item", mappedRecord)
                                            );
                                        }
                                    }
                                    list.add(mappedRecord);
                                } else {
                                    MappedRecord map = (MappedRecord)values;
                                    this.index = attributes.getValue("index");
                                    map.put(this.index, mappedRecord);                              
                                }
                            }
                        }
                        String href = attributes.getValue("href");
                        if(href != null) {
                             this.href = href;
                        }
                        String version = attributes.getValue("version");
                        if(version != null){
                            this.version = version;
                        }
                        String id = attributes.getValue("id");
                        if(id != null) {
                             this.id = id;
                        }
                        this.values.push(mappedRecord);
                    }
                } else if(ITEM_TAG.equals(qName)) {
                    this.index = attributes.getValue("index");
                    final String featureType = attributes.getValue("type");
                    if(featureType != null) {
                        this.featureType = featureType;
                    }
                } else if(OBJECTS_TAG.equals(qName)) {
                    // nothing to do
                } else {
                    if(this.featureName != null) {
                        this.featureNames.push(this.featureName);
                        this.featureTypes.push(this.featureType);
                    }
                    this.featureName = localName;
                    this.featureType = typeName;
                    this.multiplicity = getMultiplicity(featureDef);
                    if(this.isStructureType(typeName)) {
                        this.value = this.newMappedRecord(typeName);
                        nestedStructFieldStart = true;
                        switch(this.multiplicity) {
                            case OPTIONAL:
                            case SINGLE_VALUE: {                    
                                ((MappedRecord)this.values.peek()).put(
                                    this.featureName, 
                                    this.value
                                );
                                break;
                            }
                            case SET: {
                                IndexedRecord set = (IndexedRecord)((MappedRecord)this.values.peek()).get(this.featureName);
                                if(set == null) {
                                    ((MappedRecord)this.values.peek()).put(
                                        this.featureName, 
                                        set = newIndexedRecord(this.multiplicity) 
                                    );
                                }
                                set.add(this.value);
                                break;
                            }
                            case LIST: {
                                IndexedRecord list = (IndexedRecord)((MappedRecord)this.values.peek()).get(this.featureName);
                                if(list == null) {
                                    ((MappedRecord)this.values.peek()).put(
                                        this.featureName, 
                                        list = newIndexedRecord(this.multiplicity)
                                    );
                                }
                                list.add(this.value);
                                break;
                            }
                            case SPARSEARRAY: case MAP: {
                                MappedRecord map = (MappedRecord)((MappedRecord)this.values.peek()).get(this.featureName);
                                if(map == null) {
                                    ((MappedRecord)this.values.peek()).put(
                                        this.featureName, 
                                        map = newMappedRecord(this.featureType) 
                                    );
                                }
                                this.index = attributes.getValue("index");
                                map.put(this.index, this.value);                                                              
                                break;
                            }
                            default:
                                throw new ServiceException(
                                    BasicException.Code.DEFAULT_DOMAIN,
                                    BasicException.Code.ASSERTION_FAILURE,
                                    "Unexpected multiplicity",
                                    new BasicException.Parameter("multiplicity", multiplicity)
                                );
                        }
                        this.values.push(this.value);
                    } else {
                        switch(this.multiplicity) {
                            case LIST: case SET: {
                                final MappedRecord holder = (MappedRecord) this.values.peek();
                                this.value = (Record) holder.get(this.featureName);
                                if(this.value == null) {
                                    final Multiplicity multiplicity2 = this.multiplicity;
                                    holder.put(
                                        this.featureName, 
                                        this.value = newIndexedRecord(multiplicity2)
                                    );
                                } else {
                                    ((IndexedRecord)this.value).clear();
                                }
                                this.values.push(this.value);
                                break;
                            }
                            case SPARSEARRAY: {
                                final MappedRecord holder = (MappedRecord) this.values.peek();
                                this.value = (Record) holder.get(this.featureName);
                                if(this.value == null) {
                                    holder.put(
                                        this.featureName, 
                                        this.value = newMappedRecord(this.multiplicity)
                                    );
                                } else {
                                    ((MappedRecord)this.value).clear();
                                }
                                this.values.push(this.value);
                                break;
                            }
                            case OPTIONAL: {
                                final MappedRecord holder = (MappedRecord) this.values.peek();
                                this.value = (Record) holder.get(this.featureName);
                                if(this.value == null && !holder.containsKey(this.featureName)) {
                                    holder.put(
                                        this.featureName, 
                                        this.value
                                    );
                                }
                                break;
                            }
                            default:
                                this.value = null;
                        }
                    }
                }
            } catch (Exception exception) {
                throw new SAXException(exception);
            }
            this.nestedStructFieldStart = nestedStructFieldStart;
        }

        /**
         * Retrieve the feature definition
         * 
         * @param typeName
         * @param featureName
         * 
         * @return the feature definition
         * 
         * @throws ServiceException
         */
        protected ModelElement_1_0 getFeatureDef(
            String typeName,
            String featureName)
        throws ServiceException {
            return ControlObjects_2.isControlObjectType(typeName) ? null : model.getFeatureDef(
                model.getElement(typeName),
                featureName,
                false // includeSubtypes
            );
        }

        /**
         * Retrieve the feature type
         * 
         * @param featureDef
         *            feature definition
         * 
         * @return the feature type
         * 
         * @throws ServiceException
         */
        protected String getFeatureType(ModelElement_1_0 featureDef)
        throws ServiceException {
            return featureDef == null ? 
                PrimitiveTypes.STRING : 
                (String) model.getElementType(featureDef).getQualifiedName();
        }

        /**
         * Retrieve the feature's multiplicity
         * 
         * @param featureDef
         *            feature definition
         * 
         * @return the feature's multiplicity
         * 
         * @throws ServiceException
         */
        protected Multiplicity getMultiplicity(
            ModelElement_1_0 featureDef
        ) throws ServiceException {
            return featureDef == null ? Multiplicity.OPTIONAL : ModelHelper.getMultiplicity(featureDef);
        }

        /**
         * Tell whether a given record is a structure type
         * 
         * @param typeName
         * 
         * @return true in case of structure, false in
         *         case of object
         *         
         * @throws SAXException
         */
        protected boolean isStructureType(
            String typeName
        ) throws SAXException{
            try {
                return 
                    !ControlObjects_2.isControlObjectType(typeName) &&  
                    model.isStructureType(typeName);
            } catch (ServiceException exception) {
                throw new SAXException(exception);
            }
        }

        /**
         * Tell whether a given record is query type
         * 
         * @param typeName
         * 
         * @return true in case of query, false
         *         otherwise
         * 
         * @throws ServiceException
         */
        protected boolean isQueryType(
            String typeName
        ){
            return QueryRecord.NAME.equals(typeName);
        }

    }

    
    // ------------------------------------------------------------------------
    // Class ExceptionHandler
    // ------------------------------------------------------------------------

    /**
     * Content handler which maps an XML encoded values to BasicExceptions
     */
    static class ExceptionHandler extends AbstractHandler {

        /**
         * Constructor to parse an object or a record
         */
        ExceptionHandler() {
        }

        private String exceptionDomain;

        private String exceptionCode;

        private String exceptionTime;

        private String exceptionClass;

        private String methodName;

        private String lineNumber;

        private String description;

        private String parameter_id;

        private String stackTraceElement_declaringClass;
        
        private String stackTraceElement_fileName;
        
        private String stackTraceElement_methodName;
        
        private String stackTraceElement_lineNumber;
        
        private String stackTraceElements_more;

        private List stackTraceElements = new ArrayList();
        
        private StackTraceElement[] stackTrace;
        
        private final Map parameters = new HashMap();

        private BasicException stack;

        /**
         * Retrieve the exception stack
         * 
         * @return the exception stack
         * 
         * @throws ServiceException
         */
        BasicException getValue(
        ){
            return this.stack;
        }

        @Override
        public void endElement(String uri, String localName, String name)
        throws SAXException {
            try {
                if (ITEM_TAG.equals(name)) {
                    if(this.parameter_id != null) {
                        this.parameters.put(
                            this.parameter_id, 
                            super.hasData() ? (String)getData() : null
                        );
                    } else if (this.stackTraceElement_declaringClass != null){
                        this.stackTraceElements.add(
                            new StackTraceElement(
                                this.stackTraceElement_declaringClass, 
                                this.stackTraceElement_methodName, 
                                this.stackTraceElement_fileName, 
                                Integer.parseInt(this.stackTraceElement_lineNumber)
                            )
                        );
                    }
                } else if ("description".equals(name)) {
                    this.description = (String) getData();
                } else if ("element".equals(name)) {
                    BasicException.Parameter[] parameters =
                        new BasicException.Parameter[this.parameters.size()];
                    int i = 0;
                    for (Map.Entry parameter : this.parameters.entrySet()) {
                        parameters[i++] = new BasicException.Parameter(
                            parameter.getKey(),
                            parameter.getValue()
                        );
                    }
                    BasicException element = new BasicException(
                        this.exceptionDomain,
                        Integer.parseInt(this.exceptionCode),
                        this.exceptionClass,
                        this.exceptionTime == null ? null : DateTimeFormat.EXTENDED_UTC_FORMAT.parse(exceptionTime),
                        this.methodName,
                        this.lineNumber == null ? null : Integer.valueOf(this.lineNumber),
                        this.description,
                        parameters
                    );
                    element.setStackTrace(this.stackTrace);
                    if (this.stack == null) {
                        this.stack = element;
                    } else {
                        this.stack.getCause(null).initCause(element);
                    }
                } else if ("stackTraceElements".equals(name)) {
                    int stackTraceElementCount = this.stackTraceElements.size();
                    if(this.stackTraceElements_more == null || "0".equals(this.stackTraceElements_more)) {
                        this.stackTrace = this.stackTraceElements.toArray(new StackTraceElement[stackTraceElementCount]);
                    } else {
                        int more = Integer.parseInt(stackTraceElements_more);
                        StackTraceElement[] stackTrace = this.stackTraceElements.toArray(new StackTraceElement[stackTraceElementCount + more]);
                        System.arraycopy(this.stackTrace, this.stackTrace.length - more, stackTrace, stackTraceElementCount, more);
                        this.stackTrace = stackTrace;
                    }
                }
            } catch (Exception e) {
                throw new SAXException(e);
            }
        }

        @Override
        public void startElement(
            String uri,
            String localName,
            String name,
            Attributes attributes)
        throws SAXException {
            super.startElement(uri, localName, name, attributes);
            if ("element".equals(name)) {
                this.exceptionDomain = attributes.getValue("exceptionDomain");
                this.exceptionCode = attributes.getValue("exceptionCode");
                this.exceptionTime = attributes.getValue("exceptionTime");
                this.exceptionClass = attributes.getValue("exceptionClass");
                this.methodName = attributes.getValue("methodName");
                this.lineNumber = attributes.getValue("lineNumber");
            } else if ("parameter".equals(name)) {
                this.parameters.clear();
            } else if ("stackTraceElements".equals(name)) {
                this.stackTraceElements.clear();
                this.stackTraceElements_more = attributes.getValue("more");
            } else if (ITEM_TAG.equals(name)) {
                this.parameter_id = attributes.getValue("id");
                this.stackTraceElement_declaringClass = attributes.getValue("declaringClass");
                if(this.stackTraceElement_declaringClass == null) {
                    this.stackTraceElement_methodName = null;
                    this.stackTraceElement_fileName = null;
                    this.stackTraceElement_lineNumber = null;
                } else {
                    this.stackTraceElement_methodName = attributes.getValue("methodName");
                    this.stackTraceElement_fileName = attributes.getValue("fileName");
                    this.stackTraceElement_lineNumber = attributes.getValue("lineNumber");
                }
            }
        }

    }

    
    // ------------------------------------------------------------------------
    // Class AbstractHandler
    // ------------------------------------------------------------------------

    /**
     * Error handler and content handler for character data
     */
    static class AbstractHandler extends DefaultHandler implements LargeObjectWriter {

        /**
         * Constructor
         */
        AbstractHandler() {
        }

        /**
         * The character data
         */
        private final StringBuilder characterData = new StringBuilder();

        /**
         * Tells whether some character data has been read.
         */
        private boolean hasCharacterData = false;

        /**
         * binary data
         */
        private byte[] binaryData = null;

        /**
         * Tells whether there is either character or binary content available
         * 
         * @return true if some content is available
         */
        protected boolean hasData(
        ){
            return this.hasCharacterData || binaryData != null;
        }
        
        /**
         * Retrieve the elements character data
         * 
         * @return the elements character data
         */
        protected Object getData() {
            return this.hasCharacterData ? characterData.toString() : this.binaryData;
        }

        @Override
        public void error(
            SAXParseException exception
        ) throws SAXException {
            StringBuilder locationString = new StringBuilder();
            String systemId = exception.getSystemId();
            if (systemId != null) {
                int index = systemId.lastIndexOf('/');
                if (index != -1) {
                    systemId = systemId.substring(index + 1);
                }
                locationString.append(systemId);
            }
            locationString.append(
                ':'
            ).append(
                exception.getLineNumber()
            ).append(
                ':'
            ).append(
                exception.getColumnNumber()
            );
            throw Throwables.log(
                BasicException.initHolder(
                    new SAXException(
                        "XML parse error",
                        BasicException.newEmbeddedExceptionStack(
                            exception,
                            BasicException.Code.DEFAULT_DOMAIN,
                            BasicException.Code.PROCESSING_FAILURE,
                            new BasicException.Parameter("message", exception.getMessage()),
                            new BasicException.Parameter("location", locationString),
                            new BasicException.Parameter("systemId", systemId),
                            new BasicException.Parameter("lineNumber", exception.getLineNumber()),
                            new BasicException.Parameter("columnNumber", exception.getColumnNumber())
                        )
                    )
                )
            );
        }

        @Override
        public void characters(
            char[] ch, 
            int start, 
            int length
        ) throws SAXException {
            this.hasCharacterData = true;
            this.characterData.append(ch, start, length);
        }

        @Override
        public void startElement(
            String uri,
            String localName,
            String name,
            Attributes attributes)
        throws SAXException {
            this.hasCharacterData = false;
            this.characterData.setLength(0);
            this.binaryData = null;
        }

        /* (non-Javadoc)
         * @see org.openmdx.base.xml.stream.LargeObjectWriter#writeBinaryData(byte[], int, int)
         */
        @Override
        public void writeBinaryData(
            byte[] data, 
            int offset, 
            int length
        ){
            this.binaryData = offset == 0 && length == data.length ? data : ArraysExtension.copyOfRange(data, offset, offset + length);           
        }

        /* (non-Javadoc)
         * @see org.openmdx.base.xml.stream.LargeObjectWriter#writeBinaryData(org.w3c.cci2.BinaryLargeObject)
         */
        @Override
        public void writeBinaryData(
            BinaryLargeObject data
        ){
            throw new UnsupportedOperationException("Large object streaming not yet implemented");
        }

        /* (non-Javadoc)
         * @see org.openmdx.base.xml.stream.LargeObjectWriter#writeCharacterData(org.w3c.cci2.CharacterLargeObject)
         */
        @Override
        public void writeCharacterData(
            CharacterLargeObject data
        ){
            throw new UnsupportedOperationException("Large object streaming not yet implemented");
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy