com.quinsoft.zeidon.standardoe.WriteOisToJsonStream.java Source code

Java tutorial

Introduction

Here is the source code for com.quinsoft.zeidon.standardoe.WriteOisToJsonStream.java

Source

/**
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 <http://www.gnu.org/licenses/>.
    
Copyright 2009-2015 QuinSoft
 */
package com.quinsoft.zeidon.standardoe;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
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 org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.joda.time.format.ISODateTimeFormat;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.quinsoft.zeidon.SelectSet;
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<? extends View> viewList;
    private SerializeOi options;
    private EnumSet<WriteOiFlags> flags;
    private final Set<ObjectInstance> ois = new HashSet<ObjectInstance>();
    private JsonGenerator jg;
    private View currentView;
    private final LinkedHashMap<String, Object> linkedMap = new LinkedHashMap<>(5);

    @Override
    public void writeToStream(SerializeOi options, Writer writer) {
        this.viewList = options.getViewList();
        this.options = options;
        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.writeStringField("datetime", new LocalDateTime().toString());
            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<Long, SelectSet> 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 {
        jg.writeObjectFieldStart(".oimeta");
        jg.writeStringField("application", view.getLodDef().getApplication().getName());
        jg.writeStringField("odName", view.getLodDef().getName());
        jg.writeBooleanField("incremental", true);
        if (((InternalView) view).getViewImpl().getObjectInstance().isReadOnly())
            jg.writeBooleanField("readOnlyOi", true);
        else if (view.isReadOnly())
            jg.writeBooleanField("readOnly", true);

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

        jg.writeEndObject();
    }

    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(camelCaseName(entityDef.getName()));
            }

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

                if (attributeDef.isDerived())
                    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 = camelCaseName(attributeDef.getName());
                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)
                    jg.writeStringField(jsonName, ISODateTimeFormat.dateTime().print((DateTime) attrib.getValue()));
                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("." + camelCaseName(attributeDef.getName()));
        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;
    }
}