Java tutorial
/* * 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)>0 && y.compareTo(z)>0)</tt> implies * <tt>x.compareTo(z)>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; } }