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    }