001 package org.apache.ojb.broker.ant;
002
003 /* Copyright 2004-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.util.ArrayList;
019 import java.util.Arrays;
020 import java.util.HashMap;
021 import java.util.HashSet;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.Set;
026 import java.util.TreeMap;
027
028 import org.apache.commons.beanutils.DynaBean;
029 import org.apache.ddlutils.model.Column;
030 import org.apache.ddlutils.model.Database;
031 import org.apache.ddlutils.model.Table;
032 import org.apache.ojb.broker.metadata.ClassDescriptor;
033 import org.apache.ojb.broker.metadata.CollectionDescriptor;
034 import org.apache.ojb.broker.metadata.DescriptorRepository;
035 import org.apache.ojb.broker.metadata.FieldDescriptor;
036
037 /**
038 * Provides a model derived from {@link org.apache.ojb.broker.metadata.DescriptorRepository} that
039 * is preprocessed for data handling (inserting data, generating data dtd).
040 *
041 * @author Thomas Dudziak
042 */
043 public class PreparedModel
044 {
045 /** The database model. */
046 private Database _schema;
047 /** Maps dtd elements to tables */
048 private TreeMap _elementToTable = new TreeMap();
049 /** Maps dtd elements to lists of class descriptors (which all map to the same table) */
050 private HashMap _elementToClassDescriptors = new HashMap();
051 /** Maps dtd elements to colum maps which in turn map attribute names to columns */
052 private HashMap _elementToColumnMap = new HashMap();
053 /** Maps dtd elements to maps that specify which attributes are required */
054 private HashMap _elementToRequiredAttributesMap = new HashMap();
055
056 public PreparedModel(DescriptorRepository model, Database schema)
057 {
058 _schema = schema;
059 prepareModel(model);
060 }
061
062 public Iterator getElementNames()
063 {
064 return _elementToTable.keySet().iterator();
065 }
066
067 public Iterator getAttributeNames(String elementName)
068 {
069 Map columns = getColumnsFor(elementName);
070
071 return columns == null ? null : columns.keySet().iterator();
072 }
073
074 public Map getRequiredAttributes(String elementName)
075 {
076 return (Map)_elementToRequiredAttributesMap.get(elementName);
077 }
078
079 public boolean isRequired(String elementName, String attributeName)
080 {
081 Map requiredAttributes = getRequiredAttributes(elementName);
082
083 if (requiredAttributes == null)
084 {
085 return false;
086 }
087 else
088 {
089 Boolean status = (Boolean)requiredAttributes.get(attributeName);
090
091 return status == null ? false : status.booleanValue();
092 }
093 }
094
095 public Table getTableFor(String elementName)
096 {
097 return (Table)_elementToTable.get(elementName);
098 }
099
100 /**
101 * Creates a dyna bean for the table associated to the given element.
102 *
103 * @param elementName The element name
104 * @return The dyna bean
105 */
106 public DynaBean createBeanFor(String elementName)
107 {
108 return _schema.createDynaBeanFor(getTableFor(elementName));
109 }
110
111 public List getClassDescriptorsMappingTo(String elementName)
112 {
113 return (List)_elementToClassDescriptors.get(elementName);
114 }
115
116 public Map getColumnsFor(String elementName)
117 {
118 return (Map)_elementToColumnMap.get(elementName);
119 }
120
121 public Column getColumnFor(String elementName, String attrName)
122 {
123 Map columns = getColumnsFor(elementName);
124
125 if (columns == null)
126 {
127 return null;
128 }
129 else
130 {
131 return (Column)columns.get(attrName);
132 }
133 }
134
135 /**
136 * Prepares a representation of the model that is easier accessible for our purposes.
137 *
138 * @param model The original model
139 * @return The model representation
140 */
141 private void prepareModel(DescriptorRepository model)
142 {
143 TreeMap result = new TreeMap();
144
145 for (Iterator it = model.getDescriptorTable().values().iterator(); it.hasNext();)
146 {
147 ClassDescriptor classDesc = (ClassDescriptor)it.next();
148
149 if (classDesc.getFullTableName() == null)
150 {
151 // not mapped to a database table
152 continue;
153 }
154
155 String elementName = getElementName(classDesc);
156 Table mappedTable = getTableFor(elementName);
157 Map columnsMap = getColumnsFor(elementName);
158 Map requiredAttributes = getRequiredAttributes(elementName);
159 List classDescs = getClassDescriptorsMappingTo(elementName);
160
161 if (mappedTable == null)
162 {
163 mappedTable = _schema.findTable(classDesc.getFullTableName());
164 if (mappedTable == null)
165 {
166 continue;
167 }
168 columnsMap = new TreeMap();
169 requiredAttributes = new HashMap();
170 classDescs = new ArrayList();
171 _elementToTable.put(elementName, mappedTable);
172 _elementToClassDescriptors.put(elementName, classDescs);
173 _elementToColumnMap.put(elementName, columnsMap);
174 _elementToRequiredAttributesMap.put(elementName, requiredAttributes);
175 }
176 classDescs.add(classDesc);
177 extractAttributes(classDesc, mappedTable, columnsMap, requiredAttributes);
178 }
179 extractIndirectionTables(model, _schema);
180 }
181
182 private void extractAttributes(ClassDescriptor classDesc, Table mappedTable, Map columnsMap, Map requiredColumnsMap)
183 {
184 FieldDescriptor[] fieldDescs = classDesc.getFieldDescriptions();
185
186 if (fieldDescs != null)
187 {
188 for (int idx = 0; idx < fieldDescs.length; idx++)
189 {
190 Column column = mappedTable.findColumn(fieldDescs[idx].getColumnName());
191
192 if (column != null)
193 {
194 // we'll check whether another field (of not necessarily the same name)
195 // already maps to this column; if this is the case, we're ignoring
196 // this field
197 boolean alreadyMapped = false;
198
199 for (Iterator mappedColumnsIt = columnsMap.values().iterator(); mappedColumnsIt.hasNext();)
200 {
201 if (column.equals(mappedColumnsIt.next()))
202 {
203 alreadyMapped = true;
204 break;
205 }
206 }
207 if (!alreadyMapped)
208 {
209 String shortAttrName = getShortAttributeName(fieldDescs[idx].getAttributeName());
210
211 columnsMap.put(shortAttrName, column);
212 requiredColumnsMap.put(shortAttrName,
213 fieldDescs[idx].isPrimaryKey() ? Boolean.TRUE : Boolean.FALSE);
214 }
215 }
216 }
217 }
218 }
219
220 /**
221 * Extracts indirection tables from the given class descriptor, and adds elements
222 * for them. In contrast to normal elements, for indirection tables the element name
223 * matches the table name, and the attribute names match the column names.
224 *
225 * @param model The model
226 * @param elements The elements
227 */
228 private void extractIndirectionTables(DescriptorRepository model, Database schema)
229 {
230 HashMap indirectionTables = new HashMap();
231
232 // first we gather all participants for each m:n relationship
233 for (Iterator classDescIt = model.getDescriptorTable().values().iterator(); classDescIt.hasNext();)
234 {
235 ClassDescriptor classDesc = (ClassDescriptor)classDescIt.next();
236
237 for (Iterator collDescIt = classDesc.getCollectionDescriptors().iterator(); collDescIt.hasNext();)
238 {
239 CollectionDescriptor collDesc = (CollectionDescriptor)collDescIt.next();
240 String indirTable = collDesc.getIndirectionTable();
241
242 if ((indirTable != null) && (indirTable.length() > 0))
243 {
244 Set columns = (Set)indirectionTables.get(indirTable);
245
246 if (columns == null)
247 {
248 columns = new HashSet();
249 indirectionTables.put(indirTable, columns);
250 }
251 columns.addAll(Arrays.asList(collDesc.getFksToThisClass()));
252 columns.addAll(Arrays.asList(collDesc.getFksToItemClass()));
253 }
254 }
255 }
256 if (indirectionTables.isEmpty())
257 {
258 // nothing to do
259 return;
260 }
261
262 for (Iterator it = indirectionTables.keySet().iterator(); it.hasNext();)
263 {
264 String tableName = (String)it.next();
265 Set columns = (Set)indirectionTables.get(tableName);
266 String elementName = tableName;
267
268 for (Iterator classDescIt = model.getDescriptorTable().values().iterator(); classDescIt.hasNext();)
269 {
270 ClassDescriptor classDesc = (ClassDescriptor)classDescIt.next();
271
272 if (tableName.equals(classDesc.getFullTableName()))
273 {
274 elementName = getElementName(classDesc);
275
276 FieldDescriptor[] fieldDescs = classDesc.getFieldDescriptions();
277
278 if (fieldDescs != null)
279 {
280 for (int idx = 0; idx < fieldDescs.length; idx++)
281 {
282 columns.remove(fieldDescs[idx].getColumnName());
283 }
284 }
285 }
286 }
287
288 Table mappedTable = getTableFor(elementName);
289 Map columnsMap = getColumnsFor(elementName);
290 Map requiredAttributes = getRequiredAttributes(elementName);
291
292 if (mappedTable == null)
293 {
294 mappedTable = schema.findTable(elementName);
295 if (mappedTable == null)
296 {
297 continue;
298 }
299 columnsMap = new TreeMap();
300 requiredAttributes = new HashMap();
301 _elementToTable.put(elementName, mappedTable);
302 _elementToColumnMap.put(elementName, columnsMap);
303 _elementToRequiredAttributesMap.put(elementName, requiredAttributes);
304 }
305 for (Iterator columnIt = columns.iterator(); columnIt.hasNext();)
306 {
307 String columnName = (String)columnIt.next();
308 Column column = mappedTable.findColumn(columnName);
309
310 if (column != null)
311 {
312 columnsMap.put(columnName, column);
313 requiredAttributes.put(columnName, Boolean.TRUE);
314 }
315 }
316 }
317 }
318
319 /**
320 * Returns the element name for the class descriptor which is the adjusted short (unqualified) class
321 * name. Also takes care that the element name does not clash with another class of the same short
322 * name that maps to a different table though.
323 *
324 * @param classDesc The class descriptor
325 * @return The element name
326 */
327 private String getElementName(ClassDescriptor classDesc)
328 {
329 String elementName = classDesc.getClassNameOfObject().replace('$', '_');
330
331 elementName = elementName.substring(elementName.lastIndexOf('.') + 1);
332
333 Table table = getTableFor(elementName);
334 int suffix = 0;
335
336 while ((table != null) && !table.getName().equals(classDesc.getFullTableName()))
337 {
338 ++suffix;
339 table = getTableFor(elementName + "-" + suffix);
340 }
341 if (suffix > 0)
342 {
343 elementName += "-" + suffix;
344 }
345
346 return elementName;
347 }
348
349 /**
350 * Adjusts the local attribute name (the part after the last '::' for nested fields).
351 *
352 * @param attrName The original attribute name
353 * @return The local attribute name
354 */
355 private String getShortAttributeName(String attrName)
356 {
357 return attrName.substring(attrName.lastIndexOf(':') + 1);
358 }
359 }