moved from helma.objectmodel to helma.objectmodel.db package

This commit is contained in:
hns 2001-08-20 14:46:58 +00:00
parent f0151b149c
commit c977631ec9
7 changed files with 1886 additions and 0 deletions

View file

@ -0,0 +1,122 @@
// DbKey.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.io.Serializable;
/**
* This is the internal representation of a database key. It is constructed
* out of the database URL, the table name, the user name and the database
* key of the node and unique within each Helma application. Currently only
* single keys are supported.
*/
public final class DbKey implements Key, Serializable {
// the name of the prototype which defines the storage of this object.
// this is the name of the object's prototype, or one of its ancestors.
// If null, the object is stored in the embedded db.
private final String storageName;
// the id that defines this key's object within the above storage space
private final String id;
/**
* make a key for a persistent Object, describing its datasource and id.
*/
public DbKey (DbMapping dbmap, String id) {
this.id = id;
this.storageName = dbmap == null ? null : dbmap.getStorageTypeName ();
}
public boolean equals (Object what) {
if (what == this)
return true;
try {
DbKey k = (DbKey) what;
return (storageName == k.storageName || storageName.equals (k.storageName)) &&
(id == k.id || id.equals (k.id));
} catch (Exception x) {
return false;
}
}
public int hashCode () {
return storageName == null ? id.hashCode () : storageName.hashCode() + id.hashCode ();
}
public Key getParentKey () {
return null;
}
public String getStorageName () {
return storageName;
}
public String getID () {
return id;
}
public String toString () {
return storageName == null ? "["+id+"]" : storageName+"["+id+"]";
}
}

View file

@ -0,0 +1,755 @@
// DbMapping.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import helma.framework.core.Application;
import helma.util.Updatable;
import helma.util.SystemProperties;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.sql.*;
import com.workingdogs.village.*;
/**
* A DbMapping describes how a certain type of Nodes is to mapped to a
* relational database table. Basically it consists of a set of JavaScript property-to-
* Database row bindings which are represented by instances of the Relation class.
*/
public class DbMapping implements Updatable {
// DbMappings belong to an application
Application app;
// prototype name of this mapping
String typename;
// properties from where the mapping is read
SystemProperties props;
// name of data source to which this mapping writes
DbSource source;
// name of datasource
String sourceName;
// name of db table
String table;
// list of properties to try for parent
ParentInfo[] parent;
// list of properties to try as skinmanager
String[] skinmgr;
// DbMapping subnodes;
// DbMapping properties;
Relation subnodesRel;
Relation propertiesRel;
// if this defines a subnode mapping with groupby layer, we need a DbMapping for those groupby nodes
DbMapping groupbyMapping;
// Map of property names to Relations objects
Hashtable prop2db;
// Map of db columns to Relations objects
Hashtable db2prop;
// db field used as primary key
String idField;
// db field used as object name
String nameField;
// db field used to identify name of prototype to use for object instantiation
String protoField;
// name of parent prototype, if any
String extendsProto;
// dbmapping of parent prototype, if any
DbMapping parentMapping;
boolean inheritsMapping;
// db field that specifies the prototype of an object
String prototypeField;
// descriptor for key generation method
private String idgen;
// remember last key generated for this table
long lastID;
// the (village) schema of the database table
Schema schema = null;
// the (village) keydef of the db table
KeyDef keydef = null;
// timestamp of last modification of the mapping (type.properties)
long lastTypeChange;
// timestamp of last modification of an object of this type
long lastDataChange;
/**
* Create an empty DbMapping
*/
public DbMapping () {
prop2db = new Hashtable ();
db2prop = new Hashtable ();
parent = null;
// subnodes = null;
// properties = null;
idField = "id";
}
/**
* Create a DbMapping from a type.properties property file
*/
public DbMapping (Application app, String typename, SystemProperties props) {
this.app = app;
this.typename = typename;
prop2db = new Hashtable ();
db2prop = new Hashtable ();
parent = null;
// subnodes = null;
// properties = null;
idField = "id";
this.props = props;
update ();
app.putDbMapping (typename, this);
}
/**
* Tell the type manager whether we need update() to be called
*/
public boolean needsUpdate () {
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.
*/
public synchronized void update () {
table = props.getProperty ("_tablename");
idgen = props.getProperty ("_idgen");
// see if there is a field which specifies the prototype of objects, if different prototypes
// can be stored in this table
prototypeField = props.getProperty ("_prototypefield");
// see if this prototype extends (inherits from) any other prototype
extendsProto = props.getProperty ("_extends");
sourceName = props.getProperty ("_datasource");
if (sourceName != null) {
source = app.getDbSource (sourceName);
if (source == null) {
app.logEvent ("*** Data Source for prototype "+typename+" does not exist: "+sourceName);
app.logEvent ("*** accessing or storing a "+typename+" object will cause an error.");
}
}
// id field must not be null, default is "id"
idField = props.getProperty ("_id", "id");
nameField = props.getProperty ("_name");
protoField = props.getProperty ("_prototype");
String parentMapping = props.getProperty ("_parent");
if (parentMapping != null) {
// comma-separated list of properties to be used as parent
StringTokenizer st = new StringTokenizer (parentMapping, ",;");
parent = new ParentInfo[st.countTokens()];
for (int i=0; i<parent.length; i++)
parent[i] = new ParentInfo (st.nextToken().trim());
} else
parent = null;
String skm = props.getProperty ("_skinmanager");
if (skm != null) {
StringTokenizer st = new StringTokenizer (skm, ",;");
skinmgr = new String[st.countTokens()];
for (int i=0; i<skinmgr.length; i++)
skinmgr[i] = st.nextToken().trim();
} else
skinmgr = null;
lastTypeChange = props.lastModified ();
// set the cached schema & keydef to null so it's rebuilt the next time around
schema = null;
keydef = null;
}
/**
* This is the second part of the property reading process, called after the first part has been
* completed on all other mappings in this application
*/
public synchronized void rewire () {
if (extendsProto != null) {
parentMapping = app.getDbMapping (extendsProto);
}
// if (table != null && source != null) {
// app.logEvent ("set data source for "+typename+" to "+source);
Hashtable p2d = new Hashtable ();
Hashtable d2p = new Hashtable ();
for (Enumeration e=props.keys(); e.hasMoreElements(); ) {
String propName = (String) e.nextElement ();
try {
if (!propName.startsWith ("_") && propName.indexOf (".") < 0) {
String dbField = props.getProperty (propName);
// check if a relation for this propery already exists. If so, reuse it
Relation rel = propertyToRelation (propName);
if (rel == null)
rel = new Relation (dbField, propName, this, props);
else
rel.update (dbField, props);
p2d.put (propName, rel);
if (rel.columnName != null &&
(rel.reftype == Relation.PRIMITIVE ||
rel.reftype == Relation.REFERENCE))
d2p.put (rel.columnName, rel);
// app.logEvent ("Mapping "+propName+" -> "+dbField);
}
} catch (Exception x) {
app.logEvent ("Error in type.properties: "+x.getMessage ());
}
}
prop2db = p2d;
db2prop = d2p;
String subnodeMapping = props.getProperty ("_subnodes");
if (subnodeMapping != null) {
try {
// check if subnode relation already exists. If so, reuse it
if (subnodesRel == null)
subnodesRel = new Relation (subnodeMapping, "_subnodes", this, props);
else
subnodesRel.update (subnodeMapping, props);
// if (subnodesRel.isReference ())
// subnodes = subnodesRel.other;
// else
// subnodes = (DbMapping) app.getDbMapping (subnodeMapping);
} catch (Exception x) {
app.logEvent ("Error reading _subnodes relation for "+typename+": "+x.getMessage ());
// subnodesRel = null;
}
} else
subnodesRel = null;
String propertiesMapping = props.getProperty ("_properties");
if (propertiesMapping != null) {
try {
// check if property relation already exists. If so, reuse it
if (propertiesRel == null)
propertiesRel = new Relation (propertiesMapping, "_properties", this, props);
else
propertiesRel.update (propertiesMapping, props);
// if (propertiesRel.isReference ())
// properties = propertiesRel.other;
// else
// properties = (DbMapping) app.getDbMapping (propertiesMapping);
// take over groupby flag from subnodes, if properties are subnodes
if (propertiesRel.subnodesAreProperties && subnodesRel != null)
propertiesRel.groupby = subnodesRel.groupby;
} catch (Exception x) {
app.logEvent ("Error reading _properties relation for "+typename+": "+x.getMessage ());
// propertiesRel = null;
}
} else
propertiesRel = null;
if (groupbyMapping != null) {
groupbyMapping.subnodesRel = subnodesRel == null ? null : subnodesRel.getGroupbySubnodeRelation ();
groupbyMapping.propertiesRel = propertiesRel == null ? null : propertiesRel.getGroupbyPropertyRelation ();
groupbyMapping.lastTypeChange = this.lastTypeChange;
}
}
public Connection getConnection () throws ClassNotFoundException, SQLException {
// if source was previously not available, check again
if (source == null && sourceName != null)
source = app.getDbSource (sourceName);
if (sourceName == null && parentMapping != null)
return parentMapping.getConnection ();
if (source == null) {
if (sourceName == null)
throw new SQLException ("Tried to get Connection from non-relational embedded data source.");
else
throw new SQLException ("Datasource is not defined: "+sourceName+".");
}
return source.getConnection ();
}
public DbSource getDbSource () {
if (source == null && parentMapping != null)
return parentMapping.getDbSource ();
return source;
}
public String getSourceID () {
if (source == null && parentMapping != null)
return parentMapping.getSourceID ();
return source == null ? "" : source.url;
}
public String getTableName () {
if (source == null && parentMapping != null)
return parentMapping.getTableName ();
return table;
}
public Application getApplication () {
return app;
}
public String getAppName () {
return app.getName();
}
public String getTypeName () {
return typename;
}
public String getExtends () {
return extendsProto;
}
/**
* Get the primary key column name for objects using this mapping.
*/
public String getIDField () {
if (idField == null && parentMapping != null)
return parentMapping.getIDField ();
return idField;
}
/**
* Get the column used for (internal) names of objects of this type.
*/
public String getNameField () {
if (nameField == null && parentMapping != null)
return parentMapping.getNameField ();
return nameField;
}
/**
* Get the column used for names of prototype.
*/
public String getPrototypeField () {
if (protoField == null && parentMapping != null)
return parentMapping.getPrototypeField ();
return protoField;
}
/**
* Translate a database column name to an object property name according to this mapping.
*/
public String columnNameToProperty (String columnName) {
if (columnName == null)
return null;
if (table == null && parentMapping != null)
return parentMapping.columnNameToProperty (columnName);
Relation rel = (Relation) db2prop.get (columnName);
if (rel != null && (rel.reftype == Relation.PRIMITIVE || rel.reftype == Relation.REFERENCE))
return rel.propName;
return null;
}
/**
* Translate an object property name to a database column name according to this mapping.
*/
public String propertyToColumnName (String propName) {
if (propName == null)
return null;
if (table == null && parentMapping != null)
return parentMapping.propertyToColumnName (propName);
Relation rel = (Relation) prop2db.get (propName);
if (rel != null && (rel.reftype == Relation.PRIMITIVE || rel.reftype == Relation.REFERENCE))
return rel.columnName;
return null;
}
/**
* Translate a database column name to an object property name according to this mapping.
*/
public Relation columnNameToRelation (String columnName) {
if (columnName == null)
return null;
if (table == null && parentMapping != null)
return parentMapping.columnNameToRelation (columnName);
return (Relation) db2prop.get (columnName);
}
/**
* Translate an object property name to a database column name according to this mapping.
*/
public Relation propertyToRelation (String propName) {
if (propName == null)
return null;
if (table == null && parentMapping != null)
return parentMapping.propertyToRelation (propName);
return (Relation) prop2db.get (propName);
}
public synchronized ParentInfo[] getParentInfo () {
if (parent == null && parentMapping != null)
return parentMapping.getParentInfo ();
return parent;
}
public String[] getSkinManagers () {
return skinmgr;
}
public DbMapping getSubnodeMapping () {
if (subnodesRel != null)
return subnodesRel.otherType;
if (parentMapping != null)
return parentMapping.getSubnodeMapping ();
return null;
}
public DbMapping getExactPropertyMapping (String propname) {
if (propname == null)
return null;
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
if (rel == null && parentMapping != null)
return parentMapping.getExactPropertyMapping (propname);
return rel != null ? rel.otherType : null;
}
public DbMapping getPropertyMapping (String propname) {
if (propname == null) {
if (propertiesRel != null)
return propertiesRel.otherType;
if (parentMapping != null)
return parentMapping.getPropertyMapping (null);
}
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
if (rel != null) {
// if this is a virtual node, it doesn't have a dbmapping
if (rel.virtual && rel.prototype == null)
return null;
else
return rel.otherType;
}
if (propertiesRel != null)
return propertiesRel.otherType;
if (parentMapping != null)
return parentMapping.getPropertyMapping (propname);
return null;
}
public DbMapping getGroupbyMapping () {
if (subnodesRel == null || subnodesRel.groupby == null)
return null;
if (groupbyMapping == null) {
groupbyMapping = new DbMapping ();
groupbyMapping.subnodesRel = subnodesRel.getGroupbySubnodeRelation ();
if (propertiesRel != null)
groupbyMapping.propertiesRel = propertiesRel.getGroupbyPropertyRelation ();
else
groupbyMapping.propertiesRel = subnodesRel.getGroupbyPropertyRelation ();
groupbyMapping.typename = subnodesRel.groupbyprototype;
}
return groupbyMapping;
}
/* public void setPropertyMapping (DbMapping pm) {
properties = pm;
} */
public void setSubnodeRelation (Relation rel) {
subnodesRel = rel;
}
public void setPropertyRelation (Relation rel) {
propertiesRel = rel;
}
public Relation getSubnodeRelation () {
if (subnodesRel == null && parentMapping != null)
return parentMapping.getSubnodeRelation ();
return subnodesRel;
}
public Relation getPropertyRelation () {
if (propertiesRel == null && parentMapping != null)
return parentMapping.getPropertyRelation ();
return propertiesRel;
}
public Relation getPropertyRelation (String propname) {
if (propname == null)
return getPropertyRelation ();
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
if (rel == null && propertiesRel == null && parentMapping != null)
return parentMapping.getPropertyRelation (propname);
return rel != null ? rel : propertiesRel;
}
public String getSubnodeGroupby () {
if (subnodesRel == null && parentMapping != null)
return parentMapping.getSubnodeGroupby ();
return subnodesRel == null ? null : subnodesRel.groupby;
}
public String getIDgen () {
if (idgen == null && parentMapping != null)
return parentMapping.getIDgen ();
return idgen;
}
public WrappedNodeManager getWrappedNodeManager () {
if (app == null)
throw new RuntimeException ("Can't get node manager from internal db mapping");
return app.getWrappedNodeManager ();
}
/**
* Tell whether this data mapping maps to a relational database table. This returns true
* if a datasource is specified, even if it is not a valid one. Otherwise, objects with invalid
* mappings would be stored in the embedded db instead of an error being thrown, which is
* not what we want.
*/
public boolean isRelational () {
if (sourceName != null)
return true;
if (parentMapping != null)
return parentMapping.isRelational ();
return false;
}
/**
* Return a Village Schema object for this DbMapping.
*/
public synchronized Schema getSchema () throws ClassNotFoundException, SQLException, DataSetException {
if (!isRelational ())
throw new SQLException ("Can't get Schema for non-relational data mapping");
if (source == null && parentMapping != null)
return parentMapping.getSchema ();
// Use local variable s to avoid synchronization (schema may be nulled elsewhere)
Schema s = schema;
if (s != null)
return s;
schema = new Schema ().schema (getConnection (), table, "*");
return schema;
}
/**
* Return a Village Schema object for this DbMapping.
*/
public synchronized KeyDef getKeyDef () {
if (!isRelational ())
throw new RuntimeException ("Can't get KeyDef for non-relational data mapping");
if (source == null && parentMapping != null)
return parentMapping.getKeyDef ();
// Use local variable s to avoid synchronization (keydef may be nulled elsewhere)
KeyDef k = keydef;
if (k != null)
return k;
keydef = new KeyDef ().addAttrib (idField);
return keydef;
}
public String toString () {
if (app == null)
return "[unspecified internal DbMapping]";
else
return ("["+app.getName()+"."+typename+"]");
}
public long getLastTypeChange () {
return lastTypeChange;
}
public long getLastDataChange () {
return lastDataChange;
}
public void notifyDataChange () {
lastDataChange = System.currentTimeMillis ();
if (parentMapping != null && source == null)
parentMapping.notifyDataChange ();
}
public synchronized long getNewID (long dbmax) {
if (parentMapping != null && source == null)
return parentMapping.getNewID (dbmax);
lastID = Math.max (dbmax+1, lastID+1);
return lastID;
}
public Hashtable getProp2DB () {
if (table == null && parentMapping != null)
return parentMapping.getProp2DB ();
return prop2db;
}
public Hashtable getDB2Prop () {
if (table == null && parentMapping != null)
return parentMapping.getDB2Prop ();
return db2prop;
}
/**
* 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.
*/
public String getStorageTypeName () {
if (table == null && parentMapping != null)
return parentMapping.getStorageTypeName ();
return sourceName == null ? null : typename;
}
}

View file

@ -0,0 +1,121 @@
// DbSource.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
import java.sql.*;
import java.util.Hashtable;
import helma.util.SystemProperties;
/**
* This class describes a releational data source (URL, driver, user and password).
*/
public class DbSource {
private String name;
private SystemProperties props;
private static SystemProperties defaultProps = null;
protected String url;
private String driver;
protected String user;
private String password;
private long lastRead = 0l;
public DbSource (String name, SystemProperties props) throws ClassNotFoundException {
this.name = name;
this.props = props;
init ();
Server.getLogger().log ("created db source ["+name+", "+url+", "+driver+", "+user+"]");
}
public Connection getConnection () throws ClassNotFoundException, SQLException {
Transactor tx = (Transactor) Thread.currentThread ();
Connection con = tx.getConnection (this);
boolean fileUpdated = props.lastModified () > lastRead;
if (!fileUpdated && defaultProps != null)
fileUpdated = defaultProps.lastModified () > lastRead;
if (con == null || con.isClosed () || fileUpdated) {
init ();
Class.forName (driver);
con = DriverManager.getConnection (url, user, password);
// If we wanted to use SQL transactions, we'd set autoCommit to
// false here and make commit/rollback invocations in Transactor methods;
Server.getLogger().log ("Created new Connection to "+url);
tx.registerConnection (this, con);
//////////////////////////////////////////////
/* DatabaseMetaData meta = con.getMetaData ();
ResultSet tables = meta.getCatalogs ();
while (tables.next())
System.err.println ("********* TABLE: "+ tables.getObject (1));
ResultSet types = meta.getTypeInfo ();
while (types.next())
System.err.println ("******* TYPE: "+types.getObject(1) +" - "+types.getObject(2)+" - "+types.getObject(6));
*/
}
return con;
}
private void init () throws ClassNotFoundException {
lastRead = defaultProps == null ? props.lastModified () : Math.max (props.lastModified (), defaultProps.lastModified ());
url = props.getProperty (name+".url");
driver = props.getProperty (name+".driver");
Class.forName (driver);
user = props.getProperty (name+".user");
password = props.getProperty (name+".password");
}
public String getDriverName () {
return driver;
}
public String getName () {
return name;
}
public static void setDefaultProps (SystemProperties props) {
defaultProps = props;
}
}

View file

@ -0,0 +1,74 @@
// Key.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
/**
* This is the interface for the internal representation of an object key.
*
*/
public interface Key {
public Key getParentKey ();
public String getID ();
public String getStorageName ();
}

View file

@ -0,0 +1,41 @@
// ParentInfo.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
/**
* This class describes a parent relation between releational nodes.
*/
public class ParentInfo {
public final String propname;
public final String virtualname;
public final boolean named;
public final boolean isroot;
public ParentInfo (String desc) {
int n = desc.indexOf ("[named]");
named = n > -1;
String d = named ? desc.substring (0, n) : desc;
int dot = d.indexOf (".");
if (dot > -1) {
propname = d.substring (0, dot).trim();
virtualname = d.substring (dot+1).trim();
} else {
propname = d.trim();
virtualname = null;
}
isroot = "root".equals (propname);
// System.err.println ("created "+this);
}
public String toString () {
return "ParentInfo["+propname+","+virtualname+","+named+"]";
}
}

View file

@ -0,0 +1,655 @@
// Relation.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel.db;
import helma.objectmodel.*;
import helma.framework.core.Application;
import java.util.Properties;
import java.util.Vector;
/**
* This describes how a property of a persistent Object is stored in a
* relational database table. This can be either a scalar property (string, date, number etc.)
* or a reference to one or more other objects.
*/
public class Relation {
// these constants define different type of property-to-db-mappings
// there is an error in the description of this relation
public final static int INVALID = -1;
// a mapping of a non-object, scalar type
public final static int PRIMITIVE = 0;
// a 1-to-1 relation, i.e. a field in the table is a foreign key to another object
public final static int REFERENCE = 1;
// a 1-to-many relation, a field in another table points to objects of this type
public final static int COLLECTION = 2;
// direct mapping is a very powerful feature: objects of some types can be directly accessed
// by one of their properties/db fields.
// public final static int DIRECT = 3;
// the DbMapping of the type we come from
public DbMapping ownType;
// the DbMapping of the prototype we link to, unless this is a "primitive" (non-object) relation
public DbMapping otherType;
// if this relation defines a virtual node, we need to provide a DbMapping for these virtual nodes
DbMapping virtualMapping;
Relation virtualRelation;
Relation groupRelation;
public String propName;
protected String columnName;
public int reftype;
public Constraint[] constraints;
public boolean virtual;
public boolean readonly;
public boolean aggressiveLoading;
public boolean aggressiveCaching;
public boolean subnodesAreProperties;
public String accessor; // db column used to access objects through this relation
public String order;
public String groupbyorder;
public String groupby;
public String dogroupby;
public String prototype;
public String groupbyprototype;
public String filter;
// Relation subnoderelation = null; // additional relation used to filter subnodes for virtual nodes
/**
* This constructor makes a copy of an existing relation. Not all fields are copied, just those
* which are needed in groupby- and virtual nodes defined by this relation.
*/
public Relation (Relation rel) {
this.ownType = rel.ownType;
this.otherType = rel.otherType;
this.propName = rel.propName;
this.columnName = rel.columnName;
this.reftype = rel.reftype;
this.constraints = rel.constraints;
this.accessor = rel.accessor;
this.subnodesAreProperties = rel.subnodesAreProperties;
}
/**
* Reads a relation entry from a line in a properties file.
*/
public Relation (String desc, String propName, DbMapping ownType, Properties props) {
this.ownType = ownType;
this.propName = propName;
otherType = null;
update (desc, props);
}
public void update (String desc, Properties props) {
boolean mountpoint = false;
Vector cnst = null;
if (desc == null || "".equals (desc.trim ())) {
if (propName != null) {
reftype = PRIMITIVE;
columnName = propName;
} else {
reftype = INVALID;
columnName = propName;
}
} else {
desc = desc.trim ();
String descLower = desc.toLowerCase ();
if (descLower.startsWith ("[virtual]")) {
desc = desc.substring (9).trim ();
virtual = true;
} else if (descLower.startsWith ("[collection]")) {
desc = desc.substring (12).trim ();
virtual = true;
} else if (descLower.startsWith ("[mountpoint]")) {
desc = desc.substring (12).trim ();
virtual = true;
mountpoint = true;
} else {
virtual = false;
}
if (descLower.startsWith ("[readonly]")) {
desc = desc.substring (10).trim ();
readonly = true;
} else {
readonly = false;
}
}
// parse the basic properties of this mapping
parseMapping (desc, mountpoint);
// the following options only apply to object relations
if (reftype != PRIMITIVE && reftype != INVALID) {
cnst = new Vector ();
Constraint c = parseConstraint (desc);
if (c != null)
cnst.add (c);
parseOptions (cnst, props);
constraints = new Constraint[cnst.size()];
cnst.copyInto (constraints);
// System.err.println ("PARSED RELATION "+this);
// if (accessor != null)
// System.err.println ("SET ACCESSOR: "+accessor);
}
}
/**
* Parse a line describing a mapping of a property field. If the mapping is a
* object reference of a collection of objects, put any constraints in the Vector.
*/
protected void parseMapping (String desc, boolean mountpoint) {
Application app = ownType.getApplication ();
if (desc.indexOf ("<") > -1) {
reftype = COLLECTION;
int lt = desc.indexOf ("<");
int dot = desc.indexOf (".");
String other = dot < 0 ? desc.substring (lt+1).trim () : desc.substring (lt+1, dot).trim ();
otherType = app.getDbMapping (other);
if (otherType == null)
throw new RuntimeException ("DbMapping for "+other+" not found from "+ownType.typename);
columnName = null;
if (mountpoint)
prototype = other;
} else if (desc.indexOf (">") > -1) {
reftype = REFERENCE;
int bt = desc.indexOf (">");
int dot = desc.indexOf (".");
String other = dot > -1 ? desc.substring (bt+1, dot).trim () : desc.substring (bt+1).trim ();
otherType = app.getDbMapping (other);
if (otherType == null)
throw new RuntimeException ("DbMapping for "+other+" not found from "+ownType.typename);
columnName = desc.substring (0, bt).trim ();
if (mountpoint)
prototype = other;
} else if (desc.indexOf (".") > -1) {
reftype = COLLECTION;
int dot = desc.indexOf (".");
String other = desc.substring (0, dot).trim ();
otherType = app.getDbMapping (other);
if (otherType == null)
throw new RuntimeException ("DbMapping for "+other+" not found from "+ownType.typename);
columnName = null;
// set accessor
accessor = desc.substring (dot+1).trim ();
if (mountpoint)
prototype = other;
} else {
if (virtual) {
reftype = COLLECTION;
otherType = app.getDbMapping (desc);
if (otherType == null)
throw new RuntimeException ("DbMapping for "+desc+" not found from "+ownType.typename);
if (mountpoint)
prototype = desc;
} else {
reftype = PRIMITIVE;
columnName = desc.trim ();
}
}
}
/**
* Parse a line describing a mapping of a property field. If the mapping is a
* object reference of a collection of objects, put any constraints in the Vector.
*/
protected Constraint parseConstraint (String desc) {
if (desc.indexOf ("<") > -1) {
int lt = desc.indexOf ("<");
int dot = desc.indexOf (".");
String remoteField = dot < 0 ? null : desc.substring (dot+1).trim ();
String localField = lt <= 0 ? null : desc.substring (0, lt).trim ();
return new Constraint (localField, otherType.getTableName (), remoteField, false);
} else if (desc.indexOf (">") > -1) {
int bt = desc.indexOf (">");
int dot = desc.indexOf (".");
String localField = desc.substring (0, bt).trim ();
String remoteField = dot < 0 ? null : desc.substring (dot+1).trim ();
return new Constraint (localField, otherType.getTableName (), remoteField, false);
}
return null;
}
protected void parseOptions (Vector cnst, Properties props) {
String loading = props.getProperty (propName+".loadmode");
aggressiveLoading = loading != null && "aggressive".equalsIgnoreCase (loading.trim());
String caching = props.getProperty (propName+".cachemode");
aggressiveCaching = caching != null && "aggressive".equalsIgnoreCase (caching.trim());
// get order property
order = props.getProperty (propName+".order");
if (order != null && order.trim().length() == 0)
order = null;
// get additional filter property
filter = props.getProperty (propName+".filter");
if (filter != null && filter.trim().length() == 0)
filter = null;
// get group by property
groupby = props.getProperty (propName+".groupby");
if (groupby != null && groupby.trim().length() == 0)
groupby = null;
if (groupby != null) {
groupbyorder = props.getProperty (propName+".groupby.order");
if (groupbyorder != null && groupbyorder.trim().length() == 0)
groupbyorder = null;
groupbyprototype = props.getProperty (propName+".groupby.prototype");
if (groupbyprototype != null && groupbyprototype.trim().length() == 0)
groupbyprototype = null;
// aggressive loading and caching is not supported for groupby-nodes
aggressiveLoading = aggressiveCaching = false;
}
// check if subnode condition should be applied for property relations
if ("_properties".equalsIgnoreCase (propName) || virtual) {
String subnodes2props = props.getProperty (propName+".aresubnodes");
subnodesAreProperties = "true".equalsIgnoreCase (subnodes2props);
if (virtual) {
String subnodefilter = props.getProperty (propName+".subnoderelation");
if (subnodefilter != null) {
Constraint c = parseConstraint (subnodefilter);
if (c != null) {
cnst.add (c);
}
}
}
// update virtual mapping, if it already exists
if (virtualMapping != null) {
virtualMapping.subnodesRel = getVirtualSubnodeRelation ();
virtualMapping.propertiesRel = getVirtualPropertyRelation ();
virtualMapping.lastTypeChange = ownType.lastTypeChange;
}
}
}
/**
* Add a constraint to the current list of constraints
*/
protected void addConstraint (Constraint c) {
if (constraints == null) {
constraints = new Constraint[1];
constraints[0] = c;
} else {
Constraint[] nc = new Constraint[constraints.length+1];
System.arraycopy (constraints, 0, nc, 0, constraints.length);
nc[nc.length-1] = c;
constraints = nc;
}
}
public boolean usesPrimaryKey () {
if (otherType != null) {
if (reftype == REFERENCE)
return constraints.length == 1 && constraints[0].foreignKeyIsPrimary ();
if (reftype == COLLECTION)
return accessor == null || accessor.equals (otherType.getIDField ());
}
return false;
/*
if (otherType == null)
return false;
if (remoteField == null)
// if remote field is null, it is assumed that it points to the primary key
return true;
return remoteField.equalsIgnoreCase (otherType.getIDField()); */
}
public Relation getSubnodeRelation () {
// return subnoderelation;
return null;
}
/**
* Return the local field name for updates.
*/
public String getDbField () {
return columnName;
}
/**
* Get the local column name for this relation to use in where clauses of select statements.
* This uses the home node's id as fallback if local field is not specified.
*/
/* public String[] getLocalFields () {
if (constraints == null)
return new String[0];
String[] retval = new String[constraints.length];
for (int i=0; i<constraints.length; i++)
retval[i] = constraints[i].localName;
return retval;
} */
/**
* Get the "remote" column name for this relation. Uses the remote node's id as fallback if the remote field is not specified.
*/
/* public String[] getRemoteFields () {
if (constraints == null)
return new String[0];
String[] retval = new String[constraints.length];
for (int i=0; i<constraints.length; i++)
retval[i] = constraints[i].foreignName;
return retval;
} */
public DbMapping getVirtualMapping () {
if (!virtual)
return null;
if (virtualMapping == null) {
virtualMapping = new DbMapping ();
virtualMapping.subnodesRel = getVirtualSubnodeRelation ();
virtualMapping.propertiesRel = getVirtualPropertyRelation ();
}
return virtualMapping;
}
/**
* Return a Relation that defines the subnodes of a virtual node.
*/
Relation getVirtualSubnodeRelation () {
if (!virtual)
throw new RuntimeException ("getVirtualSubnodeRelation called on non-virtual relation");
Relation vr = new Relation (this);
vr.groupby = groupby;
vr.groupbyorder = groupbyorder;
vr.groupbyprototype = groupbyprototype;
vr.order = order;
vr.filter = filter;
vr.constraints = constraints;
vr.aggressiveLoading = aggressiveLoading;
vr.aggressiveCaching = aggressiveCaching;
return vr;
}
/**
* Return a Relation that defines the properties of a virtual node.
*/
Relation getVirtualPropertyRelation () {
if (!virtual)
throw new RuntimeException ("getVirtualPropertyRelation called on non-virtual relation");
Relation vr = new Relation (this);
vr.groupby = groupby;
vr.groupbyorder = groupbyorder;
vr.groupbyprototype = groupbyprototype;
vr.order = order;
vr.filter = filter;
vr.constraints = constraints;
return vr;
}
/**
* Return a Relation that defines the subnodes of a group-by node.
*/
Relation getGroupbySubnodeRelation () {
if (groupby == null)
throw new RuntimeException ("getGroupbySubnodeRelation called on non-group-by relation");
Relation vr = new Relation (this);
vr.order = order;
vr.prototype = groupbyprototype;
vr.filter = filter;
vr.constraints = constraints;
vr.addConstraint (new Constraint (null, null, groupby, true));
return vr;
}
/**
* Return a Relation that defines the properties of a group-by node.
*/
Relation getGroupbyPropertyRelation () {
if (groupby == null)
throw new RuntimeException ("getGroupbyPropertyRelation called on non-group-by relation");
Relation vr = new Relation (this);
vr.order = order;
vr.prototype = groupbyprototype;
vr.filter = filter;
vr.constraints = constraints;
vr.addConstraint (new Constraint (null, null, groupby, true));
return vr;
}
public String buildWhere (INode home, INode nonvirtual, String kstr) {
StringBuffer q = new StringBuffer ();
String prefix = "";
if (kstr != null) {
String accessColumn = accessor == null ? otherType.getIDField () : accessor;
q.append (accessColumn);
q.append (" = '");
q.append (escape (kstr));
q.append ("'");
prefix = " AND ";
}
for (int i=0; i<constraints.length; i++) {
q.append (prefix);
constraints[i].addToQuery (q, home, nonvirtual);
prefix = " AND ";
}
if (filter != null) {
q.append (prefix);
q.append (filter);
}
return q.toString ();
}
public String buildQuery (INode home, INode nonvirtual, String kstr) {
StringBuffer q = new StringBuffer ();
String prefix = " WHERE ";
if (kstr != null) {
q.append (prefix);
String accessColumn = accessor == null ? otherType.getIDField () : accessor;
q.append (accessColumn);
q.append (" = '");
q.append (escape (kstr));
q.append ("'");
prefix = " AND ";
}
for (int i=0; i<constraints.length; i++) {
q.append (prefix);
constraints[i].addToQuery (q, home, nonvirtual);
prefix = " AND ";
}
if (filter != null) {
q.append (prefix);
q.append (filter);
}
if (groupby != null)
q.append (" GROUP BY "+groupby);
if (order != null)
q.append (" ORDER BY "+order);
return q.toString ();
}
/**
* Check if the child node fullfills the constraints defined by this relation.
*/
public boolean checkConstraints (Node parent, Node child) {
for (int i=0; i<constraints.length; i++) {
String propname = otherType.columnNameToProperty (constraints[i].foreignName);
if (propname != null) {
INode home = constraints[i].isGroupby ? parent : parent.getNonVirtualParent ();
String localName = constraints[i].localName;
String value = null;
if (localName == null || localName.equals (ownType.getIDField ()))
value = home.getID ();
else if (ownType.isRelational ())
value = home.getString (ownType.columnNameToProperty (localName), false);
else
value = home.getString (localName, false);
if (value != null && !value.equals (child.getString (propname, false))) {
return false;
}
}
}
return true;
}
/**
* Make sure that the child node fullfills the constraints defined by this relation by setting the
* appropriate properties
*/
public void setConstraints (Node parent, Node child) {
for (int i=0; i<constraints.length; i++) {
String propname = otherType.columnNameToProperty (constraints[i].foreignName);
if (propname != null) {
INode home = constraints[i].isGroupby ? parent : parent.getNonVirtualParent ();
String localName = constraints[i].localName;
String value = null;
if (localName == null || localName.equals (ownType.getIDField ()))
value = home.getID ();
else if (ownType.isRelational ())
value = home.getString (ownType.columnNameToProperty (localName), false);
else
value = home.getString (localName, false);
if (value != null) {
System.err.println ("SETTING "+child+"."+propname+" TO "+value);
child.setString (propname, value);
}
}
}
}
// a utility method to escape single quotes
String escape (String str) {
if (str == null)
return null;
if (str.indexOf ("'") < 0)
return str;
int l = str.length();
StringBuffer sbuf = new StringBuffer (l + 10);
for (int i=0; i<l; i++) {
char c = str.charAt (i);
if (c == '\'')
sbuf.append ("\\");
sbuf.append (c);
}
return sbuf.toString ();
}
public String toString () {
String c = "";
if (constraints != null) {
for (int i=0; i<constraints.length; i++)
c += constraints[i].toString ();
}
return "Relation["+ownType+"."+propName+">"+otherType+"]" + c;
}
/**
* The Constraint class represents a part of the where clause in the query used to
* establish a relation between database mapped objects.
*/
class Constraint {
String localName;
String tableName;
String foreignName;
boolean isGroupby;
Constraint (String local, String table, String foreign, boolean groupby) {
localName = local;
tableName = table;
foreignName = foreign;
isGroupby = groupby;
}
public void addToQuery (StringBuffer q, INode home, INode nonvirtual) {
String local = null;
INode ref = isGroupby ? home : nonvirtual;
if (localName == null)
local = ref.getID ();
else {
String homeprop = ownType.columnNameToProperty (localName);
local = ref.getString (homeprop, false);
}
q.append (foreignName);
q.append (" = '");
q.append (escape (local));
q.append ("'");
}
public boolean foreignKeyIsPrimary () {
return foreignName == null || foreignName.equals (otherType.getIDField ());
}
public String toString () {
return ownType+"."+localName+"="+tableName+"."+foreignName;
}
}
}

View file

@ -0,0 +1,118 @@
// SyntheticKey.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.io.Serializable;
/**
* This is the internal key for an object that is not - or not directly - fetched from a db,
* but derived from another object. This is useful for all kinds of object accessed via a
* symbolic name from another object, like objects mounted via a property name column,
* virtual nodes and groupby nodes.
*/
public final class SyntheticKey implements Key, Serializable {
private final Key parentKey;
private final String name;
/**
* make a key for a persistent Object, describing its datasource and id.
*/
public SyntheticKey (Key key, String name) {
this.parentKey = key;
this.name = name;
}
public boolean equals (Object what) {
if (what == this)
return true;
try {
SyntheticKey k = (SyntheticKey) what;
return parentKey.equals (k.parentKey) &&
(name == k.name || name.equals (k.name));
} catch (Exception x) {
return false;
}
}
public int hashCode () {
return name.hashCode () + parentKey.hashCode ();
}
public Key getParentKey () {
return parentKey;
}
public String getID () {
return name;
}
public String getStorageName () {
return null;
}
public String toString () {
return parentKey+"/"+name;
}
}