org.rimudb.DataObjectNode.java Source code

Java tutorial

Introduction

Here is the source code for org.rimudb.DataObjectNode.java

Source

/*
 * Copyright (c) 2008-2011 Simon Ritchie.
 * All rights reserved. 
 * 
 * This program 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.
 * 
 * This program 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 this program.  If not, see http://www.gnu.org/licenses/>.
 */
package org.rimudb;

import java.beans.*;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;

import org.apache.commons.logging.*;
import org.rimudb.exception.*;
import org.rimudb.generic.*;
import org.w3c.dom.*;

/**
 * This class is an object representation of the structure of the database to be
 * persisted to or loaded from XML. This is an object representation of the
 * structure.xml file contents that control loading and saving of dataObject
 * trees. This is created and used internally by the DBStructure object, and
 * should not be used directly by any other class.
 * 
 */
public class DataObjectNode implements Comparable {
    private static Log log = LogFactory.getLog(DataObjectNode.class);
    private DataObjectNode parentNode = null;
    private Table table = null;
    private Class doClass = null;
    private String name = null;
    private SortedMap<String, DataObjectNode> children = new TreeMap<String, DataObjectNode>(); // child data objects
    private SortedMap<String, PropertyNode> properties = new TreeMap<String, PropertyNode>(); // properties to save/load
    private Constructor doConst1 = null;
    private DataObject current = null;
    private TreeMap<String, PropertyDescriptor> pdescMap = null;
    private List<PropertyNode> partialKeyList = new ArrayList<PropertyNode>();
    private int action = 0; // default action
    public static final int ACTION_INSERT = 0;
    public static final int ACTION_REPLACE = 1;
    public static final int ACTION_PURGE = 2;

    /**
     * commitMode - This the duplicate handling mode for the underlying
     * DataObject. Defaults to DUPLICATE_EXCEPTION. It can be changed by setting
     * the action attribute
     */
    int commitMode = DataObject.DUPLICATE_EXCEPTION;

    // Used when inserting records to indicate whether we've purged the table already
    private boolean purgedFlag = false;

    private DataObjectIterator doIterator;
    private final CompoundDatabase cdb;

    public DataObjectNode(CompoundDatabase cdb, DataObjectNode parentNode, String name, Table table) {

        this.cdb = cdb;
        this.name = name;
        this.table = table;
        this.parentNode = parentNode;

        try {
            // get property descriptors to use
            doClass = table.getDataObjectClass();
            pdescMap = new TreeMap<String, PropertyDescriptor>();
            BeanInfo info = Introspector.getBeanInfo(doClass);
            PropertyDescriptor pdesc[] = info.getPropertyDescriptors();
            for (int i = 0; i < pdesc.length; i++) {
                String propname = pdesc[i].getName();
                TableMetaData tableMetaData = table.getTableMetaData();
                ColumnMetaData columnMetaData = tableMetaData.getMetaDataByPropertyName(propname);
                if (columnMetaData != null) {
                    // these are properties to keep
                    pdescMap.put(propname, pdesc[i]);
                }
            }

            doConst1 = doClass.getConstructor(new Class[] { CompoundDatabase.class });

        } catch (Exception e) {
            log.error("in DataObjectNode.DataObjectNode", e);

        } catch (Error err) {
            log.info("name=" + name + " table=" + table + " doClass=" + doClass);
            log.error("in DataObjectNode.DataObjectNode", err);
            throw err;
        }
    }

    public void addDataObjectNode(DataObjectNode node) {
        children.put(node.getName(), node);
    }

    public void addProperty(Element element) {
        addProperty(element, 0);
    }

    public void addProperty(Element element, int keySequence) {
        PropertyNode newNode = new PropertyNode(this, element, keySequence);
        properties.put(newNode.getName(), newNode);
        if (keySequence > 0 && (newNode.isFromParent() || newNode.isConstant())) {
            partialKeyList.add(newNode);
        }
    }

    public void addPropertyNode(PropertyNode node) {
        properties.put(node.getName(), node);
    }

    public void commit(Session session) throws RimuDBException {
        if (current != null) {
            if (session == null) {
                current.commit(commitMode);
            } else {
                current.commit(session, commitMode);
            }
        }
    }

    /**
     * Compares this object with the specified object for order. Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     * <p>
     * 
     * The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     * <p>
     * 
     * The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
     * <tt>x.compareTo(z)&gt;0</tt>.
     * <p>
     * 
     * Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for all
     * <tt>z</tt>.
     * <p>
     * 
     * It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates this
     * condition should clearly indicate this fact. The recommended language is
     * "Note: this class has a natural ordering that is inconsistent with
     * equals."
     * 
     * @param o
     *            the Object to be compared.
     * @return a negative integer, zero, or a positive integer as this object is
     *         less than, equal to, or greater than the specified object.
     * 
     * @throws ClassCastException
     *             if the specified object's type prevents it from being
     *             compared to this Object.
     */
    public int compareTo(java.lang.Object o) {
        DataObjectNode other = (DataObjectNode) o;
        return getName().compareTo(other.getName()); // order by names
    }

    public DataObject createDataObject()
            throws InvocationTargetException, InstantiationException, IllegalAccessException {
        current = (DataObject) doConst1.newInstance(new Object[] { cdb });
        return current;
    }

    public boolean equals(Object o) {
        if (!(o instanceof DataObjectNode))
            return false;
        DataObjectNode other = (DataObjectNode) o;
        if (!other.getName().equals(getName()))
            return false;
        return true;
    }

    public int getAction() {
        return action;
    }

    /**
     *  Return all the data objects that match the WhereList.
     *  
     * @param wherelist WhereList
     * @return DataObject[]
     * @throws RimuDBException
     */
    public DataObject[] selectAll(WhereList wherelist) throws RimuDBException {
        List<? extends DataObject> list = table.getAllDataObjects(wherelist, null, null,
                AbstractFinder.ALL_RECORDS);
        return (DataObject[]) list.toArray(new DataObject[list.size()]);
    }

    /**
     * Returns the <code>WhereList</code> that represents any "constant" or
     * "parent" <code>Property</code> tags in the <code>KeyList</code>
     * element in the "structure.xml" document. If there are no "constant" or
     * "parent" properties, it returns an empty WhereList. Any "constant" values need
     * to be passed as an argument.
     */
    public WhereList getWhereList(Properties constants) throws RimuDBException {
        WhereList whereList = new WhereList();

        PropertyNode[] nodes = (PropertyNode[]) partialKeyList.toArray(new PropertyNode[partialKeyList.size()]);

        for (int i = 0; i < nodes.length; i++) {
            Object whereValue = null;

            if (nodes[i].isFromParent()) {
                DataObjectNode searchNode = parentNode;
                if (nodes[i].getSourceParent() != null && nodes[i].getSourceParent().trim().length() > 0) {
                    searchNode = searchParentTree(parentNode, nodes[i].getSourceParent());
                }
                if (searchNode != null) {
                    PropertyNode propnode = searchNode.getProperty(nodes[i].getSourceName());
                    if (propnode != null) {
                        whereValue = propnode.getValue();
                    } else {
                        error("Property named " + nodes[i].getSourceName().trim() + " not found in "
                                + searchNode.getName());
                    }
                } else {
                    error("Parent node named " + nodes[i].getSourceParent().trim() + " not found");
                }
            }
            if (nodes[i].isConstant()) {
                String constValue = constants.getProperty(nodes[i].getName());
                if (constValue == null)
                    throw new IllegalArgumentException("Missing constant value for " + nodes[i].getName());
                if (current == null) {
                    try {
                        createDataObject();
                    } catch (Exception e) {
                        throw new RuntimeException("while creating data object: " + e);
                    }
                }
                nodes[i].setValue(constValue);
                whereValue = nodes[i].getValue();
            }

            String whereName = nodes[i].getName();

            whereList.add(whereName, Operator.EQ, whereValue);
        }

        return whereList;
    }

    /**
     * Search the tree of parent nodes to find a specific node
     * 
     * @param searchNode DataObjectNode
     * @param sourceParent String
     * @return DataObjectNode
     */
    private DataObjectNode searchParentTree(DataObjectNode searchNode, String sourceParent) {
        while (true) {
            // Quit if there are no more parents
            if (searchNode == null) {
                break;
            }
            // Did we find the node we were searching for
            if (searchNode.getName().equals(sourceParent)) {
                return searchNode;
            }
            // Go up to the tree to the next parent
            searchNode = searchNode.parentNode;
        }
        return null;
    }

    /**
     * This method returns an array of <code>DataObject</code>'s for some or all
     * records in the table. 
     * <p>
     * 
     * @return com.amo.data.access.KeyList[]
     */
    public DataObject[] getAllByStructure(Properties constants) throws RimuDBException {
        return selectAll(getWhereList(constants));
    }

    /**
     * This method returns an array of <code>DataObject</code>'s for some or all
     * records in the table.
     * <p>
     * This method returns the <code>DataObject</code>s in batches. The batchSize
     * is passed in as an argument. If the batchSize is greater than or equal to
     * the total number of records, it will return all the records available. If
     * the batchSize is less than the total number of records, use
     * getNextKeysByStructureIteratively() to get the rest of the records.
     * 
     */
    public DataObject[] getAllByStructureIteratively(Properties constants, int batchSize) throws RimuDBException {
        DataObject[] result = null;
        WhereList whereList = getWhereList(constants);
        String sql = table.getSQLAdapter().build(table.getTableMetaData(), table.getTableName(), whereList, null,
                null, AbstractFinder.ALL_RECORDS, false);
        Database db = cdb.getDatabase(doClass);
        IterativeQuery query = new IterativeQuery(db, doClass, sql);
        log.info("SQL to get keys iteratively: " + sql);

        Object[] parameters = null;
        if (whereList.size() > 0) {
            parameters = new Object[whereList.size()];
            for (int i = 0; i < whereList.size(); i++) {
                parameters[i] = whereList.getValue(i);
            }
        }

        doIterator = query.createDataObjectIterator(parameters);
        List<DataObject> dataObjectList = doIterator.next(batchSize);
        if (dataObjectList != null) {
            result = (DataObject[]) dataObjectList.toArray(new DataObject[dataObjectList.size()]);
        } else {
            doIterator.close();
        }
        return result;
    }

    /**
     * This method should be used on after getAllByStructureIteratively()
     * has been called. Call this method to return in batches any
     * <code>DataObject</code>s that haven't already been retrieved. The batchSize
     * is passed in as an argument. If the batchSize is greater than or equal to
     * the total number of records, it will return all the records available. If
     * the batchSize is less than the total number of records, use getAllByStructureIteratively()
     * getNextIterativeBatch() to get the rest of the records. If
     * there are no more records to retrieve, it returns an empty array.
     * 
     */
    public DataObject[] getNextIterativeBatch(int batchSize) throws RimuDBException {
        DataObject[] result = null;
        try {
            if (doIterator == null) {
                throw new IllegalStateException(
                        "Method called out of order. Please call getAllByStructureIteratively() first"
                                + "before calling this method");
            }
            List<DataObject> dataObjectList = doIterator.next(batchSize);
            if (dataObjectList != null) {
                result = (DataObject[]) dataObjectList.toArray(new DataObject[dataObjectList.size()]);
            } else {
                doIterator.close();
            }
        } catch (Exception e) {
            log.error("Error getting records from database.", e);
        }
        return result;

    }

    public DataObjectNode getChild(String name) {
        return (DataObjectNode) children.get(name);
    }

    public java.util.Collection getChildren() {
        return children.values();
    }

    public DataObject getCurrentDataObject() {
        return current;
    }

    public java.lang.String getName() {
        return name;
    }

    public PropertyNode getProperty(String name) {
        return (PropertyNode) properties.get(name);
    }

    public PropertyDescriptor getPropertyDescriptor(String name) {
        PropertyDescriptor pd = (PropertyDescriptor) pdescMap.get(name);
        if (pd == null) {
            try {
                BeanInfo info = Introspector.getBeanInfo(doClass);
                PropertyDescriptor pdesc[] = info.getPropertyDescriptors();
                for (int i = 0; i < pdesc.length; i++) {
                    String propname = pdesc[i].getName();
                    if (propname.equals(name)) {
                        // these are properties to keep
                        pdescMap.put(propname, pdesc[i]);
                        pd = pdesc[i];
                    }
                }
            } catch (IntrospectionException e) {
                error("property " + name + " does not exist in " + getName());
            }
        }
        return pd;

    }

    public java.util.Collection getProperties() {
        return properties.values();
    }

    public void error(String s) {
        log.error(s);
    }

    public void setAction(int newAction) {
        action = newAction;
        switch (action) {
        case ACTION_INSERT:
            commitMode = DataObject.DUPLICATE_EXCEPTION;
            break;

        case ACTION_REPLACE:
            commitMode = DataObject.DUPLICATE_REPLACE;
            break;

        case ACTION_PURGE:
            commitMode = DataObject.DUPLICATE_EXCEPTION;
            break;
        }
    }

    /**
     * Loads any properties configured as from the parent object with the data
     * from the parent object.
     */
    public void setParentProperties() {
        if (parentNode == null || parentNode.getCurrentDataObject() == null) {
            // skip missing parents or parents with no data object attached
            return;
        }

        Iterator it = getProperties().iterator();
        while (it.hasNext()) {
            PropertyNode prop = (PropertyNode) it.next();
            if (prop.isFromParent()) {
                PropertyNode parentProp = (PropertyNode) parentNode.getProperty(prop.getSourceName());
                if (parentProp == null) {
                    error("no parent property for " + prop.getSourceName() + " in " + getName());
                    return;
                } else {
                    prop.setValue("" + parentProp.getValue());
                }
            }
        }
    }

    /**
     * Purge all the records in the table before inserting any new records.
     * Note: This calls a purge() method in the DO, so if you haven't
     * implemented it yet, you must do so. See CSIItemDO for an example.
     * 
     */
    public void purge() {
        Connection con = null;
        try {
            if (getAction() == ACTION_PURGE && getPurgedFlag() == false) {
                DataObject dataObject = createDataObject();
                con = table.getDatabase().getDatabaseConnection();
                int nrRecords = table.deleteAll((WhereList) null);
                // dataObject.purge(con);
                setPurgedFlag(true); // set the purgedFlag so it doesn't purge
                                     // again
                log.info("Purged " + nrRecords + " from table " + table.getTableName());
            }
        } catch (java.sql.SQLException e) {
            System.err.println("In DataObjectNode.purge(), error getting database connection: " + e);
            e.printStackTrace(System.err);
        } catch (RimuDBException e) {
            System.err.println("In DataObjectNode.purge(), error getting database connection: " + e);
            e.printStackTrace(System.err);
        } catch (InstantiationException e) {
            System.err.println("In DataObjectNode.purge(), error creating data object: " + e);
            e.printStackTrace(System.err);
        } catch (IllegalAccessException e) {
            System.err.println("In DataObjectNode.purge(), error creating data object: " + e);
            e.printStackTrace(System.err);
        } catch (InvocationTargetException e) {
            System.err.println("In DataObjectNode.purge(), error creating data object: " + e);
            e.printStackTrace(System.err);
        } finally {
            try {
                if (con != null)
                    con.close();
            } catch (java.sql.SQLException e) {
                System.err.println("In DataObjectNode.purge(), error closing database connection: " + e);
                e.printStackTrace(System.err);
            }
        }
    }

    /**
     * Getter method for <code>purgedFlag</code>.
     * <p>
     * If we are persisting to the database, and the action tells us to purge
     * the table before inserting the records, the table should only be purged
     * once. Use this method to check this flag to see if we have already purged
     * the table.
     * 
     * @return <code>true</code> if we've already purged the table, false
     *         otherwise.
     */
    public boolean getPurgedFlag() {
        return purgedFlag;
    }

    /**
     * Setter method for <code>purgedFlag</code>.
     * <p>
     * If we are persisting to the database, and the action tells us to purge
     * the table before inserting the records, the table should only be purged
     * once. Use this method to set this flag to <code>true</code> after we have
     * purged the table.
     * 
     * @param purgedFlag
     */
    public void setPurgedFlag(boolean purgedFlag) {
        this.purgedFlag = purgedFlag;
    }

    /**
     * @param dobj
     */
    public void setCurrentDataObject(DataObject dobj) {
        current = dobj;
    }
}