package org.apache.ojb.broker.ant;
/* Copyright 2004-2005 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.beanutils.DynaBean;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.DescriptorRepository;
import org.apache.ojb.broker.metadata.FieldDescriptor;
/**
* Provides a model derived from {@link org.apache.ojb.broker.metadata.DescriptorRepository} that
* is preprocessed for data handling (inserting data, generating data dtd).
*
* @author Thomas Dudziak
*/
public class PreparedModel
{
/** The database model. */
private Database _schema;
/** Maps dtd elements to tables */
private TreeMap _elementToTable = new TreeMap();
/** Maps dtd elements to lists of class descriptors (which all map to the same table) */
private HashMap _elementToClassDescriptors = new HashMap();
/** Maps dtd elements to colum maps which in turn map attribute names to columns */
private HashMap _elementToColumnMap = new HashMap();
/** Maps dtd elements to maps that specify which attributes are required */
private HashMap _elementToRequiredAttributesMap = new HashMap();
public PreparedModel(DescriptorRepository model, Database schema)
{
_schema = schema;
prepareModel(model);
}
public Iterator getElementNames()
{
return _elementToTable.keySet().iterator();
}
public Iterator getAttributeNames(String elementName)
{
Map columns = getColumnsFor(elementName);
return columns == null ? null : columns.keySet().iterator();
}
public Map getRequiredAttributes(String elementName)
{
return (Map)_elementToRequiredAttributesMap.get(elementName);
}
public boolean isRequired(String elementName, String attributeName)
{
Map requiredAttributes = getRequiredAttributes(elementName);
if (requiredAttributes == null)
{
return false;
}
else
{
Boolean status = (Boolean)requiredAttributes.get(attributeName);
return status == null ? false : status.booleanValue();
}
}
public Table getTableFor(String elementName)
{
return (Table)_elementToTable.get(elementName);
}
/**
* Creates a dyna bean for the table associated to the given element.
*
* @param elementName The element name
* @return The dyna bean
*/
public DynaBean createBeanFor(String elementName)
{
return _schema.createDynaBeanFor(getTableFor(elementName));
}
public List getClassDescriptorsMappingTo(String elementName)
{
return (List)_elementToClassDescriptors.get(elementName);
}
public Map getColumnsFor(String elementName)
{
return (Map)_elementToColumnMap.get(elementName);
}
public Column getColumnFor(String elementName, String attrName)
{
Map columns = getColumnsFor(elementName);
if (columns == null)
{
return null;
}
else
{
return (Column)columns.get(attrName);
}
}
/**
* Prepares a representation of the model that is easier accessible for our purposes.
*
* @param model The original model
* @return The model representation
*/
private void prepareModel(DescriptorRepository model)
{
TreeMap result = new TreeMap();
for (Iterator it = model.getDescriptorTable().values().iterator(); it.hasNext();)
{
ClassDescriptor classDesc = (ClassDescriptor)it.next();
if (classDesc.getFullTableName() == null)
{
// not mapped to a database table
continue;
}
String elementName = getElementName(classDesc);
Table mappedTable = getTableFor(elementName);
Map columnsMap = getColumnsFor(elementName);
Map requiredAttributes = getRequiredAttributes(elementName);
List classDescs = getClassDescriptorsMappingTo(elementName);
if (mappedTable == null)
{
mappedTable = _schema.findTable(classDesc.getFullTableName());
if (mappedTable == null)
{
continue;
}
columnsMap = new TreeMap();
requiredAttributes = new HashMap();
classDescs = new ArrayList();
_elementToTable.put(elementName, mappedTable);
_elementToClassDescriptors.put(elementName, classDescs);
_elementToColumnMap.put(elementName, columnsMap);
_elementToRequiredAttributesMap.put(elementName, requiredAttributes);
}
classDescs.add(classDesc);
extractAttributes(classDesc, mappedTable, columnsMap, requiredAttributes);
}
extractIndirectionTables(model, _schema);
}
private void extractAttributes(ClassDescriptor classDesc, Table mappedTable, Map columnsMap, Map requiredColumnsMap)
{
FieldDescriptor[] fieldDescs = classDesc.getFieldDescriptions();
if (fieldDescs != null)
{
for (int idx = 0; idx < fieldDescs.length; idx++)
{
Column column = mappedTable.findColumn(fieldDescs[idx].getColumnName());
if (column != null)
{
// we'll check whether another field (of not necessarily the same name)
// already maps to this column; if this is the case, we're ignoring
// this field
boolean alreadyMapped = false;
for (Iterator mappedColumnsIt = columnsMap.values().iterator(); mappedColumnsIt.hasNext();)
{
if (column.equals(mappedColumnsIt.next()))
{
alreadyMapped = true;
break;
}
}
if (!alreadyMapped)
{
String shortAttrName = getShortAttributeName(fieldDescs[idx].getAttributeName());
columnsMap.put(shortAttrName, column);
requiredColumnsMap.put(shortAttrName,
fieldDescs[idx].isPrimaryKey() ? Boolean.TRUE : Boolean.FALSE);
}
}
}
}
}
/**
* Extracts indirection tables from the given class descriptor, and adds elements
* for them. In contrast to normal elements, for indirection tables the element name
* matches the table name, and the attribute names match the column names.
*
* @param model The model
* @param elements The elements
*/
private void extractIndirectionTables(DescriptorRepository model, Database schema)
{
HashMap indirectionTables = new HashMap();
// first we gather all participants for each m:n relationship
for (Iterator classDescIt = model.getDescriptorTable().values().iterator(); classDescIt.hasNext();)
{
ClassDescriptor classDesc = (ClassDescriptor)classDescIt.next();
for (Iterator collDescIt = classDesc.getCollectionDescriptors().iterator(); collDescIt.hasNext();)
{
CollectionDescriptor collDesc = (CollectionDescriptor)collDescIt.next();
String indirTable = collDesc.getIndirectionTable();
if ((indirTable != null) && (indirTable.length() > 0))
{
Set columns = (Set)indirectionTables.get(indirTable);
if (columns == null)
{
columns = new HashSet();
indirectionTables.put(indirTable, columns);
}
columns.addAll(Arrays.asList(collDesc.getFksToThisClass()));
columns.addAll(Arrays.asList(collDesc.getFksToItemClass()));
}
}
}
if (indirectionTables.isEmpty())
{
// nothing to do
return;
}
for (Iterator it = indirectionTables.keySet().iterator(); it.hasNext();)
{
String tableName = (String)it.next();
Set columns = (Set)indirectionTables.get(tableName);
String elementName = tableName;
for (Iterator classDescIt = model.getDescriptorTable().values().iterator(); classDescIt.hasNext();)
{
ClassDescriptor classDesc = (ClassDescriptor)classDescIt.next();
if (tableName.equals(classDesc.getFullTableName()))
{
elementName = getElementName(classDesc);
FieldDescriptor[] fieldDescs = classDesc.getFieldDescriptions();
if (fieldDescs != null)
{
for (int idx = 0; idx < fieldDescs.length; idx++)
{
columns.remove(fieldDescs[idx].getColumnName());
}
}
}
}
Table mappedTable = getTableFor(elementName);
Map columnsMap = getColumnsFor(elementName);
Map requiredAttributes = getRequiredAttributes(elementName);
if (mappedTable == null)
{
mappedTable = schema.findTable(elementName);
if (mappedTable == null)
{
continue;
}
columnsMap = new TreeMap();
requiredAttributes = new HashMap();
_elementToTable.put(elementName, mappedTable);
_elementToColumnMap.put(elementName, columnsMap);
_elementToRequiredAttributesMap.put(elementName, requiredAttributes);
}
for (Iterator columnIt = columns.iterator(); columnIt.hasNext();)
{
String columnName = (String)columnIt.next();
Column column = mappedTable.findColumn(columnName);
if (column != null)
{
columnsMap.put(columnName, column);
requiredAttributes.put(columnName, Boolean.TRUE);
}
}
}
}
/**
* Returns the element name for the class descriptor which is the adjusted short (unqualified) class
* name. Also takes care that the element name does not clash with another class of the same short
* name that maps to a different table though.
*
* @param classDesc The class descriptor
* @return The element name
*/
private String getElementName(ClassDescriptor classDesc)
{
String elementName = classDesc.getClassNameOfObject().replace('$', '_');
elementName = elementName.substring(elementName.lastIndexOf('.') + 1);
Table table = getTableFor(elementName);
int suffix = 0;
while ((table != null) && !table.getName().equals(classDesc.getFullTableName()))
{
++suffix;
table = getTableFor(elementName + "-" + suffix);
}
if (suffix > 0)
{
elementName += "-" + suffix;
}
return elementName;
}
/**
* Adjusts the local attribute name (the part after the last '::' for nested fields).
*
* @param attrName The original attribute name
* @return The local attribute name
*/
private String getShortAttributeName(String attrName)
{
return attrName.substring(attrName.lastIndexOf(':') + 1);
}
}