package com.openMap1.mapper.query; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import com.openMap1.mapper.CrossCondition; import com.openMap1.mapper.Mapping; import com.openMap1.mapper.MappingCondition; import com.openMap1.mapper.ObjMapping; import com.openMap1.mapper.PropMapping; import com.openMap1.mapper.ValueCondition; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.mapping.propertyMapping; /** * * Superclass of QueryClass, LinkAssociation, WriteField and QueryCondition, * supporting methods needed when setting up an SQLQuery. * * Has some methods common only to WriteField and QueryCondition, * defined because they both depend on a set of PropMappings * * * @author Robert * */ abstract public class QueryMappingUser { protected QueryParser parser; /* for each data source, all property mappings needed to populate the field. * There may be more than one for each property in a write field or condition, because of property conversions. */ protected Hashtable<String,Vector<PropMapping>> allMappings; public QueryMappingUser(QueryParser parser) { this.parser = parser; allMappings = new Hashtable<String,Vector<PropMapping>>(); } /** * set up all the property mappings needed to populate the field from the data source. * There may be more than one because of property conversions * @param code * @param mainPropMapping */ public void addMappings(String code, propertyMapping propMapping) { Vector<PropMapping> mappings = allMappings.get(code); if (mappings == null) mappings = new Vector<PropMapping>(); // for fixed values in object mappings, the class cast won't work if (propMapping.map() instanceof PropMapping) mappings.add((PropMapping)propMapping.map()); allMappings.put(code,mappings); } /** * @param code * @return all the property mappings needed to populate the field from the data source. * There may be more than one, because of property conversions */ public Vector<PropMapping> getMappings(String code) { return allMappings.get(code); } /** * add all tables, columns and conditions to an SQLQuery to ensure it retrieves the smallest DOM * required to support a query */ abstract void buildQuery(SQLQuery query, String code) throws MapperException; /** * * @param mapping a mapping to a relational database source * @return the table name of the mapped node * @throws MapperException if the XPath of the mapping does not have the required form for a mapping to a * relational database */ protected String getTableName(Mapping mapping) throws MapperException { String tableName = null; String path = mapping.getStringRootPath(); int pathType = getPathType(path); // mappings to the top database node have no table if (pathType == DATABASE) {} // for mapppings to 'record' or to a column, pick out the second step in the path 'database/<TABLE>/record/.. else if ((pathType == RECORD) || (pathType == COLUMN)) { StringTokenizer st = new StringTokenizer(path,"/"); st.nextToken(); tableName = st.nextToken(); } else if (pathType == INVALID) throw new MapperException("Invalid path for RDBMS mapping: '" + path + "'"); return tableName; } public static int DATABASE = 0; public static int RECORD = 1; public static int COLUMN = 2; public static int INVALID = 3; /** * * @return one of the allowed path types for paths in mappings to RDBMS, * of the INVALID value */ protected int getPathType(String path) { int type = INVALID; StringTokenizer steps = new StringTokenizer(path,"/"); // 'database' (possible for Object mappings) if ((steps.countTokens() == 1) && (steps.nextToken().equals("database"))) type = DATABASE; // 'database/<Table name>/record' (possible for Object and Association mappings) if ((steps.countTokens() == 3) && (steps.nextToken().equals("database")) && (steps.nextToken().length() > 0) && (steps.nextToken().equals("record"))) type = RECORD; // 'database/<Table name>/record/<Column name> (possible for property mappings) */ if ((steps.countTokens() == 4) && (steps.nextToken().equals("database")) && (steps.nextToken().length() > 0) && (steps.nextToken().equals("record"))) type = COLUMN; return type; } /** * * @param relPath * @return from a relative path (involved in mapping conditions), the column name (which is the last step) */ protected String getColName(String relPath) { StringTokenizer steps = new StringTokenizer(relPath,"/"); String column = ""; while (steps.hasMoreTokens()) column = steps.nextToken(); return column; } /** * if a relative path (involved in a mapping condition) * includes a table name, by ending in <table name>/record/<column name>, * return the table name; otherwise return null * @param relPath * @return */ protected String getTableName(String relPath) throws MapperException { String tableName = null; StringTokenizer steps = new StringTokenizer(relPath,"/"); int is = 0; int ns = steps.countTokens(); if (ns > 2) while (steps.hasMoreTokens()) { is++; String step = steps.nextToken(); if (is == ns - 2) tableName = step; // e.g step 1 of 3, or 2 of 4, etc. if ((is == ns - 1) && (!step.equals("record"))) // next step must be 'record' throw new MapperException("Relative path '" + relPath + "' in a mapping to an RDBMS does not end in <Table name>/record/<column name> "); } return tableName; } /** * make all the additions to an SQLQuery necessary to handle a mapping condition - * which may be a value condition or a cross condition. * Tables need not be added - because they have been added when handling the * containing mappings. * But columns for RHS and LHS, and SQL conditions for their relation, need to be added * @param mc * @param map * @param query * @param code */ protected void handleMappingCondition(MappingCondition mc, Mapping mapping, SQLQuery query, String code, boolean isCore) throws MapperException { String leftTable = getTableName(mapping); String leftCol = getColName(mc.getLeftPath()); query.addOutputColumn(leftTable, leftCol,isCore); if (mc instanceof ValueCondition) { ValueCondition vc = (ValueCondition)mc; query.addValCondition(leftTable, leftCol, vc.getTest().toString(), vc.getRightValue()); } else if (mc instanceof CrossCondition) { CrossCondition cc = (CrossCondition)mc; String objClass = mapping.getQualifiedClassName(); String subset = mapping.getSubset(); ObjMapping om = getObjMapping(objClass,subset, code); String rightTable = getTableName(om); String rightCol = getColName(cc.getRightPath()); query.addOutputColumn(rightTable, rightCol,isCore); query.addCrossCondition(leftTable, leftCol, cc.getTest().toString(), rightTable, rightCol,isCore); } } /** * * @param objClass * @param subset * @param code * @return */ protected ObjMapping getObjMapping(String objClass,String subset,String code) throws MapperException { ObjMapping om = null; for (Iterator<QueryClass> it = parser.queryClasses().iterator();it.hasNext();) { QueryClass qc = it.next(); if (qc.className().equals(objClass)) { ObjMapping omp = qc.getMapping(code); if ((omp != null) && (omp.getSubset().equals(subset))) om = omp; } } if (om == null) throw new MapperException("Cannot find object mapping for class " + objClass + ", subset '" + subset + "'"); return om; } /** * * @param mapping * @param query * @param code */ protected void handlePropMapping(PropMapping mapping,SQLQuery query,String code, boolean isCore) throws MapperException { String path = mapping.getStringRootPath(); // only allowed case - property value is mapped to a column if (getPathType(path) == QueryMappingUser.COLUMN) { String tName = getTableName(mapping); query.addTable(tName,isCore); query.addOutputColumn(tName, getColName(path),isCore); // handle all mapping conditions for (Iterator<MappingCondition> iu = mapping.getMappingConditions().iterator();iu.hasNext();) handleMappingCondition(iu.next(),mapping,query,code,isCore); } else throw new MapperException("Invalid path to a property mapping in an RDBMS: '" + path + "'"); } protected void message(String s) {System.out.println(s);} }