* Implement db mapping dependencies introduced by additionalTables switch

* Try to get extract column name from sql functions when converting db column to property
* Added Javadoc comments
This commit is contained in:
hns 2005-09-16 14:31:56 +00:00
parent a9553bb471
commit fb1f5154e4
3 changed files with 137 additions and 44 deletions

View file

@ -112,6 +112,9 @@ public final class DbMapping {
// evict objects of this type when received via replication
private boolean evictOnReplication;
// Set of mappings that depend on us and should be forwarded last data change events
HashSet dependentMappings = new HashSet();
/**
* Create an empty DbMapping
*/
@ -140,12 +143,10 @@ public final class DbMapping {
db2prop = new HashMap();
columnMap = new HashMap();
parentInfo = null;
idField = null;
this.props = props;
readBasicProperties();
}
/**
@ -155,17 +156,12 @@ public final class DbMapping {
return props.lastModified() != lastTypeChange;
}
/**
* Read the mapping from the Properties. Return true if the properties were changed.
* The read is split in two, this method and the rewire method. The reason is that in order
* for rewire to work, all other db mappings must have been initialized and registered.
* Read in basic properties and register dbmapping with the
* dbsource.
*/
public synchronized void update() {
// read in properties
private void readBasicProperties() {
tableName = props.getProperty("_table");
idgen = props.getProperty("_idgen");
dbSourceName = props.getProperty("_db");
if (dbSourceName != null) {
@ -179,25 +175,37 @@ public final class DbMapping {
} else if (tableName == null) {
app.logEvent("*** No table name specified for prototype " + typename);
app.logEvent("*** accessing or storing a " + typename +
" object will cause an error.");
" object will cause an error.");
// mark mapping as invalid by nulling the dbSource field
dbSource = null;
} else {
// dbSource and tableName not null - register this instance
dbSource.registerDbMapping(this);
}
}
}
/**
* Read the mapping from the Properties. Return true if the properties were changed.
* The read is split in two, this method and the rewire method. The reason is that in order
* for rewire to work, all other db mappings must have been initialized and registered.
*/
public synchronized void update() {
// read in properties
readBasicProperties();
idgen = props.getProperty("_idgen");
// if id field is null, we assume "ID" as default. We don't set it
// however, so that if null we check the parent prototype first.
idField = props.getProperty("_id");
nameField = props.getProperty("_name");
protoField = props.getProperty("_prototype");
evictOnReplication = "true".equals(props.getProperty("_evictOnReplication"));
String parentSpec = props.getProperty("_parent");
evictOnReplication = "true".equals(props.getProperty("_evictOnReplication"));
if (parentSpec != null) {
// comma-separated list of properties to be used as parent
StringTokenizer st = new StringTokenizer(parentSpec, ",;");
@ -503,6 +511,14 @@ public final class DbMapping {
return null;
}
// SEMIHACK: If columnName is a function call, try to extract actual
// column name from it
int open = columnName.indexOf('(');
int close = columnName.indexOf(')');
if (open > -1 && close > open) {
columnName = columnName.substring(open + 1, close);
}
return _columnNameToProperty(columnName.toUpperCase());
}
@ -1163,9 +1179,9 @@ public final class DbMapping {
}
/**
* Return a string representation for this DbMapping
*
*
* @return ...
* @return a string representation
*/
public String toString() {
if (typename == null) {
@ -1176,18 +1192,18 @@ public final class DbMapping {
}
/**
* Get the last time something changed in the Mapping
*
*
* @return ...
* @return time of last mapping change
*/
public long getLastTypeChange() {
return lastTypeChange;
}
/**
* Get the last time something changed in our data
*
*
* @return ...
* @return time of last data change
*/
public long getLastDataChange() {
// refer to parent mapping if it uses the same db/table
@ -1199,25 +1215,48 @@ public final class DbMapping {
}
/**
*
* Set the last time something changed in the data, propagating the event
* to mappings that depend on us through an additionalTables switch.
*/
public void setLastDataChange(long t) {
// propagate data change timestamp to storage-compatible parent mapping
// forward data change timestamp to storage-compatible parent mapping
if (inheritsStorage()) {
parentMapping.setLastDataChange(t);
} else {
lastDataChange = t;
// propagate data change timestamp to mappings that depend on us
if (!dependentMappings.isEmpty()) {
Iterator it = dependentMappings.iterator();
while(it.hasNext()) {
DbMapping dbmap = (DbMapping) it.next();
dbmap.setIndirectDataChange(t);
}
}
}
}
/**
* Set the last time something changed in the data. This is already an indirect
* data change triggered by a mapping we depend on, so we don't propagate it to
* mappings that depend on us through an additionalTables switch.
*/
protected void setIndirectDataChange(long t) {
// forward data change timestamp to storage-compatible parent mapping
if (inheritsStorage()) {
parentMapping.setIndirectDataChange(t);
} else {
lastDataChange = t;
}
}
/**
* Helper method to generate a new ID. This is only used in the special case
* when using the select(max) method and the underlying table is still empty.
*
*
* @param dbmax ...
*
* @return ...
* @param dbmax the maximum value already stored in db
* @return a new and hopefully unique id
*/
public synchronized long getNewID(long dbmax) {
protected synchronized long getNewID(long dbmax) {
// refer to parent mapping if it uses the same db/table
if (inheritsStorage()) {
return parentMapping.getNewID(dbmax);
@ -1228,9 +1267,9 @@ public final class DbMapping {
}
/**
* Return an enumeration of all properties defined by this db mapping.
*
*
* @return ...
* @return the property enumeration
*/
public Enumeration getPropertyEnumeration() {
HashSet set = new HashSet();
@ -1250,6 +1289,11 @@ public final class DbMapping {
};
}
/**
* Collect a set of all properties defined by this db mapping
*
* @param basket the set to put properties into
*/
private void collectPropertyNames(HashSet basket) {
// fetch propnames from parent mapping first, than add our own.
if (parentMapping != null) {
@ -1262,7 +1306,7 @@ public final class DbMapping {
}
/**
* Return the name of the prototype which specifies the storage location
* Return the name of the prototype which specifies the storage location
* (dbsource + tablename) for this type, or null if it is stored in the embedded
* db.
*/
@ -1282,7 +1326,7 @@ public final class DbMapping {
*
* @return true if this mapping shares its parent mapping storage
*/
private boolean inheritsStorage() {
protected boolean inheritsStorage() {
// note: tableName and dbSourceName are nulled out in update() if they
// are inherited from the parent mapping. This way we know that
// storage is not inherited if either of them is not null.
@ -1344,20 +1388,30 @@ public final class DbMapping {
}
/**
* Get the mapping we inherit from, or null
*
*
* @return ...
* @return the parent DbMapping, or null
*/
public DbMapping getParentMapping() {
return parentMapping;
}
/**
* Get our ResourceProperties
*
*
* @return ...
* @return our properties
*/
public ResourceProperties getProperties() {
return props;
}
/**
* Register a DbMapping that depends on this DbMapping, so that collections of other mapping
* should be reloaded if data on this mapping is updated.
*
* @param dbmap the DbMapping that depends on us
*/
protected void addDependency(DbMapping dbmap) {
this.dependentMappings.add(dbmap);
}
}

View file

@ -23,6 +23,7 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Hashtable;
/**
* This class describes a releational data source (URL, driver, user and password).
@ -36,6 +37,7 @@ public class DbSource {
private String driver;
private boolean isOracle;
private long lastRead = 0L;
private Hashtable dbmappings = new Hashtable();
/**
* Creates a new DbSource object.
@ -143,38 +145,59 @@ public class DbSource {
}
/**
* Return the class name of the JDBC driver
*
*
* @return ...
* @return the class name of the JDBC driver
*/
public String getDriverName() {
return driver;
}
/**
* Return the name of the db dource
*
*
* @return ...
* @return the name of the db dource
*/
public String getName() {
return name;
}
/**
* Set the default (server-wide) properties
*
*
* @param props ...
* @param props server default db.properties
*/
public static void setDefaultProps(ResourceProperties props) {
defaultProps = props;
}
/**
* Is this an Oracle database?
* Check if this DbSource represents an Oracle database
*
* @return true if we're using an oracle JDBC driver
*/
public boolean isOracle() {
return isOracle;
}
/**
* Register a dbmapping by its table name.
*
* @param dbmap the DbMapping instance to register
*/
protected void registerDbMapping(DbMapping dbmap) {
if (!dbmap.inheritsStorage() && dbmap.getTableName() != null) {
dbmappings.put(dbmap.getTableName().toUpperCase(), dbmap);
}
}
/**
* Look up a DbMapping instance for the given table name.
*
* @param tablename the table name
* @return the matching DbMapping instance
*/
protected DbMapping getDbMapping(String tablename) {
return (DbMapping) dbmappings.get(tablename.toUpperCase());
}
}

View file

@ -19,6 +19,7 @@ package helma.objectmodel.db;
import helma.framework.core.Application;
import helma.objectmodel.INode;
import helma.objectmodel.IProperty;
import helma.util.StringUtils;
import java.sql.SQLException;
import java.util.HashMap;
@ -297,6 +298,21 @@ public final class Relation {
additionalTables = null;
} else {
String ucTables = additionalTables.toUpperCase();
// create dependencies implied by additional tables
DbSource dbsource = otherType.getDbSource();
if (dbsource != null) {
String[] tables = StringUtils.split(ucTables, ", ");
for (int i=0; i<tables.length; i++) {
// Skip some join-related keyworks we might encounter here
if ("AS".equals(tables[i]) || "ON".equals(tables[i])) {
continue;
}
DbMapping dbmap = dbsource.getDbMapping(tables[i]);
if (dbmap != null) {
dbmap.addDependency(otherType);
}
}
}
// see wether the JOIN syntax is used. look for " join " with whitespaces on both sides
// and for "join " at the beginning:
additionalTablesJoined = (ucTables.indexOf(" JOIN ") != -1 ||