1 package org.apache.torque.map;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.StringTokenizer;
33
34 import org.apache.commons.lang.StringUtils;
35 import org.apache.torque.Database;
36 import org.apache.torque.Torque;
37 import org.apache.torque.TorqueException;
38 import org.apache.torque.adapter.IDMethod;
39
40 /**
41 * TableMap is used to model a table in a database.
42 *
43 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
44 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
45 * @author <a href="mailto:greg.monroe@dukece.com">Greg Monroe</a>
46 * @version $Id: TableMap.java 1375888 2012-08-22 03:51:00Z tfischer $
47 */
48 public class TableMap implements Serializable
49 {
50 /**
51 * Serial version.
52 */
53 private static final long serialVersionUID = 1L;
54
55 /** The columns in the table. XML Order is preserved. */
56 private final Map<String, ColumnMap> columns
57 = Collections.synchronizedMap(
58 new LinkedHashMap<String, ColumnMap>());
59
60 /** The foreign keys in the table. XML Order is preserved. */
61 private final List<ForeignKeyMap> foreignKeys = new ArrayList<ForeignKeyMap>();
62
63 /** The database this table belongs to. */
64 private DatabaseMap dbMap;
65
66 /** The name of the table. */
67 private String tableName;
68
69 /**
70 * The name of the schema to which this table belongs,
71 * or null for the default schema.
72 */
73 private String schemaName;
74
75 /** The JavaName of the table as defined in XML */
76 private String javaName;
77
78 /** The prefix on the table name. */
79 private String prefix;
80
81 /** The primary key generation method. */
82 private IDMethod primaryKeyMethod = IDMethod.NO_ID_METHOD;
83
84 /** The table description info. */
85 private String description = "";
86
87 /** The Peer Class for this table. */
88 private Class<?> peerClass;
89
90 /** The OM Root Class for this table. */
91 private Class<?> omClass;
92
93 /** Whether any column uses Inheritance. */
94 private boolean useInheritance = false;
95
96 /** Whether cache managers are used. */
97 private boolean useManager = false;
98
99 /** The associated cache manager class. */
100 private Class<?> managerClass;
101
102 /** Overrides the information stored in the pkInfoMap for all id methods. */
103 private Object pkInfoOverride;
104
105 /**
106 * Stores information that is needed for generating primary keys.
107 * The information is keyed by the idMethodType because it might be
108 * different for different id methods.
109 */
110 private final Map<IDMethod, Object> pkInfoMap
111 = new HashMap<IDMethod, Object>();
112
113 /** Associated options. */
114 private final Map<String, String> optionsMap
115 = Collections.synchronizedMap(new LinkedHashMap<String, String>());
116
117 /**
118 * Constructor.
119 *
120 * @param tableName The name of the table, may be prefixed with a
121 * schema name, not null.
122 * @param containingDB A DatabaseMap that this table belongs to.
123 */
124 public TableMap(String tableName, DatabaseMap containingDB)
125 {
126 setTableName(tableName);
127 dbMap = containingDB;
128 }
129
130 /**
131 * Constructor.
132 *
133 * @param tableName The name of the table, may be prefixed with a
134 * schema name, not null.
135 * @param prefix The prefix for the table name (ie: SCARAB for
136 * SCARAB_PROJECT).
137 * @param containingDB A DatabaseMap that this table belongs to.
138 */
139 public TableMap(String tableName,
140 String prefix,
141 DatabaseMap containingDB)
142 {
143 setTableName(tableName);
144 this.prefix = prefix;
145 dbMap = containingDB;
146 }
147
148 private void setTableName(String tableName)
149 {
150 if (tableName == null)
151 {
152 throw new NullPointerException("tableName must not be null");
153 }
154 int dotIndex = tableName.indexOf(".");
155 if (dotIndex != -1)
156 {
157 this.schemaName = tableName.substring(0, dotIndex);
158 this.tableName = tableName.substring(dotIndex + 1);
159 }
160 else
161 {
162 this.tableName = tableName;
163 }
164 }
165
166 /**
167 * Sets the database map this table belongs to.
168 * @param databaseMap
169 */
170 void setDatabaseMap(DatabaseMap databaseMap)
171 {
172 dbMap = databaseMap;
173 }
174
175 /**
176 * Does this table contain the specified column?
177 *
178 * @param column A ColumnMap.
179 * @return True if the table contains the column.
180 */
181 public boolean containsColumn(ColumnMap column)
182 {
183 return containsColumn(column.getColumnName());
184 }
185
186 /**
187 * Does this table contain the specified column?
188 *
189 * @param name A String with the name of the column.
190 * @return True if the table contains the column.
191 */
192 public boolean containsColumn(String name)
193 {
194 if (name.indexOf('.') > 0)
195 {
196 name = name.substring(name.indexOf('.') + 1);
197 }
198 return columns.containsKey(name);
199 }
200
201 /**
202 * Get the DatabaseMap containing this TableMap.
203 *
204 * @return A DatabaseMap.
205 */
206 public DatabaseMap getDatabaseMap()
207 {
208 return dbMap;
209 }
210
211 /**
212 * Returns true if this tableMap contains a column with object
213 * data. If the type of the column is not a string, a number or a
214 * date, it is assumed that it is object data.
215 *
216 * @return True if map contains a column with object data.
217 */
218 public boolean containsObjectColumn()
219 {
220 synchronized (columns)
221 {
222 Iterator<ColumnMap> it = columns.values().iterator();
223 while (it.hasNext())
224 {
225 Object theType = it.next().getType();
226 if (!(theType instanceof String || theType instanceof Number
227 || theType instanceof java.util.Date))
228 {
229 return true;
230 }
231 }
232 }
233 return false;
234 }
235
236 /**
237 * Get the name of the Table, not prefixed by a possible schema name
238 *
239 * @return A String with the name of the table, not null.
240 */
241 public String getName()
242 {
243 return tableName;
244 }
245
246 /**
247 * Get the schema to which the table belongs to.
248 *
249 * @return the schema name, or null if the default schema should be used.
250 */
251 public String getSchemaName()
252 {
253 return schemaName;
254 }
255
256 /**
257 * Get the Java name of the table as defined in XML.
258 *
259 * @return A String with the Java name of the table.
260 */
261 public String getJavaName()
262 {
263 return javaName;
264 }
265
266 /**
267 * Set the Java name of the table as defined by generator/XML.
268 *
269 * @param value A String with the Java name of the table.
270 */
271 public void setJavaName(String value)
272 {
273 this.javaName = value;
274 }
275
276 /**
277 * Get table prefix name.
278 *
279 * @return A String with the prefix.
280 */
281 public String getPrefix()
282 {
283 return this.prefix;
284 }
285
286 /**
287 * Set table prefix name.
288 *
289 * @param prefix The prefix for the table name (ie: SCARAB for
290 * SCARAB_PROJECT).
291 */
292 public void setPrefix(String prefix)
293 {
294 this.prefix = prefix;
295 }
296
297 /**
298 * Get the method used to generate primary keys for this table.
299 *
300 * @return A String with the method.
301 */
302 public IDMethod getPrimaryKeyMethod()
303 {
304 return primaryKeyMethod;
305 }
306
307 /**
308 * Get the information used to generate a primary key
309 *
310 * @return An Object.
311 */
312 public Object getPrimaryKeyMethodInfo(IDMethod idMethod)
313 {
314 if (pkInfoOverride != null)
315 {
316 return pkInfoOverride;
317 }
318 return pkInfoMap.get(idMethod);
319 }
320
321 /**
322 * Get a ColumnMap[] of the columns in this table.
323 *
324 * @return A ColumnMap[].
325 */
326 public ColumnMap[] getColumns()
327 {
328 ColumnMap[] tableColumns = new ColumnMap[columns.size()];
329 synchronized (columns)
330 {
331 Iterator<ColumnMap> it = columns.values().iterator();
332 int i = 0;
333 while (it.hasNext())
334 {
335 tableColumns[i++] = it.next();
336 }
337 }
338 return tableColumns;
339 }
340
341 /**
342 * Get all foreign keys in the table..
343 *
344 * @return All foreign keys, not null.
345 */
346 public List<ForeignKeyMap> getForeignKeys()
347 {
348 return Collections.unmodifiableList(foreignKeys);
349 }
350
351 /**
352 * Get a ColumnMap for the named table.
353 *
354 * @param name A String with the name of the table.
355 * @return A ColumnMap.
356 */
357 public ColumnMap getColumn(String name)
358 {
359 try
360 {
361 return columns.get(name);
362 }
363 catch (Exception e)
364 {
365 return null;
366 }
367 }
368
369 /**
370 * Add a pre-created column to this table. It will replace any
371 * existing column.
372 *
373 * @param cmap A ColumnMap.
374 */
375 public void addColumn(ColumnMap cmap)
376 {
377 columns.put(cmap.getColumnName(), cmap);
378 }
379
380 /**
381 * Add a foreign key to this table.
382 *
383 * @param foreignKey the foreign key map, not null
384 */
385 public void addForeignKey(ForeignKeyMap foreignKey)
386 {
387 foreignKeys.add(foreignKey);
388 }
389
390 /**
391 * Sets the method used to generate a key for this table. Valid
392 * values are as specified in the {@link
393 * org.apache.torque.adapter.IDMethod} interface.
394 *
395 * @param method The ID generation method type, not null.
396 */
397 public void setPrimaryKeyMethod(IDMethod method)
398 {
399 if (method == null)
400 {
401 throw new NullPointerException("method must not be null");
402 }
403 primaryKeyMethod = method;
404 if (IDMethod.ID_BROKER == method)
405 {
406 Database database = Torque.getOrCreateDatabase(
407 getDatabaseMap().getName());
408 database.createAndRegisterIdBroker();
409 }
410 }
411
412 /**
413 * Sets the pk information needed to generate a key.
414 * This overrides all information set by
415 * <code>setPrimaryKeyMethodInfo(String, Object)</code>.
416 *
417 * @param pkInfo information needed to generate a key
418 */
419 public void setPrimaryKeyMethodInfo(Object pkInfo)
420 {
421 pkInfoOverride = pkInfo;
422 }
423
424 /**
425 * Sets the pk information needed to generate a key.
426 *
427 * @param idMethod the id method for which this information is stored.
428 * @param pkInfo information needed to generate a key.
429 */
430 public void setPrimaryKeyMethodInfo(IDMethod idMethod, Object pkInfo)
431 {
432 pkInfoMap.put(idMethod, pkInfo);
433 }
434
435 //---Utility methods for doing intelligent lookup of table names
436
437 /**
438 * Tell me if i have PREFIX in my string.
439 *
440 * @param data A String.
441 * @return True if prefix is contained in data.
442 */
443 private boolean hasPrefix(String data)
444 {
445 return (data.indexOf(getPrefix()) != -1);
446 }
447
448 /**
449 * Removes the PREFIX.
450 *
451 * @param data A String.
452 * @return A String with data, but with prefix removed.
453 */
454 private String removePrefix(String data)
455 {
456 return data.substring(getPrefix().length());
457 }
458
459 /**
460 * Removes the PREFIX, removes the underscores and makes
461 * first letter caps.
462 *
463 * SCARAB_FOO_BAR becomes FooBar.
464 *
465 * @param data A String.
466 * @return A String with data processed.
467 */
468 public final String removeUnderScores(String data)
469 {
470 String tmp = null;
471 StringBuffer out = new StringBuffer();
472 if (hasPrefix(data))
473 {
474 tmp = removePrefix(data);
475 }
476 else
477 {
478 tmp = data;
479 }
480
481 StringTokenizer st = new StringTokenizer(tmp, "_");
482 while (st.hasMoreTokens())
483 {
484 String element = ((String) st.nextElement()).toLowerCase();
485 out.append(StringUtils.capitalize(element));
486 }
487 return out.toString();
488 }
489
490 /**
491 * Returns the table description info.
492 *
493 * @return Returns the description.
494 */
495 public String getDescription()
496 {
497 return description;
498 }
499
500 /**
501 * Sets the table description.
502 *
503 * @param description The description to set.
504 */
505 public void setDescription(String description)
506 {
507 this.description = description;
508 }
509
510 /**
511 * Returns the OM class for this table.
512 *
513 * @return the OM class.
514 */
515 public Class<?> getOMClass()
516 {
517 return omClass;
518 }
519
520 /**
521 * Sets the OM root class for this table.
522 *
523 * @param omClass The OM root class for this table.
524 */
525 public void setOMClass(Class<?> omClass)
526 {
527 this.omClass = omClass;
528 }
529
530 /**
531 * Returns the Peer Class for this table.
532 *
533 * @return The peerClass for this table.
534 */
535 public Class<?> getPeerClass()
536 {
537 return peerClass;
538 }
539
540 /**
541 * Sets the Peer class for this table.
542 *
543 * @param peerClass The peerClass to set.
544 */
545 public void setPeerClass(Class<?> peerClass)
546 {
547 this.peerClass = peerClass;
548 }
549
550 /**
551 * Returns the database map for this table.
552 *
553 * @return the database map for this table.
554 */
555 public DatabaseMap getDbMap()
556 {
557 return dbMap;
558 }
559
560 /**
561 * Returns whether this table uses inheritance.
562 *
563 * @return whether inheritance is used.
564 */
565 public boolean isUseInheritance()
566 {
567 return useInheritance;
568 }
569
570 /**
571 * Sets whether this table uses inheritance.
572 *
573 * @param useInheritance whether this table uses inheritance.
574 */
575 public void setUseInheritance(boolean useInheritance)
576 {
577 this.useInheritance = useInheritance;
578 }
579
580 /**
581 * Returns whether managers are used for this table.
582 *
583 * @return whether managers are used for this table.
584 */
585 public boolean isUseManager()
586 {
587 return useManager;
588 }
589
590 /**
591 * Sets whether managers are used for this table.
592 *
593 * @param useManager whether managers are used for this table.
594 */
595 public void setUseManager(boolean useManager)
596 {
597 this.useManager = useManager;
598 }
599
600 /**
601 * Returns the manager class for this table.
602 *
603 * @return the managerClass.
604 */
605 public Class<?> getManagerClass()
606 {
607 return managerClass;
608 }
609
610 /**
611 * Sets the manager class for this table.
612 *
613 * @param managerClass the manager class for this table.
614 */
615 public void setManagerClass(Class<?> managerClass)
616 {
617 this.managerClass = managerClass;
618 }
619
620 /**
621 * Returns an unmodifiable map of all options.
622 *
623 * @return A map containing all options, not null.
624 */
625 public Map<String, String> getOptions()
626 {
627 return Collections.unmodifiableMap(optionsMap);
628 }
629
630 /**
631 * Sets an option.
632 *
633 * @param key the key of the option
634 * @param value the value of the option.
635 */
636 public void setOption(String key, String value)
637 {
638 optionsMap.put(key, value);
639 }
640
641 /**
642 * Returns the value of an option.
643 *
644 * @param key the key of the option.
645 *
646 * @return the value of the option, or null if not set.
647 */
648 public String getOption(String key)
649 {
650 return optionsMap.get(key);
651 }
652
653 /**
654 * Returns the single primary key of this table, if it exists
655 *
656 * @return the single primary key column.
657 *
658 * @throws TorqueException If the table has no primary key
659 * or if the table has multiple primary keys.
660 */
661 public ColumnMap getPrimaryKey()
662 throws TorqueException
663 {
664 Set<ColumnMap> result = new HashSet<ColumnMap>();
665
666 for (ColumnMap column : columns.values())
667 {
668 if (column.isPrimaryKey())
669 {
670 result.add(column);
671 }
672 }
673 if (result.isEmpty())
674 {
675 throw new TorqueException("getPrimaryKey(): Table " + tableName
676 + "has no primary key.");
677 }
678 if (result.size() > 1)
679 {
680 throw new TorqueException("getPrimaryKey(): Table " + tableName
681 + "has more than one primary key.");
682 }
683 return result.iterator().next();
684 }
685
686 @Override
687 public String toString()
688 {
689 return "TableMap[" + tableName + "]";
690 }
691 }