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

com.quinsoft.zeidon.standardoe.ActivateOisFromXmlStream Maven / Gradle / Ivy

The newest version!
/**
    This file is part of the Zeidon Java Object Engine (Zeidon JOE).

    Zeidon JOE is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zeidon JOE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Zeidon JOE.  If not, see .

    Copyright 2009-2015 QuinSoft
 */
package com.quinsoft.zeidon.standardoe;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Deque;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.lang3.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import com.quinsoft.zeidon.ActivateFlags;
import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.CreateEntityFlags;
import com.quinsoft.zeidon.CursorPosition;
import com.quinsoft.zeidon.DeserializeOi;
import com.quinsoft.zeidon.SerializationMapping;
import com.quinsoft.zeidon.StreamReader;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.objectdefinition.AttributeDef;
import com.quinsoft.zeidon.objectdefinition.EntityDef;
import com.quinsoft.zeidon.objectdefinition.LodDef;

/**
 * @author dgc
 *
 */
class ActivateOisFromXmlStream implements StreamReader
{
    private static final EnumSet CREATE_FLAGS = EnumSet.of( CreateEntityFlags.fNO_SPAWNING,
                                                                               CreateEntityFlags.fIGNORE_MAX_CARDINALITY,
                                                                               CreateEntityFlags.fDONT_UPDATE_OI,
                                                                               CreateEntityFlags.fDONT_INITIALIZE_ATTRIBUTES,
                                                                               CreateEntityFlags.fIGNORE_PERMISSIONS );

    private Task        task;
    private InputStream inputStream;
    private boolean     ignoreInvalidEntityNames;
    private boolean     ignoreInvalidAttributeNames;

    private Application              application;
    private LodDef                   lodDef;

    /**
     * Current view being read.
     */
    private ViewImpl view;

    /**
     * List of returned views.
     */
    private final List viewList = new ArrayList<>();

    private EnumSet   control;
    private boolean                  incremental = false;
    private final Stack  entityAttributes = new Stack();
    private final Stack  attributeAttributes = new Stack();
    private final Deque   currentEntityStack = new LinkedList();
    private EntityDef                currentEntityDef;
    private StringBuilder            characterBuffer;

    /**
     * Used to keep track of the instances that are flagged as selected in the input
     * stream.  Cursors will be set afterwards.
     */
    private List selectedInstances;
    private SerializationMapping mapper;

    /**
     * Keeps track of current location in SAX parser.
     */
    private Locator locator;

    private ViewImpl read()
    {
        try
        {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

            SAXParser saxParser = factory.newSAXParser();
            DefaultHandler handler = new SaxParserHandler();
            saxParser.parse( inputStream, handler );

            // If user wanted just one root remove others if we have more than one.
            // We don't want to abort the loading of entities in the middle of
            // the stream because that could throw off XML processing.
            EntityCursorImpl rootCursor = view.getViewCursor().getEntityCursor( lodDef.getRoot() );
            if ( control.contains( ActivateFlags.fSINGLE ) && rootCursor.getEntityCount() > 1 )
            {
                rootCursor.setFirst();
                while ( rootCursor.setNext().isSet() )
                    rootCursor.dropEntity();
                rootCursor.setFirst();
            }

            if ( selectedInstances.size() > 0 )
                setCursors();
            else
                view.reset();

            return view;
        }
        catch ( Exception e )
        {
            ZeidonException ze = ZeidonException.wrapException( e );
            if ( locator != null )
                ze.appendMessage( "Line/col = %d/%d", locator.getLineNumber(), locator.getColumnNumber() );

            throw ze;
        }
    }

    private boolean isYes( String str )
    {
        if ( StringUtils.isBlank( str ) )
            return false;

        switch ( str.toUpperCase().charAt( 0 ) )
        {
            case 'Y':
            case '1':
            case 'T':
                return true;

            default:
                return false;
        }
    }

    /**
     * The view has been loaded from the stream and it was indicated that there are
     * cursor selections.  Reset them.
     */
    private void setCursors()
    {
        for ( EntityInstanceImpl ei : selectedInstances )
        {
            EntityDef entityDef = ei.getEntityDef();
            EntityCursorImpl cursor = view.cursor( entityDef );

            // Use setEntityInstance() because we are setting all cursors.  This is
            // faster than using setCursor().
            cursor.setEntityInstance( ei );
        }
    }

    /**
     * Called to handle the zOI entity.
     *
     * @param qName
     * @param attributes
     */
    private void createOi( String qName, Attributes attributes )
    {
        String appName = attributes.getValue( "appName" );
        if ( StringUtils.isBlank( appName ) )
            throw new ZeidonException("zOI element does not specify appName" );

        application = task.getApplication( appName );
        String odName = attributes.getValue( "recordName" );
        if ( StringUtils.isBlank( odName ) )
            throw new ZeidonException("zOI element does not specify recordName" );

        lodDef = application.getLodDef( task, odName );
        view = (ViewImpl) task.activateEmptyObjectInstance( lodDef );
        viewList.add( view );

        String increFlags = attributes.getValue( "increFlags" );
        if ( ! StringUtils.isBlank( increFlags ) )
            incremental = isYes( increFlags );

        String rootCount = attributes.getValue( "totalRootCount" );
        if ( ! StringUtils.isBlank( rootCount ) )
            view.setTotalRootCount( Integer.parseInt( rootCount ) );

        // Create a list to keep track of selected instances.
        selectedInstances = new ArrayList<>();
    }


    private void createEntity( EntityDef entityDef, Attributes attributes )
    {
        currentEntityDef = entityDef;
        currentEntityStack.push( currentEntityDef );
        EntityCursorImpl cursor = view.cursor( currentEntityDef );
        cursor.createEntity( CursorPosition.LAST, CREATE_FLAGS );

        // If we're setting incremental flags, save them for later.  Create
        // a copy of the AttributesImpl because the original gets reused.
        if ( incremental )
            entityAttributes.push( new AttributesImpl( attributes ) );
    }

    private void setAttribute( String attributeName, Attributes attributes )
    {
        characterBuffer = new StringBuilder();

        if ( incremental )
            attributeAttributes.push( attributes );
    }

    private class SaxParserHandler extends DefaultHandler
    {
        // this will be called when XML-parser starts reading
        // XML-data; here we save reference to current position in XML:
        @Override
        public void setDocumentLocator(Locator locator)
        {
            ActivateOisFromXmlStream.this.locator = locator;
        }

        @Override
        public void startElement( String uri,
                                  String localName,
                                  String qName,
                                  Attributes attributes ) throws SAXException
        {
            if ( StringUtils.equalsIgnoreCase( qName, "zOIs" ) )
            {
                // Nothing to do.
                return;
            }

            if ( StringUtils.equalsIgnoreCase( qName, "zOI" ) )
            {
                createOi( qName, attributes );
                return;
            }

            // If we get here then we better have a view.
            if ( view == null )
                throw new ZeidonException( "XML stream does not specify zOI element" );

            if ( currentEntityDef != null && currentEntityDef.getAttribute( qName, false ) != null )
            {
                setAttribute( qName, attributes );
                return;
            }

            // Is the element name an entity name?
            EntityDef entityDef = mapper.getEntityFromRecord( qName, currentEntityStack.peekFirst(), lodDef );
            if ( entityDef != null )
            {
                createEntity( entityDef, attributes );
                return;
            }

            // If we get here then we don't know what we have.  If user has specified
            // that we're to ignore entity or attribute errors then we'll just assume that
            // this element is an old entity/attribute name and we can ignore it.
            if ( ignoreInvalidAttributeNames || ignoreInvalidEntityNames )
                return;

            throw new ZeidonException( "Unknown XML element: %s", qName );
        }

        @Override
        public void endElement( String uri, String localName, String qName ) throws SAXException
        {
            // Is the element an attribute name?
            AttributeDef attributeDef = currentEntityDef.getAttribute( qName, false );
            if ( attributeDef != null )
            {
                if ( characterBuffer == null )
                {
                    // If we get here then we should be in a situation where an attribute name
                    // is the same as its containing entity.  We've already read the attribute
                    // so qName should be the entity name.  Verify.
                    assert lodDef.getEntityDef( qName, false ) != null : "Unexpected null characterBuffer";
                }
                else
                {
                    EntityInstanceImpl ei = view.cursor( attributeDef.getEntityDef() ).getEntityInstance();
                    ei.getAttribute( attributeDef ).setInternalValue( characterBuffer.toString(), false ) ;

                    characterBuffer = null; // Indicates we've read the attribute.

                    if ( incremental )
                    {
                        Attributes attributes = attributeAttributes.pop();
                        ei.getAttribute( attributeDef ).setIsUpdated( isYes( attributes.getValue( "updated" ) ) );
                    }
                    else
                    {
                        // If we just set the key then we'll assume the entity has
                        // already been created.
                        if ( attributeDef.isKey() )
                            ei.setIncrementalFlags( IncrementalEntityFlags.UPDATED );
                    }

                    return;
                }
            }

            // Is the element name an entity name?
            EntityDef entityDef = mapper.getEntityFromRecord( qName, currentEntityDef.getParent(), lodDef );
            if ( entityDef != null )
            {
                assert entityDef == currentEntityDef : "Mismatching entity names in XML";

                if ( incremental )
                {
                    EntityInstanceImpl ei = view.cursor( entityDef ).getEntityInstance();
                    Attributes attributes = entityAttributes.pop();

                    ei.setUpdated( isYes( attributes.getValue( "updated" ) ) );
                    ei.setCreated( isYes( attributes.getValue( "created" ) ) );
                    ei.setIncluded( isYes( attributes.getValue( "included" ) ) );
                    ei.setExcluded( isYes( attributes.getValue( "excluded" ) ) );
                    ei.setDeleted( isYes( attributes.getValue( "deleted" ) ) );
                    if ( isYes( attributes.getValue( "incomplete" ) ) )
                        ei.setIncomplete( null );
                    if ( isYes( attributes.getValue( "selected" ) ) )
                        selectedInstances.add( ei );
                    String lazyLoaded = attributes.getValue( "lazyLoaded" );
                    if ( ! StringUtils.isBlank( lazyLoaded ) )
                    {
                        String[] names = lazyLoaded.split( "," );
                        for ( String name: names )
                            ei.getEntitiesLoadedLazily().add( lodDef.getEntityDef( name ) );
                    }
                }

                // The top of the stack equals currentEntityDef.  Pop it off the stack and
                // set currentEntityDef to the next item in the stack.
                currentEntityStack.pop();
                if ( currentEntityDef.getParent() != null ) // Is it root?
                    currentEntityDef = currentEntityStack.peek();
                return;
            }

            if ( StringUtils.equalsIgnoreCase( qName, "zOI" ) )
            {
                return;
            }

            if ( StringUtils.equalsIgnoreCase( qName, "zOIs" ) )
            {
                return;
            }

            throw new ZeidonException( "Unexpected qname: %s", qName );
        } // endElement

        @Override
        public void characters( char[] ch, int start, int length ) throws SAXException
        {
            if ( characterBuffer == null )
                return;  // We don't need to capture the characters.

            characterBuffer.append( ch, start, length );
        }
    } // class SaxParserHandler

    @Override
    public List readFromStream( DeserializeOi options )
    {
        this.task = options.getTask();
        control = options.getFlags();
        this.inputStream = options.getInputStream();;
        ignoreInvalidEntityNames = control.contains( ActivateFlags.fIGNORE_ENTITY_ERRORS );
        ignoreInvalidAttributeNames = control.contains( ActivateFlags.fIGNORE_ATTRIB_ERRORS );
        mapper = options.getSerializationMapping();

        read();
        return viewList;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy