diff --git a/src/helma/objectmodel/db/DbKey.java b/src/helma/objectmodel/db/DbKey.java new file mode 100644 index 00000000..d5fc2a77 --- /dev/null +++ b/src/helma/objectmodel/db/DbKey.java @@ -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+"]"; + } + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java new file mode 100644 index 00000000..40e53b78 --- /dev/null +++ b/src/helma/objectmodel/db/DbMapping.java @@ -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 "+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; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/db/DbSource.java b/src/helma/objectmodel/db/DbSource.java new file mode 100644 index 00000000..b853e65d --- /dev/null +++ b/src/helma/objectmodel/db/DbSource.java @@ -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; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/db/Key.java b/src/helma/objectmodel/db/Key.java new file mode 100644 index 00000000..f48d66ab --- /dev/null +++ b/src/helma/objectmodel/db/Key.java @@ -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 (); + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/db/ParentInfo.java b/src/helma/objectmodel/db/ParentInfo.java new file mode 100644 index 00000000..4931e013 --- /dev/null +++ b/src/helma/objectmodel/db/ParentInfo.java @@ -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+"]"; + } + +} diff --git a/src/helma/objectmodel/db/Relation.java b/src/helma/objectmodel/db/Relation.java new file mode 100644 index 00000000..a908fd93 --- /dev/null +++ b/src/helma/objectmodel/db/Relation.java @@ -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"+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; + } + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/db/SyntheticKey.java b/src/helma/objectmodel/db/SyntheticKey.java new file mode 100644 index 00000000..86934505 --- /dev/null +++ b/src/helma/objectmodel/db/SyntheticKey.java @@ -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; + } + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +