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