001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc;
012:
013: import com.versant.core.metadata.parser.JdoExtension;
014: import com.versant.core.metadata.parser.JdoElement;
015: import com.versant.core.metadata.parser.JdoExtensionKeys;
016: import com.versant.core.metadata.MetaDataBuilder;
017: import com.versant.core.metadata.ClassMetaData;
018: import com.versant.core.metadata.ClassIdTranslator;
019: import com.versant.core.jdbc.metadata.*;
020: import com.versant.core.util.IntObjectHashMap;
021:
022: import java.util.*;
023:
024: import com.versant.core.common.BindingSupportImpl;
025:
026: /**
027: * This will analyze meta data for a reference to any PC class from an
028: * array of extensions. It is used for polyref fields and for polymorphic link
029: * tables. Instances of this class may only be used once.
030: */
031: public class JdbcPolyRefMetaDataBuilder implements JdoExtensionKeys {
032:
033: private JdbcMetaDataBuilder mdb;
034: private JdoElement context;
035: private String fieldName;
036: private JdbcField field;
037:
038: private ArrayList colsList = new ArrayList();
039:
040: private JdbcColumn classIdCol;
041: private JdbcColumn[] pkCols;
042: private JdbcColumn[] cols;
043:
044: private HashSet classIdSet = new HashSet(); // to detect duplicate IDs
045:
046: private boolean stringClassIds; // is at least one class-id in map not int
047:
048: // ClassMetaData -> String for ID translation
049: private Map cmdToIDString = new HashMap();
050: private Map idToCmdString; // String ID -> ClassMetaData
051:
052: // contains int values of class-ids if all are ints
053: private Map cmdToIDInt = new HashMap();
054: private IntObjectHashMap idToCmdInt; // int ID -> ClassMetaData
055:
056: private ClassIdTranslator classIdTranslator;
057:
058: public JdbcPolyRefMetaDataBuilder(JdbcMetaDataBuilder mdb,
059: JdoElement context, String fieldName,
060: JdoExtension[] extensions, JdbcField field) {
061: this .mdb = mdb;
062: this .context = context;
063: this .fieldName = fieldName;
064: this .field = field;
065: process(extensions);
066: }
067:
068: private void process(JdoExtension[] extensions) {
069: JdoExtension classIdColExt = null;
070: if (extensions != null) {
071: int ne = extensions.length;
072: for (int i = 0; i < ne; i++) {
073: JdoExtension e = extensions[i];
074: switch (e.key) {
075:
076: case JDBC_CLASS_ID:
077: if (classIdColExt != null) {
078: throw BindingSupportImpl.getInstance().runtime(
079: "Only one jdbc-class-id extension is allowed\n"
080: + context);
081: }
082: classIdColExt = e;
083: break;
084:
085: case JDBC_REF:
086: colsList.add(mdb.createColumn(e.nested, fieldName,
087: Integer.class));
088: break;
089:
090: case VALID_CLASS:
091: processClassIdMapping(e);
092: break;
093:
094: default:
095: if (e.isJdbc())
096: MetaDataBuilder.throwUnexpectedExtension(e);
097: break;
098: }
099: }
100: }
101:
102: // convert all the class-id's to ints if possible
103: try {
104: for (Iterator i = cmdToIDString.keySet().iterator(); i
105: .hasNext();) {
106: ClassMetaData key = (ClassMetaData) i.next();
107: String value = (String) cmdToIDString.get(key);
108: cmdToIDInt.put(key, new Integer(value));
109: }
110: } catch (NumberFormatException e) {
111: stringClassIds = true;
112: }
113:
114: // get rid of empty maps and init id -> class maps
115: if (cmdToIDString.size() == 0)
116: cmdToIDString = null;
117: if (cmdToIDInt.size() == 0)
118: cmdToIDInt = null;
119:
120: if (cmdToIDInt != null) { // build reverse map
121: idToCmdInt = new IntObjectHashMap();
122: for (Iterator i = cmdToIDInt.keySet().iterator(); i
123: .hasNext();) {
124: ClassMetaData key = (ClassMetaData) i.next();
125: int value = ((Integer) cmdToIDInt.get(key)).intValue();
126: idToCmdInt.put(value, key);
127: }
128: }
129:
130: if (cmdToIDString != null) { // build reverse map
131: idToCmdString = new HashMap();
132: for (Iterator i = cmdToIDString.keySet().iterator(); i
133: .hasNext();) {
134: ClassMetaData key = (ClassMetaData) i.next();
135: String value = (String) cmdToIDString.get(key);
136: idToCmdString.put(value, key);
137: }
138: }
139:
140: // create the translator
141: classIdTranslator = new ClassIdTranslator(
142: field.fmd.classMetaData.jmd, stringClassIds,
143: cmdToIDString, idToCmdString, cmdToIDInt, idToCmdInt);
144: classIdTranslator.setMessage("field " + field.fmd.getQName());
145:
146: // make sure all possible classes have the same number of primary
147: // key columns if any valid-class elements were used and also get
148: // the primary key of the first class
149: JdbcColumn[] firstPk = null;
150: List cl = classIdTranslator.getClassList();
151: int n = cl.size();
152: if (n > 0) {
153: ClassMetaData first = (ClassMetaData) cl.get(0);
154: firstPk = ((JdbcClass) first.storeClass).table.pk;
155: for (int i = 1; i < n; i++) {
156: ClassMetaData cmd = (ClassMetaData) cl.get(i);
157: int len = ((JdbcClass) cmd.storeClass).table.pk.length;
158: if (len != firstPk.length) {
159: throw BindingSupportImpl.getInstance().runtime(
160: "All valid classes must have the same number of "
161: + "primary key columns\n" + first
162: + " has " + firstPk.length + "\n"
163: + cmd + " has " + len + "\n"
164: + field.fmd.jdoField.getContext());
165: }
166: }
167: }
168:
169: // generate the classIdCol
170: classIdCol = mdb.createColumn(classIdColExt == null ? null
171: : classIdColExt.nested, fieldName,
172: stringClassIds ? String.class : Integer.class);
173:
174: // generate primary key column(s) if none specified
175: if (colsList.isEmpty()) {
176: if (firstPk != null) {
177: for (int i = 0; i < firstPk.length; i++) {
178: JdbcColumn c = mdb.createColumn(null, firstPk[i]);
179: c.pk = false;
180: colsList.add(c);
181: }
182: } else {
183: colsList.add(mdb.createColumn(null, fieldName,
184: Integer.class));
185: }
186: } else if (firstPk != null && colsList.size() != firstPk.length) {
187: throw BindingSupportImpl.getInstance().runtime(
188: "Mismatched reference column(s): "
189: + colsList.size()
190: + " reference column(s) defined but "
191: + "valid classes have " + firstPk.length
192: + " primary key column(s)\n"
193: + field.fmd.jdoField.getContext());
194: }
195:
196: pkCols = new JdbcColumn[colsList.size()];
197: colsList.toArray(pkCols);
198: colsList.add(0, classIdCol);
199: cols = new JdbcColumn[colsList.size()];
200: colsList.toArray(cols);
201: }
202:
203: public JdbcColumn getClassIdCol() {
204: return classIdCol;
205: }
206:
207: public JdbcColumn[] getPkCols() {
208: return pkCols;
209: }
210:
211: public JdbcColumn[] getCols() {
212: return cols;
213: }
214:
215: public ArrayList getColsList() {
216: return colsList;
217: }
218:
219: /**
220: * Get a translator for the class-id's of this field.
221: */
222: public ClassIdTranslator getClassIdTranslator() {
223: return classIdTranslator;
224: }
225:
226: private void processClassIdMapping(JdoExtension e) {
227: String value = e.getString();
228: int i = value.indexOf('=');
229: String cname = i < 0 ? value : value.substring(0, i);
230: String id = i < 0 ? null : value.substring(i + 1);
231: ClassMetaData key = mdb.getJmd().getClassMetaData(
232: field.fmd.classMetaData, cname);
233: if (key == null) {
234: throw BindingSupportImpl.getInstance().runtime(
235: "No persistent class found for '" + cname + "': "
236: + e + "\n" + e.getContext());
237: }
238: if (cmdToIDString.containsKey(key)) {
239: throw BindingSupportImpl.getInstance().runtime(
240: "Duplicate class in mapping '" + cname + "': " + e
241: + "\n" + e.getContext());
242: }
243: if (id == null || id.length() == 0)
244: id = Integer.toString(key.classId);
245: if (classIdSet.contains(id)) {
246: throw BindingSupportImpl.getInstance().runtime(
247: "Duplicate class-id in mapping '" + id + "': " + e
248: + "\n" + e.getContext());
249: }
250: cmdToIDString.put(key, id);
251: classIdSet.add(id);
252: }
253:
254: }
|