blob: f612f09dc67428df9e7492e521ab87da7dfb8f13 [file] [log] [blame]
/*
* 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.empire.db;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.empire.commons.ObjectUtils;
import org.apache.empire.commons.Options;
import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.Column;
import org.apache.empire.data.ColumnExpr;
import org.apache.empire.data.Record;
import org.apache.empire.db.exceptions.FieldIsReadOnlyException;
import org.apache.empire.db.exceptions.FieldValueNotFetchedException;
import org.apache.empire.db.expr.compare.DBCompareExpr;
import org.apache.empire.exceptions.BeanPropertyGetException;
import org.apache.empire.exceptions.InvalidArgumentException;
import org.apache.empire.exceptions.ObjectNotValidException;
import org.apache.empire.xml.XMLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
*
* This class handles one record from a database table.
*
*/
public class DBRecord extends DBRecordData implements Record, Cloneable
{
private final static long serialVersionUID = 1L;
/* Record state enum */
public enum State
{
Invalid,
/* Empty, not used! */
Valid,
Modified,
New;
/* Accessors */
boolean isLess(State other)
{
return this.ordinal()<other.ordinal();
}
boolean isEqualOrMore(State other)
{
return this.ordinal()>=other.ordinal();
}
}
protected static final Logger log = LoggerFactory.getLogger(DBRecord.class);
// This is the record data
private State state;
private DBRowSet rowset;
private Object[] fields;
private boolean[] modified;
private boolean validateFieldValues;
// Special Rowset Data (usually null)
private Object rowsetData;
/**
* Create a new DBRecord object.<BR>
* The record is not attached to a RowSet and the record's state is initially set to REC_INVALID.
*
* Please derive your own Objects from this class.
*/
public DBRecord()
{
state = State.Invalid;
rowset = null;
fields = null;
modified = null;
rowsetData = null;
validateFieldValues = true;
}
public DBRecord(DBRowSet initialRowset)
{
this();
// allow initial rowset
rowset = initialRowset;
}
/**
* This method is used internally by the RowSet to initialize the record's properties
* @param rowset the rowset to which to attach this record
* @param rowSetData any further RowSet specific data
* @param newRecord
*/
protected void initData(DBRowSet rowset, Object rowSetData, boolean newRecord)
{
// Init rowset
boolean rowsetChanged = (this.rowset != rowset);
if (rowsetChanged)
fields = null;
this.rowset = rowset;
// Init fields
if (rowset!=null)
{ // Set Rowset
int colCount = rowset.getColumns().size();
if (fields==null || fields.length!=colCount)
fields = new Object[colCount];
else
{ // clear fields
for (int i=0; i<fields.length; i++)
if (fields[i]!=ObjectUtils.NO_VALUE)
fields[i]=null;
}
}
// Set State
this.rowsetData = rowSetData;
this.modified = null;
changeState((rowset==null ? State.Invalid : (newRecord ? State.New : State.Valid)));
// notify
if (rowsetChanged)
onRowSetChanged();
}
/**
* changes the state of the record
* @param newState
*/
protected void changeState(State newState)
{
this.state = newState;
}
/**
* This function provides direct access to the record fields.<BR>
* This method is used internally be the RowSet to fill the data.<BR>
* @return an array of field values
*/
protected Object[] getFields()
{
return fields;
}
/**
* Closes the record by releasing all resources and resetting the record's state to invalid.
*/
@Override
public void close()
{
// rowset = null; -- do not change this --
fields = null;
modified = null;
rowsetData = null;
// change state
if (state!=State.Invalid)
changeState(State.Invalid);
}
/** {@inheritDoc} */
@Override
public DBRecord clone()
{
try
{
DBRecord rec = (DBRecord)super.clone();
rec.rowset = this.rowset;
rec.state = this.state;
if (rec.fields == fields && fields!=null)
rec.fields = fields.clone();
if (rec.modified == modified && modified!=null)
rec.modified = modified.clone();
rec.rowsetData = this.rowsetData;
rec.validateFieldValues = this.validateFieldValues;
return rec;
} catch (CloneNotSupportedException e)
{
log.error("Unable to clone record.", e);
return null;
}
}
/**
* Returns the current DBDatabase object.
*
* @return the current DBDatabase object
*/
@Override
public DBDatabase getDatabase()
{
return (rowset != null) ? rowset.db : null;
}
/**
* Returns the DBRowSet object.
*
* @return the DBRowSet object
*/
public DBRowSet getRowSet()
{
return rowset;
}
/**
* Returns the DBRowSet object.
*
* @return the DBRowSet object
*/
public Object getRowSetData()
{
return rowsetData;
}
/**
* Returns the record state.
*
* @return the record state
*/
public State getState()
{
return state;
}
/**
* Returns true if the record is valid.
*
* @return true if the record is valid
*/
@Override
public boolean isValid()
{
return (state != State.Invalid);
}
/**
* Returns true if the record is valid.
*
* @return true if the record is valid
*/
@Override
public boolean isReadOnly()
{
if (!isValid())
return true;
DBRowSet rowset = getRowSet();
return (rowset==null || !rowset.isUpdateable());
}
/**
* Returns true if the record is modified.
*
* @return true if the record is modified
*/
@Override
public boolean isModified()
{
return (state.isEqualOrMore(State.Modified));
}
/**
* Returns true if this record is a new record.
*
* @return true if this record is a new record
*/
@Override
public boolean isNew()
{
return (state == State.New);
}
/**
* Returns true if this record is a existing record (valid but not new).
* This may be used from expression language instead of the not allowed property "new"
*
* @return true if this record is a existing record (valid but not new).
*/
public boolean isExists()
{
return (state == State.Valid || state == State.Modified);
}
/**
* Returns the number of the columns.
*
* @return the number of the columns
*/
@Override
public int getFieldCount()
{
return (fields != null) ? fields.length : 0;
}
/**
* Returns the index value by a specified DBColumnExpr object.
*
* @return the index value
*/
@Override
public int getFieldIndex(ColumnExpr column)
{
DBColumnExpr expr = (DBColumnExpr)column;
return (rowset != null) ? rowset.getColumnIndex(expr.getUpdateColumn()) : -1;
}
/**
* Returns the index value by a specified column name.
*
* @return the index value
*/
@Override
public int getFieldIndex(String column)
{
if (rowset != null)
{
List<DBColumn> columns = rowset.getColumns();
for (int i = 0; i < columns.size(); i++)
{
DBColumn col = columns.get(i);
if (col.getName().equalsIgnoreCase(column))
return i;
}
}
// not found
return -1;
}
/**
* Returns the DBColumn for the field at the given index.
*
* @param index the field index
*
* @return the index value
*/
public DBColumn getDBColumn(int index)
{
return (rowset!=null ? rowset.getColumn(index) : null);
}
/**
* Implements the Record Interface getColumn method.<BR>
* Internally calls getDBColumn()
* @return the Column at the specified index
*/
@Override
public final Column getColumn(int index)
{
return getDBColumn(index);
}
/**
* Returns a DBColumnExpr object by a specified index value.
* @return the index value
*/
@Override
public final ColumnExpr getColumnExpr(int index)
{
return getDBColumn(index);
}
/**
* helper function to check if a given field index corresponds to one of the given columns
* @param index the field index
* @param column one or more columns to check
* @return true if the index is for one of the columns or false otherwise
*/
protected boolean isColumn(int index, DBColumn... column)
{
if (index < 0 || index >= fields.length)
throw new InvalidArgumentException("index", index);
if (column==null)
throw new InvalidArgumentException("column", column);
Column col = getColumn(index);
for (int i=0; i<column.length; i++)
{ // compare
if (col==column[i])
return true;
}
// not found
return false;
}
/**
* Returns true if the field was modified.
*
* @param index the field index
*
* @return true if the field was modified
*/
public boolean wasModified(int index)
{
if (!isValid())
throw new ObjectNotValidException(this);
if (index < 0 || index >= fields.length)
throw new InvalidArgumentException("index", index);
// Check modified
if (modified == null)
return false;
return modified[index];
}
/**
* Returns true if the field was modified.
*
* @return true if the field was modified
*/
@Override
public final boolean wasModified(Column column)
{
return wasModified(getFieldIndex(column));
}
/**
* Returns true if any of the given fields was modified.
*
* @return true if any of the given fields were modified or false otherwise
*/
public final boolean wasAnyModified(Column... columns)
{
for (Column c : columns)
{
if (wasModified(getFieldIndex(c)))
return true;
}
return false;
}
/**
* Sets the modified state of a column.<BR>
* This will force the field to be updated in the database, if set to TRUE.
*
* @param column the column
* @param isModified modified or not
*/
public void setModified(DBColumn column, boolean isModified)
{ // Check valid
if (state == State.Invalid)
throw new ObjectNotValidException(this);
// Check modified
if (modified == null)
{ // Init array
modified = new boolean[fields.length];
for (int j = 0; j < fields.length; j++)
modified[j] = false;
}
int index = getFieldIndex(column);
if (index >= 0)
modified[index] = isModified;
// Set State to modified, if not already at least modified and isModified is set to true
if (state.isLess(State.Modified) && isModified)
changeState(State.Modified);
// Reset state to unmodified, if currently modified and not modified anymore after the change
if (state == State.Modified && !isModified)
{
boolean recordNotModified = true;
for (int j = 0; j < fields.length; j++)
{
if (modified[j] == true)
{
recordNotModified = false;
}
}
if (recordNotModified)
{
changeState(State.Valid);
}
}
}
/**
* returns an array of key columns which uniquely identify the record.
* @return the array of key columns if any
*/
@Override
public Column[] getKeyColumns()
{
return rowset.getKeyColumns();
}
/**
* Returns the array of primary key columns.
* @return the array of primary key columns
*/
public Object[] getKeyValues()
{
return ((rowset != null) ? rowset.getRecordKey(this) : null);
}
/**
* Returns the value for the given column or null if either the index is out of range or the value is not valid (see {@link DBRecord#isValueValid(int)})
* @return the index value
*/
@Override
public Object getValue(int index)
{ // Check state
if (fields == null)
throw new ObjectNotValidException(this);
// Check index
if (index < 0 || index>= fields.length)
throw new InvalidArgumentException("index", index);
// Special check for NO_VALUE
if (fields[index] == ObjectUtils.NO_VALUE)
throw new FieldValueNotFetchedException(getColumn(index));
// Return field value
return fields[index];
}
/**
* Returns whether a field value is provided i.e. the value is not DBRowSet.NO_VALUE<BR>
* This function is only useful in cases where records are partially loaded.<BR>
*
* @param index the filed index
*
* @return true if a valid value is supplied for the given field or false if value is {@link ObjectUtils#NO_VALUE}
*/
public boolean isValueValid(int index)
{ // Check state
if (fields == null)
throw new ObjectNotValidException(this);
// Check index
if (index < 0 || index>= fields.length)
{ // Index out of range
throw new InvalidArgumentException("index", index);
}
// Special check for NO_VALUE
return (fields[index] != ObjectUtils.NO_VALUE);
}
/**
* Gets the possbile Options for a field in the context of the current record.
*
* @param column the database field column
*
* @return the field options
*/
public Options getFieldOptions(DBColumn column)
{
// DBColumn col = ((colexpr instanceof DBColumn) ? ((DBColumn) colexpr) : colexpr.getUpdateColumn());
return column.getOptions();
}
/**
* Gets the possbile Options for a field in the context of the current record.<BR>
* Same as getFieldOptions(DBColumn)
* @return the Option
*/
@Override
public final Options getFieldOptions(Column column)
{
return getFieldOptions((DBColumn)column);
}
/**
* Modifies a column value bypassing all checks made by setValue.
* Use this to explicitly set invalid values i.e. for temporary storage.
*
* @param index index of the column
* @param value the column value
*/
protected void modifyValue(int index, Object value, boolean fireChangeEvent)
{ // Check valid
if (state == State.Invalid)
throw new ObjectNotValidException(this);
if (index < 0 || index >= fields.length)
throw new InvalidArgumentException("index", index);
// modified state array
if (modified == null)
{ modified = new boolean[fields.length];
for (int j = 0; j < fields.length; j++)
modified[j] = false;
}
// set value and modified
fields[index] = value;
modified[index] = true;
// set record state
if (state.isLess(State.Modified))
changeState(State.Modified);
// field changed event
if (fireChangeEvent)
onFieldChanged(index);
}
/**
* validates all modified values of a record
*/
public void validateAllValues()
{
if (!this.isValid())
throw new ObjectNotValidException(this);
// Modified
if (modified == null)
return; // nothing to do
// check for field
for (int index=0; index<fields.length; index++)
{ // Modified or No value?
if (modified[index]==false || fields[index]==ObjectUtils.NO_VALUE)
continue;
// Auto-generated ?
DBColumn column = rowset.getColumn(index);
if (column.isAutoGenerated())
continue;
// validate this one
fields[index] = validateValue(column, fields[index]);
}
}
/**
* Sets the value of the column in the record.
* The functions checks if the column and the value are valid and whether the
* value has changed.
*
* @param index the index of the column
* @param value the value
*/
@Override
public void setValue(int index, Object value)
{
if (state == State.Invalid)
throw new ObjectNotValidException(this);
if (index < 0 || index >= fields.length)
throw new InvalidArgumentException("index", index);
// Strings special
if ((value instanceof String) && ((String)value).length()==0)
value = null;
// Is value valid
Object current = fields[index];
if (current==ObjectUtils.NO_VALUE)
throw new FieldValueNotFetchedException(getColumn(index));
// convert
DBColumn column = rowset.getColumn(index);
// must convert enums
if (value!=null && value.getClass().isEnum())
{ // convert enum
Enum<?> enumVal = ((Enum<?>)value);
boolean numeric = column.getDataType().isNumeric();
value = (numeric ? enumVal.ordinal() : enumVal.name());
}
// Has Value changed?
if (ObjectUtils.compareEqual(current, value))
{ // value has not changed!
return;
}
// Check whether we can change this field
if (!allowFieldChange(column))
{ // Read Only column may be set
throw new FieldIsReadOnlyException(column);
}
// Is Value valid?
if (this.validateFieldValues)
{ // validate
value = validateValue(column, value);
}
// Init original values
modifyValue(index, value, true);
}
/**
* Sets the value of the column in the record.
* The functions checks if the column and the value are valid and whether the
* value has changed.
*
* @param column a DBColumn object
* @param value the value
*/
@Override
public final void setValue(Column column, Object value)
{
if (!isValid())
throw new ObjectNotValidException(this);
// Get Column Index
setValue(getFieldIndex(column), value);
}
/**
* Checks whether or not this field can be changed at all.
* Note: This is not equivalent to isFieldReadOnly()
* @param column the column that needs to be changed
* @return true if it is possible to change this field for this record context
*/
protected boolean allowFieldChange(DBColumn column)
{
// Check auto generated
if (column.isAutoGenerated() && (!isNew() || !isNull(column)))
return false;
// Check key Column
if (!isNew() && rowset.isKeyColumn(column))
return false;
// done
return true;
}
/**
* Validates a value before it is set in the record.
* By default, this method simply calls column.validate()
* @param column the column that needs to be changed
* @param value the new value
*/
@Override
public Object validateValue(Column column, Object value)
{
return column.validate(value);
}
/**
* Returns whether or not values are checked for validity when calling setValue().
* If set to true validateValue() is called to check validity
* @return true if the validity of values is checked or false otherwise
*/
public boolean isValidateFieldValues()
{
return validateFieldValues;
}
/**
* Set whether or not values are checked for validity when calling setValue().
* If set to true validateValue() is called to check validity, otherwise not.
* @param validateFieldValues flag whether to check validity
*/
public void setValidateFieldValues(boolean validateFieldValues)
{
this.validateFieldValues = validateFieldValues;
}
/**
* returns whether a field is visible to the client or not
* <P>
* May be overridden to implement context specific logic.
* @param column the column which to check for visibility
* @return true if the column is visible or false if not
*/
@Override
public boolean isFieldVisible(Column column)
{
if (rowset==null)
return false;
// Check value
int index = rowset.getColumnIndex(column);
if (index<0)
{ // Column not found
log.warn("Column {} does not exist for record of {}", column.getName(), rowset.getName());
}
return (index>=0 && isValueValid(index));
}
/**
* returns whether a field is read only or not
*
* @param column the database column
*
* @return true if the field is read only
*/
@Override
public boolean isFieldReadOnly(Column column)
{
if (rowset==null)
throw new ObjectNotValidException(this);
if (getFieldIndex(column)<0)
throw new InvalidArgumentException("column", column);
// Check key column
if (isValid() && !isNew() && rowset.isKeyColumn((DBColumn)column))
return true;
// Ask RowSet
return (rowset.isColumnReadOnly((DBColumn)column));
}
/**
* returns whether a field is required or not
*
* @param column the database column
*
* @return true if the field is required
*/
@Override
public boolean isFieldRequired(Column column)
{
if (rowset==null)
throw new ObjectNotValidException(this);
if (rowset.getColumnIndex(column)<0)
throw new InvalidArgumentException("column", column);
// from column definition
return (column.isRequired());
}
/**
* Initializes this record object by attaching it to a rowset,
* setting its primary key values and setting the record state.<BR>
* This function is useful for updating a record without prior reading.
* <P>
* @param table the rowset
* @param keyValues a Object array, the primary key(s)
* @param insert if true change the state of this object to REC_NEW
*/
public void init(DBRowSet table, Object[] keyValues, boolean insert)
{ // Init with keys
if (table!=null)
table.initRecord(this, keyValues, insert);
else
initData(null, null, false);
}
/**
* Creates a new record for the given table.<BR>
* All record fields will be filled with their default values.
* The record's state is set to NEW
* <P>
* If a connection is supplied sequence generated values will be obtained<BR>
* Otherwise the sequence will be generated later.
* <P>
* @param table the table for which to create a record
* @param conn a valid JDBC connection
*/
public void create(DBRowSet table, Connection conn)
{
if (table==null)
throw new InvalidArgumentException("table", table);
// create
table.createRecord(this, conn);
}
/**
* Creates a new record for the given table.<BR>
* All record fields will be filled with their default values.<BR>
* The record's state is set to NEW
* <P>
* @param table the table for which to create a record
*/
public final void create(DBRowSet table)
{
create(table, null);
}
/**
* Reads a record from the database identified by it's primary key.
* After successful reading the record will be valid and all values will be accessible.
* @see org.apache.empire.db.DBTable#readRecord(DBRecord, Object[], Connection)
*
* @param table the rowset from which to read the record
* @param keys an array of the primary key values
* @param conn a valid connection to the database.
*/
public void read(DBRowSet table, Object[] key, Connection conn)
{
if (table==null)
throw new InvalidArgumentException("table", table);
// read
table.readRecord(this, key, conn);
}
/**
* Reads a record from the database identified by it's primary key.
* After successful reading the record will be valid and all values will be accessible.
* @see org.apache.empire.db.DBTable#readRecord(DBRecord, Object[], Connection)
*
* @param table the rowset from which to read the record
* @param id the primary key of the record to load.
* @param conn a valid connection to the database.
*/
public final void read(DBRowSet table, Object id, Connection conn)
{
if (id instanceof Collection<?>)
{ // If it's a collection then convert it to an array
read(table, ((Collection<?>)id).toArray(), conn);
}
// Simple One-Column key
read(table, new Object[] { id }, conn);
}
/**
* Reads a record from the database identified by one or more constraints.
*
* In oder to concatenate constraints use the and() operator from the first constraint
* e.g. FIRSTNAME.is("Joe").and(LASTNAME.is("Doe"))
*
* @param table the rowset from which to read the record
* @param whereConstraints the constraint(s) (which must all be on the table)
* @param conn a valid connection to the database.
*/
public void read(DBRowSet table, DBCompareExpr whereConstraints, Connection conn)
{
if (whereConstraints==null)
throw new InvalidArgumentException("whereConstraints", null);
// check constraints
Set<DBColumn> columns = new HashSet<DBColumn>();
whereConstraints.addReferencedColumns(columns);
for (DBColumn c : columns)
if (!table.equals(c.getRowSet()))
throw new InvalidArgumentException("whereConstraints", c.getFullName());
// read now
DBCommand cmd = table.getDatabase().createCommand();
cmd.select(table.getColumns());
cmd.where(whereConstraints);
table.readRecord(this, cmd, conn);
}
/**
* Updates the record and saves all changes in the database.
*
* @see org.apache.empire.db.DBTable#updateRecord(DBRecord, Connection)
* @param conn a valid connection to the database.
*/
public void update(Connection conn)
{
if (!isValid())
throw new ObjectNotValidException(this);
if (!isModified())
return; /* Not modified. Nothing to do! */
// update
rowset.updateRecord(this, conn);
}
/**
* This method is used internally to indicate that the record update has completed<BR>
* This will set change the record's state to Valid
* @param rowSetData additional data held by the rowset for this record (optional)
*/
protected void updateComplete(Object rowSetData)
{
this.rowsetData = rowSetData;
this.modified = null;
changeState(State.Valid);
}
/**
* This helper function calls the DBRowset.deleteRecord method
* to delete the record.
*
* WARING: There is no guarantee that it ist called
* Implement delete logic in the table's deleteRecord method if possible
*
* @see org.apache.empire.db.DBTable#deleteRecord(Object[], Connection)
* @param conn a valid connection to the database.
*/
public void delete(Connection conn)
{
if (isValid()==false)
throw new ObjectNotValidException(this);
// Delete only if record is not new
if (!isNew())
{
Object[] keys = rowset.getRecordKey(this);
rowset.deleteRecord(keys, conn);
}
close();
}
/**
* This function set the field descriptions to the the XML tag.
*
* @return the number of column descriptions added to the element
*/
@Override
public int addColumnDesc(Element parent)
{
if (!isValid())
throw new ObjectNotValidException(this);
// Add Field Description
int count = 0;
List<DBColumn> columns = rowset.getColumns();
for (int i = 0; i < columns.size(); i++)
{ // Add Field
DBColumn column = columns.get(i);
if (isFieldVisible(column)==false)
continue;
column.addXml(parent, 0);
count++;
}
return count;
}
/**
* Add the values of this record to the specified XML Element object.
*
* @param parent the XML Element object
* @return the number of row values added to the element
*/
@Override
public int addRowValues(Element parent)
{
if (!isValid())
throw new ObjectNotValidException(this);
// set row key
DBColumn[] keyColumns = rowset.getKeyColumns();
if (keyColumns != null && keyColumns.length > 0)
{ // key exits
if (keyColumns.length > 1)
{ // multi-Column-id
StringBuilder buf = new StringBuilder();
for (int i = 0; i < keyColumns.length; i++)
{ // add
if (i > 0)
buf.append("/");
buf.append(getString(keyColumns[i]));
}
parent.setAttribute("id", buf.toString());
}
else
parent.setAttribute("id", getString(keyColumns[0]));
}
// row attributes
if (isNew())
parent.setAttribute("new", "1");
// Add all children
int count = 0;
List<DBColumn> columns = rowset.getColumns();
for (int i = 0; i < fields.length; i++)
{ // Read all
DBColumn column = columns.get(i);
if (isFieldVisible(column)==false)
continue;
// Add Field Value
String name = column.getName();
if (fields[i] != null)
XMLUtil.addElement(parent, name, getString(i));
else
XMLUtil.addElement(parent, name).setAttribute("null", "yes"); // Null-Value
// increase count
count++;
}
return count;
}
/**
* returns the DBXmlDictionary that should used to generate XMLDocuments<BR>
* @return the DBXmlDictionary
*/
protected DBXmlDictionary getXmlDictionary()
{
return DBXmlDictionary.getInstance();
}
/**
* Returns a XML document with the field description an values of this record.
*
* @return the new XML Document object
*/
@Override
public Document getXmlDocument()
{
if (!isValid())
throw new ObjectNotValidException(this);
// Create Document
DBXmlDictionary xmlDic = getXmlDictionary();
Element root = XMLUtil.createDocument(xmlDic.getRowSetElementName());
if (rowset.getName() != null)
root.setAttribute("name", rowset.getName());
// Add Field Description
if (addColumnDesc(root)>0)
{ // Add row Values
addRowValues(XMLUtil.addElement(root, xmlDic.getRowElementName()));
}
// return Document
return root.getOwnerDocument();
}
/**
* Set the record default value for the fields with
* the value {@link ObjectUtils#NO_VALUE}
*
* @param conn the sql connection
*
* @return the number of fields set to default
*/
public int fillMissingDefaults(Connection conn)
{
int count = 0;
for (int i = 0; i < fields.length; i++)
{
if (fields[i] == ObjectUtils.NO_VALUE)
{
DBTableColumn col = (DBTableColumn) rowset.getColumn(i);
Object value = col.getRecordDefaultValue(conn);
if (value==null)
continue;
// Modify value
modifyValue(i, value, true);
count++;
}
}
return count;
}
/**
* set a record value from a particular bean property.
* <P>
* For a property called FOO this is equivalent of calling<BR>
* setValue(column, bean.getFOO())
* <P>
* @param bean the Java Bean from which to read the value from
* @param property the name of the property
* @param column the column for which to set the record value
*/
protected void setRecordValue(Column column, Object bean, String property)
{
if (StringUtils.isEmpty(property))
property = column.getBeanPropertyName();
try
{
// Get Property Value
PropertyUtilsBean pub = BeanUtilsBean.getInstance().getPropertyUtils();
Object value = pub.getSimpleProperty(bean, property);
// Now, set the record value
setValue( column, value );
} catch (IllegalAccessException e)
{ log.error(bean.getClass().getName() + ": unable to get property '" + property + "'");
throw new BeanPropertyGetException(bean, property, e);
} catch (InvocationTargetException e)
{ log.error(bean.getClass().getName() + ": unable to get property '" + property + "'");
throw new BeanPropertyGetException(bean, property, e);
} catch (NoSuchMethodException e)
{ log.warn(bean.getClass().getName() + ": no getter available for property '" + property + "'");
throw new BeanPropertyGetException(bean, property, e);
}
}
/**
* Sets record values from the supplied java bean.
*
* @return true if at least one value has been set successfully
*/
@Override
public int setRecordValues(Object bean, Collection<Column> ignoreList)
{
// Add all Columns
int count = 0;
for (int i = 0; i < getFieldCount(); i++)
{ // Check Property
DBColumn column = getDBColumn(i);
if (column.isReadOnly())
continue;
if (ignoreList != null && ignoreList.contains(column))
continue; // ignore this property
// Get Property Name
String property = column.getBeanPropertyName();
setRecordValue(column, bean, property);
count++;
}
return count;
}
/**
* Sets record values from the suppied java bean.
* @return true if at least one value has been set sucessfully
*/
@Override
public final int setRecordValues(Object bean)
{
return setRecordValues(bean, null);
}
/**
* Override this to do extra handling when the rowset for this record changes
*/
protected void onRowSetChanged()
{
if (log.isTraceEnabled() && rowset!=null)
log.trace("Record has been attached to rowset " + rowset.getName());
}
/**
* Override this to do extra handling when the record changes
*/
protected void onRecordChanged()
{
if (log.isTraceEnabled() && isValid())
log.trace("Record has been changed");
}
/**
* Override this to get notified when a field value changes
*/
protected void onFieldChanged(int i)
{
if (log.isDebugEnabled())
log.debug("Record field " + rowset.getColumn(i).getName() + " changed to " + String.valueOf(fields[i]));
}
}