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

com.quinsoft.zeidon.standardoe.WriteOisToJsonStream 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.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.quinsoft.zeidon.ActivateOptions;
import com.quinsoft.zeidon.Pagination;
import com.quinsoft.zeidon.SelectSet;
import com.quinsoft.zeidon.SerializationMapping;
import com.quinsoft.zeidon.SerializeOi;
import com.quinsoft.zeidon.StreamWriter;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.WriteOiFlags;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.domains.BigDecimalDomain;
import com.quinsoft.zeidon.domains.BooleanDomain;
import com.quinsoft.zeidon.domains.DateTimeDomain;
import com.quinsoft.zeidon.domains.Domain;
import com.quinsoft.zeidon.domains.DoubleDomain;
import com.quinsoft.zeidon.domains.IntegerDomain;
import com.quinsoft.zeidon.domains.LongDomain;
import com.quinsoft.zeidon.objectdefinition.AttributeDef;
import com.quinsoft.zeidon.objectdefinition.EntityDef;

/**
 * @author dgc
 *
 */
public class WriteOisToJsonStream implements StreamWriter
{
    private final static String VERSION = "1";

    private Collection viewList;
    private SerializeOi options;
    private EnumSet flags;
    private final Set ois = new HashSet();
    private JsonGenerator jg;
    private View currentView;
    private final LinkedHashMap linkedMap = new LinkedHashMap<>(5);
    private SerializationMapping mapper;

    // 2018-05-23T00:18:23.110-07:00
    private static final DateTimeFormatter JSON_DATE_FORMATTER = DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss.Sxxx" );

    @Override
    public void writeToStream( SerializeOi options, Writer writer )
    {
        this.viewList = options.getViewList();
        this.options = options;
        mapper = options.getSerializationMapping();
        if ( options.getFlags() == null )
            flags = EnumSet.noneOf( WriteOiFlags.class );
        else
            flags = options.getFlags();
        if ( ! flags.contains( WriteOiFlags.INCREMENTAL ) )
            throw new ZeidonException( "This JSON stream writer intended for writing incremental." );

        // Create a set of all the OIs and turn off the record owner flag.  The record owner
        // flag will be used to determine if a linked EI has been written to the stream.
        for ( View view : viewList )
        {
            ObjectInstance oi = ((InternalView) view).getViewImpl().getObjectInstance();
            ois.add( oi );
            for ( EntityInstanceImpl ei = oi.getRootEntityInstance(); ei != null; ei = ei.getNextTwin() )
                ei.setRecordOwner( false );
        }

        JsonFactory jsonF = new JsonFactory();
        try
        {
            jg = jsonF.createGenerator( writer );
            if ( ! options.isCompressed() )
                jg.useDefaultPrettyPrinter(); // enable indentation just to make debug/testing easier

            jg.writeStartObject();

            // Write meta info for entire JSON object.
            jg.writeObjectFieldStart( ".meta" );
            jg.writeStringField( "version", VERSION );
            if ( options.isWriteDate() )
                jg.writeObjectField( "datetime", ZonedDateTime.now() );
            jg.writeEndObject();

            jg.writeArrayFieldStart( "OIs" );

            for ( View view : viewList )
            {
                currentView = view;
                jg.writeStartObject();
                writeOi( view );
                jg.writeEndObject();
            }
            jg.writeEndArray();
            jg.writeEndObject();
            jg.close();
        }
        catch ( Exception e )
        {
            throw ZeidonException.wrapException( e );
        }
    }

    private void writeOi( View view ) throws Exception
    {
        SelectSet rootSelectSet = null;
        Map sets = options.getRootSelectSets();
        if ( sets != null )
            rootSelectSet = sets.get( view.getOiId() );

        writeOiMeta( view );

        EntityDef lastEntityDef = null;

        ViewImpl viewImpl = ((InternalView)view).getViewImpl();
        for ( EntityInstanceImpl ei = viewImpl.getObjectInstance().getRootEntityInstance();
              ei != null;
              ei = ei.getNextTwin() )
        {
            // If we have a root select set and the EI is not selected then skip it.
            if ( rootSelectSet != null && rootSelectSet.isSelected( ei ) )
                continue;

            lastEntityDef = writeEntity( ei, lastEntityDef );
        }

        if ( lastEntityDef != null )
            jg.writeEndArray();
    }

    private void writeOiMeta( View view ) throws Exception
    {
        ObjectInstance oi = ((InternalView) view).getViewImpl().getObjectInstance();
        jg.writeObjectFieldStart( ".oimeta" );
        jg.writeStringField( "application", view.getLodDef().getApplication().getName() );
        jg.writeStringField( "odName", view.getLodDef().getName() );
        jg.writeBooleanField( "incremental", true );
        if ( oi.isReadOnly() )
            jg.writeBooleanField( "readOnlyOi", true );
        else
            if ( view.isReadOnly() )
                jg.writeBooleanField( "readOnly", true );

        if ( oi.isLocked() )
            jg.writeBooleanField( "locked", true );

        Integer rootCount = view.getTotalRootCount();
        if ( rootCount != null )
            jg.writeNumberField( "totalRootCount", rootCount );

        writePagination( view );
        jg.writeEndObject();
    }

    private void writePagination( View view ) throws Exception
    {
        ActivateOptions options = view.getActivateOptions();
        if ( options == null )
            return;

        Pagination paging = options.getPagingOptions();
        if ( paging == null )
            return;

        jg.writeObjectFieldStart( "pagination" );
        jg.writeNumberField( "pageSize", paging.getPageSize() );
        jg.writeNumberField( "currentPage", paging.getPageNumber() );

        Integer rootCount = view.getTotalRootCount();
        if ( rootCount != null )
        {
            jg.writeNumberField( "totalCount", rootCount );
            jg.writeNumberField( "totalPages", rootCount / paging.getPageSize() + 1 );
        }

        jg.writeEndObject();
    }

    /**
     * Convert the name of the EntityDef to the object name used in JSON.
     * @param entityDef
     * @return
     */
    private String objectName( EntityDef entityDef )
    {
        String objectName = mapper.entityToRecord( entityDef );
        return camelCaseName( objectName );
    }

    private String fieldName( AttributeDef attributeDef )
    {
        String fieldName = mapper.attributeToField( attributeDef );
        return camelCaseName( fieldName );
    }

    private String camelCaseName( String name )
    {
        if ( ! options.isCamelCase() )
            return name;

        char[] nameChars = name.toCharArray();
        for ( int i = 0; i < nameChars.length; i++ )
        {
            if ( ! Character.isUpperCase( nameChars[ i ] ) )
                break;

            nameChars[ i ] = Character.toLowerCase( nameChars[ i ] );
        }

        return String.valueOf( nameChars );
    }

    private EntityDef writeEntity( EntityInstanceImpl ei, EntityDef lastEntityDef ) throws Exception
    {
        try
        {
            // See if we need to open or close an array field.
            final EntityDef entityDef = ei.getEntityDef();
            if ( lastEntityDef != entityDef )
            {
                if ( lastEntityDef != null )
                    jg.writeEndArray();

                lastEntityDef = entityDef;
                jg.writeArrayFieldStart( objectName( entityDef ) );
            }

            jg.writeStartObject();
            boolean writePersistent = writeEntityMeta( ei );
            for ( AttributeDef attributeDef : entityDef.getAttributes() )
            {
                // If the attribute is not persistent and we're only writing persistent
                // then go to the next one.
                if ( attributeDef.isPersistent() && ! writePersistent )
                    continue;

                if ( attributeDef.isDerived() && ! options.isWriteDerivedAttributes() )
                    continue;

                if ( attributeDef.isHidden() && ! options.isWriteHiddenAttributes() )
                    continue;

                AttributeInstanceImpl attrib = ei.getAttribute( attributeDef );
                if ( attrib.isNull() && ! attrib.isUpdated() )
                    continue;

                AttributeValue attribValue = ei.getInternalAttribute( attributeDef );

                // Check for integer, double, or boolean so that it gets written without quotes.
                // TODO: Do this more dynamically.
                Domain domain = attributeDef.getDomain();
                String jsonName = fieldName( attributeDef );
                if ( domain instanceof IntegerDomain )
                    jg.writeNumberField( jsonName, attrib.getInteger() );
                else
                if ( domain instanceof DoubleDomain )
                    jg.writeNumberField( jsonName, attrib.getDouble() );
                else
                if ( domain instanceof BooleanDomain )
                    jg.writeBooleanField( jsonName, attrib.getBoolean() );
                else
                if ( domain instanceof LongDomain )
                    jg.writeNumberField( jsonName, (Long) attrib.getValue() );
                else
                if ( domain instanceof BigDecimalDomain )
                    jg.writeNumberField( jsonName, (BigDecimal) attrib.getValue() );
                else
                if ( domain instanceof DateTimeDomain ) {
                    ZonedDateTime date = (ZonedDateTime) attrib.getValue();
                    String str = JSON_DATE_FORMATTER.format( date );
                    jg.writeStringField( jsonName, str );
                }
                else
                {
                    String value = attribValue.getString( ei.getTask(), attributeDef );
                    jg.writeStringField( jsonName, value );
                }

                if ( attributeDef.isPersistent() )
                    writeAttributeMeta( attribValue, attributeDef );
            }

            // Loop through the children and add them.
            EntityDef lastChildEntityDef = null;
            for ( EntityInstanceImpl child : ei.getDirectChildren( true, false ) )
            {
                lastChildEntityDef = writeEntity( child, lastChildEntityDef );
            }

            if ( lastChildEntityDef != null )
                jg.writeEndArray();

            jg.writeEndObject();

            return entityDef;
        }
        catch ( Exception e )
        {
            throw ZeidonException.wrapException( e ).prependEntityInstance( ei );
        }
    }

    private void writeAttributeMeta( AttributeValue attrib, AttributeDef attributeDef )
                        throws JsonGenerationException, IOException
    {
        if ( ! attrib.isUpdated() )
            return;

        jg.writeObjectFieldStart( "." + fieldName( attributeDef ) );
        jg.writeStringField( "updated", "true" );
        jg.writeEndObject();
    }

    private String createIncrementalStr( EntityInstanceImpl ei )
    {
        String str = "";

        if ( ei.isUpdated() )
            str += 'U';

        if ( ei.isCreated() )
            str += 'C';

        if ( ei.isDeleted() )
            str += 'D';

        if ( ei.isIncluded() )
            str += 'I';

        if ( ei.isExcluded() )
            str += 'X';

        return str;
    }

    /**
     * If this EI is linked with another EI in the OI set then this returns the record owner.
     * If no record owner, returns EI.
     *
     * @param ei
     * @return
     */
    private EntityInstanceImpl findLinkedRecordOwner( EntityInstanceImpl ei )
    {
        // Keep track of whether we found another EI linked with this one.
        boolean foundLinked = false;

        // Run through the list of the other linked instances.
        for ( EntityInstanceImpl linked : ei.getLinkedInstances() )
        {
            if ( ois.contains( linked.getObjectInstance() ) )
            {
                foundLinked = true;
                if ( linked.isRecordOwner() )
                    return linked;
            }
        }

        // If we get here then we didn't find a record owner.  if foundLinked is true
        // then we did find a linked EI.
        if ( foundLinked )
            return ei;

        // Didn't find any EI's that are linked with ei.
        return null;
    }

    /**
     * Returns true if we should also write the persistent attributes of the EI.  We
     * don't write the persistent attributes if the EI is linked and it's not the
     * record owner.
     *
     * @param ei
     * @return
     * @throws Exception
     */
    private boolean writeEntityMeta( EntityInstanceImpl ei ) throws Exception
    {
        boolean writeAttributes = true;
        EntityInstanceImpl recordOwner = findLinkedRecordOwner( ei );

        EntityDef entityDef = ei.getEntityDef();
        boolean selectedCursor = currentView.cursor( entityDef ).getEntityInstance() == ei;

        linkedMap.clear();

        String str = createIncrementalStr( ei );
        if ( ! StringUtils.isBlank( str ) )
            linkedMap.put( "incrementals", str );

        if ( selectedCursor && options.isWithCursors() )
            linkedMap.put( "selected", true );

        if ( ei.isIncomplete() )
            linkedMap.put( "incomplete", true );

        if ( ei.hasLoadedLazyChildren() )
        {
            StringBuilder sb = new StringBuilder();
            for ( EntityDef def : ei.getEntitiesLoadedLazily() )
                sb.append( "," ).append( def.getName() );
            sb.deleteCharAt( 0 );
            linkedMap.put( "lazyLoaded", sb.toString() );
        }

        if ( recordOwner != null )
        {
            if ( recordOwner == ei )
            {
                // TODO: validate that ei.entityDef has all the attributes in the shared
                // attribute hash.
                ei.setRecordOwner( true );
                linkedMap.put( "isLinkedSource", true );
                linkedMap.put( "entityKey", Long.toString( ei.getEntityKey() ) );
            }
            else
            {
                // Write the entity key of the record owner.
                linkedMap.put( "linkedSource", Long.toString( recordOwner.getEntityKey() ));
                writeAttributes = false;
            }
        }

        if ( linkedMap.size() == 0 )
            return writeAttributes;

        jg.writeObjectFieldStart( ".meta" );

        for ( String key : linkedMap.keySet() )
            jg.writeObjectField( key, linkedMap.get( key ) );

        jg.writeEndObject();

        return writeAttributes;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy