Package org.apache.isis.objectstore.xml.internal.data.xml

Source Code of org.apache.isis.objectstore.xml.internal.data.xml.XmlDataManager

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/

package org.apache.isis.objectstore.xml.internal.data.xml;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.Vector;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.commons.ensure.Assert;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.commons.xml.ContentWriter;
import org.apache.isis.core.commons.xml.XmlFile;
import org.apache.isis.core.metamodel.adapter.oid.Oid;
import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
import org.apache.isis.core.metamodel.adapter.oid.RootOid;
import org.apache.isis.core.metamodel.adapter.oid.RootOidDefault;
import org.apache.isis.core.metamodel.adapter.version.Version;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
import org.apache.isis.core.runtime.persistence.ObjectNotFoundException;
import org.apache.isis.core.runtime.persistence.ObjectPersistenceException;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.objectstore.xml.internal.data.CollectionData;
import org.apache.isis.objectstore.xml.internal.data.Data;
import org.apache.isis.objectstore.xml.internal.data.DataManager;
import org.apache.isis.objectstore.xml.internal.data.ListOfRootOid;
import org.apache.isis.objectstore.xml.internal.data.ObjectData;
import org.apache.isis.objectstore.xml.internal.data.ObjectDataVector;
import org.apache.isis.objectstore.xml.internal.data.PersistorException;
import org.apache.isis.objectstore.xml.internal.version.FileVersion;

public class XmlDataManager implements DataManager {
    private final XmlFile xmlFile;

    public XmlDataManager(final XmlFile xmlFile) {
        this.xmlFile = xmlFile;
    }

    // ////////////////////////////////////////////////////////
    // shutdown
    // ////////////////////////////////////////////////////////

    @Override
    public void shutdown() {
    }

    // ////////////////////////////////////////////////////////
    // SAX Handlers
    // ////////////////////////////////////////////////////////

    // TODO the following methods are being called repeatedly - is there no
    // caching? See the print statements
    private class DataHandler extends DefaultHandler {
       
        final StringBuilder data = new StringBuilder();
        CollectionData collection;
        String fieldName;
        ObjectData object;

        @Override
        public void characters(final char[] ch, final int start, final int end) throws SAXException {
            data.append(new String(ch, start, end));
        }

        @Override
        public void endElement(final String ns, final String name, final String tagName) throws SAXException {
            if (object != null) {
                if (tagName.equals("value")) {
                    final String value = data.toString();
                    object.set(fieldName, value);
                }
            }
        }

        @Override
        public void startElement(final String ns, final String name, final String tagName, final Attributes attributes) throws SAXException {
            if (object != null) {
                if (tagName.equals("value")) {
                    fieldName = attributes.getValue("field");
                    data.setLength(0);
                } else if (tagName.equals("association")) {
                    object.set(attributes.getValue("field"), oidFrom(attributes));
                } else if (tagName.equals("element")) {
                    object.addElement(fieldName, oidFrom(attributes));
                } else if (tagName.equals("multiple-association")) {
                    fieldName = attributes.getValue("field");
                    object.initCollection(fieldName);
                }
            } else if (collection != null) {
                if (tagName.equals("element")) {
                   
                    collection.addElement(oidFrom(attributes));
                }
            } else {
                if (tagName.equals("isis")) {
                    final RootOidDefault oid = oidFrom(attributes);
                    final Version fileVersion = fileVersionFrom(attributes);
                   
                    object = new ObjectData(oid, fileVersion);
                } else if (tagName.equals("collection")) {
                   
                    final RootOidDefault oid = oidFrom(attributes);
                    final Version fileVersion = fileVersionFrom(attributes);
                   
                    collection = new CollectionData(oid, fileVersion);
                } else {
                    throw new SAXException("Invalid data");
                }
            }
        }
    }

    private static RootOidDefault oidFrom(final Attributes attributes) {
        final String oid = attributes.getValue("oid");
        return RootOidDefault.deString(oid, getOidMarshaller());
    }

    private static Version fileVersionFrom(final Attributes attributes) {
        final String user = attributes.getValue("user");
        final long version = Long.valueOf(attributes.getValue("ver"), 16).longValue();
        final Version fileVersion = FileVersion.create(user, version);
        return fileVersion;
    }


    private class InstanceHandler extends DefaultHandler {
        Vector<RootOid> instances = new Vector<RootOid>();

        @Override
        public void characters(final char[] arg0, final int arg1, final int arg2) throws SAXException {
        }

        @Override
        public void startElement(final String ns, final String name, final String tagName, final Attributes attrs) throws SAXException {
            if (tagName.equals("instance")) {
               
                final String oidStr = attrs.getValue("oid");
                final RootOidDefault oid = RootOidDefault.deString(oidStr, getOidMarshaller());
               
                instances.addElement(oid);
            }
        }
    }

    private class NumberHandler extends DefaultHandler {
        boolean captureValue = false;
        long value = 0;

        @Override
        public void characters(final char[] arg0, final int arg1, final int arg2) throws SAXException {
            if (captureValue) {
                final String string = new String(arg0, arg1, arg2);
                value = Long.valueOf(string, 16).longValue();
            }
        }

        @Override
        public void startElement(final String ns, final String name, final String tagName, final Attributes attrs) throws SAXException {
            captureValue = tagName.equals("number");
        }

        public ContentWriter writer(final long nextId) {
            return new ContentWriter() {
                @Override
                public void write(final Writer writer) throws IOException {
                    writer.write("<number>");
                    final String nextIdHex = Long.toString(nextId, 16);
                    writer.append("" + nextIdHex);
                    writer.append("</number>");
                    writer.flush();
                }
            };
        }
       
       
    }

    // ////////////////////////////////////////////////////////
    // fixtures
    // ////////////////////////////////////////////////////////

    @Override
    public boolean isFixturesInstalled() {
        return xmlFile.isFixturesInstalled();
    }

    // ////////////////////////////////////////////////////////
    // loadData
    // ////////////////////////////////////////////////////////

    @Override
    public Data loadData(final RootOid oid) {
        final DataHandler handler = new DataHandler();
        xmlFile.parse(handler, filename(oid));

        if (handler.object != null) {
            return handler.object;
        } else {
            return handler.collection;
        }
    }

    // ////////////////////////////////////////////////////////
    // getInstances, numberOfInstances
    // ////////////////////////////////////////////////////////

    @Override
    public ObjectDataVector getInstances(final ObjectData pattern) {
       
        final Vector<RootOid> instances = loadInstances(pattern.getSpecification(getSpecificationLoader()));

        if (instances == null) {
            return new ObjectDataVector();
        }

        final ObjectDataVector matches = new ObjectDataVector();
        for (final RootOid oid : instances) {
            final ObjectData instanceData = (ObjectData) loadData(oid);
            // TODO check loader first
            if (instanceData == null) {
                throw new IsisException("No data found for " + oid + " (possible missing file)");
            }
            if (matchesPattern(pattern, instanceData)) {
                matches.addElement(instanceData);
            }
        }
        return matches;
    }

    @Override
    public int numberOfInstances(final ObjectData pattern) {
        return getInstances(pattern).size();
    }

    private Vector<RootOid> loadInstances(final ObjectSpecification noSpec) {
        final InstanceHandler handler = new InstanceHandler();
        parseSpecAndSubclasses(handler, noSpec);
        return handler.instances;
    }

    private void parseSpecAndSubclasses(final InstanceHandler handler, final ObjectSpecification noSpec) {
        parseIfNotAbstract(noSpec, handler);
        for (final ObjectSpecification subSpec : noSpec.subclasses()) {
            parseSpecAndSubclasses(handler, subSpec);
        }
    }

    private void parseIfNotAbstract(final ObjectSpecification noSpec, final InstanceHandler handler) {
        if (noSpec.isAbstract()) {
            return;
        }
        xmlFile.parse(handler, noSpec.getFullIdentifier());
    }

    /**
     * A helper that determines if two sets of data match. A match occurs when
     * the types are the same and any field in the pattern also occurs in the
     * data set under test.
     */
    // TODO we need to be able to find collection instances as well
    protected boolean matchesPattern(final ObjectData patternData, final ObjectData candidateData) {
        if (patternData == null || candidateData == null) {
            throw new NullPointerException("Can't match on nulls " + patternData + " " + candidateData);
        }

        if (!candidateData.getSpecification(getSpecificationLoader()).isOfType(patternData.getSpecification(getSpecificationLoader()))) {
            return false;
        }

        for (final String field : patternData.fields()) {
            final Object patternFieldValue = patternData.get(field);
            final Object candidateFieldValue = candidateData.get(field);

            if (candidateFieldValue instanceof ListOfRootOid) {
                final ListOfRootOid patternElements = (ListOfRootOid) patternFieldValue;
                for (int i = 0; i < patternElements.size(); i++) {
                    final RootOid requiredElement = patternElements.elementAt(i); // must have this element
                    boolean requiredFound = false;
                    final ListOfRootOid testElements = ((ListOfRootOid) candidateFieldValue);
                    for (int j = 0; j < testElements.size(); j++) {
                        if (requiredElement.equals(testElements.elementAt(j))) {
                            requiredFound = true;
                            break;
                        }
                    }
                    if (!requiredFound) {
                        return false;
                    }
                }
            } else {
                if (!patternFieldValue.equals(candidateFieldValue)) {
                    return false;
                }
            }

        }

        return true;
    }

    /**
     * Read in the next unique number for the object identifier.
     */
    protected long nextId() throws PersistorException {
        final NumberHandler handler = new NumberHandler();
        xmlFile.parse(handler, "oid");

        final long nextId = handler.value + 1;
        xmlFile.writeXml("oid", handler.writer(nextId));

        return nextId;
    }

    // ////////////////////////////////////////////////////////
    // insertObject, remove
    // ////////////////////////////////////////////////////////

    /**
     * Save the data for an object and adds the reference to a list of instances
     */
    @Override
    public final void insertObject(final ObjectData data) {
        if (data.getRootOid() == null) {
            throw new IllegalArgumentException("Oid must be non-null");
        }

        writeInstanceToItsDataFile(data);
        final ObjectSpecification objSpec = data.getSpecification(getSpecificationLoader());
        addReferenceToInstancesFile(data.getRootOid(), objSpec);
    }

    private void addReferenceToInstancesFile(final RootOid oid, final ObjectSpecification noSpec) {
        final Vector<RootOid> instances = loadInstances(noSpec);
        instances.addElement(oid);
        writeInstanceFile(noSpec, instances);
    }

    // ////////////////////////////////////////////////////////
    // remove
    // ////////////////////////////////////////////////////////

    @Override
    public final void remove(final RootOid oid) throws ObjectNotFoundException, ObjectPersistenceException {
        final Data data = loadData(oid);
        removeReferenceFromInstancesFile(oid, data.getSpecification(getSpecificationLoader()));
        deleteData(oid);
    }

    /**
     * Delete the data for an existing instance.
     */
    private void deleteData(final RootOid oid) {
        xmlFile.delete(filename(oid));
    }

    private void removeReferenceFromInstancesFile(final RootOid oid, final ObjectSpecification noSpec) {
        final Vector<RootOid> instances = loadInstances(noSpec);
        instances.removeElement(oid);
        writeInstanceFile(noSpec, instances);
    }

    // ////////////////////////////////////////////////////////
    // helpers (used by both add & remove)
    // ////////////////////////////////////////////////////////

    private void writeInstanceFile(final ObjectSpecification noSpec, final Vector<RootOid> instances) {
        writeInstanceFile(noSpec.getFullIdentifier(), instances);
    }

    private void writeInstanceFile(final String name, final Vector<RootOid> instances) {
        xmlFile.writeXml(name, new ContentWriter() {
            @Override
            public void write(final Writer writer) throws IOException {
                writer.write("<instances");
                Utils.appendAttribute(writer, "name", name);
                writer.append(">\n");

                for (final RootOid elementAt : instances) {
                    writer.append("  <instance");
                    Utils.appendAttribute(writer, "oid", elementAt.enString(getOidMarshaller()));
                    writer.append("/>\n");
                }
                writer.append("</instances>");
                writer.flush();
            }
        });
    }

    // ////////////////////////////////////////////////////////
    // save
    // ////////////////////////////////////////////////////////

    /**
     * Save the data for latter retrieval.
     */
    @Override
    public final void save(final Data data) {
        writeInstanceToItsDataFile(data);
    }

    private void writeInstanceToItsDataFile(final Data data) {
        xmlFile.writeXml(filename(data.getRootOid()), new ContentWriter() {
            @Override
            public void write(final Writer writer) throws IOException {
                final boolean isObject = data instanceof ObjectData;
                final String tag = isObject ? "isis" : "collection";
                writer.write("<");
                writer.write(tag);
                final RootOid oid = data.getRootOid();
                Utils.appendAttribute(writer, "oid", oid.enString(getOidMarshaller()));
                Utils.appendAttribute(writer, "user", "" + getAuthenticationSession().getUserName());

                final long sequence = data.getVersion().getSequence();
                final String sequenceString = Long.toHexString(sequence).toUpperCase();
                Utils.appendAttribute(writer, "ver", "" + sequenceString);

                writer.append(">\n");

                if (isObject) {
                    writeObject(data, writer);
                } else {
                    writeCollection(data, writer);
                }

                writer.append("</" + tag + ">\n");
                writer.flush();
            }

        });
    }

    private void writeObject(final Data data, final Writer writer) throws IOException {
        final ObjectData object = (ObjectData) data;
        for (final String field : object.fields()) {
            writeField(writer, object, field);
        }
    }

    private void writeField(final Writer writer, final ObjectData object, final String field) throws IOException {
        final Object entry = object.get(field);

        if (entry instanceof RootOidDefault) {
            writeAssociationField(writer, field, entry);
        } else if (entry instanceof ListOfRootOid) {
            writeMultipleAssociationField(writer, field, entry);
        } else {
            writeValueField(writer, field, entry);
        }
    }

    private void writeAssociationField(final Writer writer, final String field, final Object entry) throws IOException {
        final Oid rootOidDefault = (Oid)entry;
        Assert.assertFalse(rootOidDefault.isTransient());
        writer.append("  <association");
        Utils.appendAttribute(writer, "field", field);
        Utils.appendAttribute(writer, "oid", rootOidDefault.enString(getOidMarshaller()));
        writer.append("/>\n");
    }

    private void writeMultipleAssociationField(final Writer writer, final String field, final Object entry) throws IOException {
        final ListOfRootOid references = (ListOfRootOid) entry;
        final int size = references.size();

        if (size > 0) {
            writer.append("  <multiple-association field=\"" + field + "\" ");
            writer.append(">\n");
            for (int i = 0; i < size; i++) {
                final Object oid = references.elementAt(i);
                final RootOidDefault rootOidDefault = (RootOidDefault) oid;
                if (rootOidDefault.isTransient()) {
                    throw new ObjectPersistenceException("Can't add tranisent OID (" + oid + ") to " + field + " element.");
                }
                writer.append("    <element ");
                Utils.appendAttribute(writer, "oid", rootOidDefault.enString(getOidMarshaller()));
                writer.append("/>\n");
            }
            writer.append("  </multiple-association>\n");
            writer.flush();
        }
    }

    private static void writeValueField(final Writer writer, final String field, final Object entry) throws IOException {
        writer.append("  <value");
        Utils.appendAttribute(writer, "field", field);
        writer.append(">");
        writer.append(XmlFile.getValueWithSpecialsEscaped(entry.toString()));
        writer.append("</value>\n");
    }

    private static void writeCollection(final Data data, final Writer writer) throws IOException {
        final CollectionData collection = (CollectionData) data;
        final ListOfRootOid refs = collection.references();
        for (int i = 0; i < refs.size(); i++) {
            final Object oid = refs.elementAt(i);
            writer.append("  <element");
            final RootOid rootOid = (RootOid) oid;
            Utils.appendAttribute(writer, "oid", rootOid.enString(getOidMarshaller()));
            writer.append("/>\n");
        }
    }

   
    private static String filename(final RootOid oid) {
        return oid.getObjectSpecId() + File.separator + oid.getIdentifier();
    }



    // ////////////////////////////////////////////////////////
    // Debugging
    // ////////////////////////////////////////////////////////

    @Override
    public String getDebugData() {
        return "Data directory " + xmlFile.getDirectory();
    }

   
    // ////////////////////////////////////////////////////////
    // dependencies (from context)
    // ////////////////////////////////////////////////////////

    protected static OidMarshaller getOidMarshaller() {
    return IsisContext.getOidMarshaller();
  }

    protected AuthenticationSession getAuthenticationSession() {
        return IsisContext.getAuthenticationSession();
    }

    protected SpecificationLoaderSpi getSpecificationLoader() {
        return IsisContext.getSpecificationLoader();
    }

   
}
TOP

Related Classes of org.apache.isis.objectstore.xml.internal.data.xml.XmlDataManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.