Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » meta » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.    
   18    */
   19   package org.apache.openjpa.jdbc.meta;
   20   
   21   import java.io.Serializable;
   22   import java.sql.Types;
   23   import java.util.ArrayList;
   24   import java.util.Collections;
   25   import java.util.List;
   26   
   27   import org.apache.commons.lang.StringUtils;
   28   import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
   29   import org.apache.openjpa.jdbc.schema.Column;
   30   import org.apache.openjpa.jdbc.schema.ColumnIO;
   31   import org.apache.openjpa.jdbc.schema.ForeignKey;
   32   import org.apache.openjpa.jdbc.schema.Index;
   33   import org.apache.openjpa.jdbc.schema.PrimaryKey;
   34   import org.apache.openjpa.jdbc.schema.Schema;
   35   import org.apache.openjpa.jdbc.schema.SchemaGroup;
   36   import org.apache.openjpa.jdbc.schema.Schemas;
   37   import org.apache.openjpa.jdbc.schema.Table;
   38   import org.apache.openjpa.jdbc.schema.Unique;
   39   import org.apache.openjpa.jdbc.sql.DBDictionary;
   40   import org.apache.openjpa.lib.log.Log;
   41   import org.apache.openjpa.lib.util.Localizer;
   42   import org.apache.openjpa.lib.util.Localizer.Message;
   43   import org.apache.openjpa.meta.JavaTypes;
   44   import org.apache.openjpa.meta.MetaDataContext;
   45   import org.apache.openjpa.util.MetaDataException;
   46   import serp.util.Strings;
   47   
   48   /**
   49    * Base class storing raw mapping information; defines utility methods for
   50    * converting raw mapping information to full mapping to the schema.
   51    *
   52    * @author Abe White
   53    */
   54   public abstract class MappingInfo
   55       implements Serializable {
   56   
   57       public static final int JOIN_NONE = 0;
   58       public static final int JOIN_FORWARD = 1;
   59       public static final int JOIN_INVERSE = 2;
   60   
   61       private static final Object NULL = new Object();
   62   
   63       private static final Localizer _loc = Localizer.forPackage
   64           (MappingInfo.class);
   65   
   66       private String _strategy = null;
   67       private List _cols = null;
   68       private Index _idx = null;
   69       private Unique _unq = null;
   70       private ForeignKey _fk = null;
   71       private boolean _canIdx = true;
   72       private boolean _canUnq = true;
   73       private boolean _canFK = true;
   74       private int _join = JOIN_NONE;
   75       private ColumnIO _io = null;
   76   
   77       /**
   78        * Mapping strategy name.
   79        */
   80       public String getStrategy() {
   81           return _strategy;
   82       }
   83   
   84       /**
   85        * Mapping strategy name.
   86        */
   87       public void setStrategy(String strategy) {
   88           _strategy = strategy;
   89       }
   90   
   91       /**
   92        * Raw column data.
   93        */
   94       public List getColumns() {
   95           return (_cols == null) ? Collections.EMPTY_LIST : _cols;
   96       }
   97   
   98       /**
   99        * Raw column data.
  100        */
  101       public void setColumns(List cols) {
  102           _cols = cols;
  103       }
  104   
  105       /**
  106        * Raw index.
  107        */
  108       public Index getIndex() {
  109           return _idx;
  110       }
  111   
  112       /**
  113        * Raw index.
  114        */
  115       public void setIndex(Index idx) {
  116           _idx = idx;
  117       }
  118   
  119       /**
  120        * The user can mark columns as explicitly non-indexable.
  121        */
  122       public boolean canIndex() {
  123           return _canIdx;
  124       }
  125   
  126       /**
  127        * The user can mark columns as explicitly non-indexable.
  128        */
  129       public void setCanIndex(boolean indexable) {
  130           _canIdx = indexable;
  131       }
  132   
  133       /**
  134        * Raw foreign key information.
  135        */
  136       public ForeignKey getForeignKey() {
  137           return _fk;
  138       }
  139   
  140       /**
  141        * Raw foreign key information.
  142        */
  143       public void setForeignKey(ForeignKey fk) {
  144           _fk = fk;
  145           if (fk != null && _join == JOIN_NONE)
  146               _join = JOIN_FORWARD;
  147       }
  148   
  149       /**
  150        * The user can mark columns as explicitly not having a foreign key.
  151        */
  152       public boolean canForeignKey() {
  153           return _canFK;
  154       }
  155   
  156       /**
  157        * The user can mark columns as explicitly not having a foreign key.
  158        */
  159       public void setCanForeignKey(boolean fkable) {
  160           _canFK = fkable;
  161       }
  162   
  163       /**
  164        * Raw unique constraint information.
  165        */
  166       public Unique getUnique() {
  167           return _unq;
  168       }
  169   
  170       /**
  171        * Raw unique constraint information.
  172        */
  173       public void setUnique(Unique unq) {
  174           _unq = unq;
  175       }
  176   
  177       /**
  178        * The user can mark columns as explicitly not having a unique constraint.
  179        */
  180       public boolean canUnique() {
  181           return _canUnq;
  182       }
  183   
  184       /**
  185        * The user can mark columns as explicitly not having a unique constraint.
  186        */
  187       public void setCanUnique(boolean uniquable) {
  188           _canUnq = uniquable;
  189       }
  190   
  191       /**
  192        * I/O for the columns created by the last call to {@link #createColumns},
  193        * or for the foreign key created by the last call to
  194        * {@link #createForeignKey}. This is also expected to be set correctly
  195        * prior to calls to {@link #syncColumns} and {@link #syncForeignKey}.
  196        */
  197       public ColumnIO getColumnIO() {
  198           return _io;
  199       }
  200   
  201       /**
  202        * I/O for the columns created by the last call to {@link #createColumns},
  203        * or for the foreign key created by the last call to
  204        * {@link #createForeignKey}. This is also expected to be set correctly
  205        * prior to calls to {@link #syncColumns} and {@link #syncForeignKey}.
  206        */
  207       public void setColumnIO(ColumnIO io) {
  208           _io = io;
  209       }
  210   
  211       /**
  212        * Direction of the join that the columns of this mapping info form. This
  213        * is usually automatically set by {@link #createForeignKey}. This flag
  214        * is also expected to be set correctly prior to calls to
  215        * {@link #syncForeignKey} if the join is inversed.
  216        */
  217       public int getJoinDirection() {
  218           return _join;
  219       }
  220   
  221       /**
  222        * Direction of the join that the columns of this mapping info form. This
  223        * is usually automatically set by {@link #createForeignKey}. This flag
  224        * is also expected to be set correctly prior to calls to
  225        * {@link #syncForeignKey} if the join is inversed.
  226        */
  227       public void setJoinDirection(int join) {
  228           _join = join;
  229       }
  230   
  231       /**
  232        * Clear all mapping information.
  233        */
  234       public void clear() {
  235           clear(true);
  236       }
  237   
  238       /**
  239        * Clear mapping information.
  240        *
  241        * @param canFlags whether to clear information about whether we
  242        * can place indexed, foreign keys, etc on this mapping
  243        */
  244       protected void clear(boolean canFlags) {
  245           _strategy = null;
  246           _cols = null;
  247           _io = null;
  248           _idx = null;
  249           _unq = null;
  250           _fk = null;
  251           _join = JOIN_NONE;
  252           if (canFlags) {
  253               _canIdx = true;
  254               _canFK = true;
  255               _canUnq = true;
  256           }
  257       }
  258   
  259       /**
  260        * Copy missing info from the instance to this one.
  261        */
  262       public void copy(MappingInfo info) {
  263           if (_strategy == null)
  264               _strategy = info.getStrategy();
  265           if (_canIdx && _idx == null) {
  266               if (info.getIndex() != null)
  267                   _idx = info.getIndex();
  268               else
  269                   _canIdx = info.canIndex();
  270           }
  271           if (_canUnq && _unq == null) {
  272               if (info.getUnique() != null)
  273                   _unq = info.getUnique();
  274               else
  275                   _canUnq = info.canUnique();
  276           }
  277           if (_canFK && _fk == null) {
  278               if (info.getForeignKey() != null)
  279                   _fk = info.getForeignKey();
  280               else
  281                   _canFK = info.canForeignKey();
  282           }
  283   
  284           List cols = getColumns();
  285           List icols = info.getColumns();
  286           if (!icols.isEmpty() && (cols.isEmpty()
  287               || cols.size() == icols.size())) {
  288               if (cols.isEmpty())
  289                   cols = new ArrayList(icols.size());
  290               for (int i = 0; i < icols.size(); i++) {
  291                   if (cols.size() == i)
  292                       cols.add(new Column());
  293                   ((Column) cols.get(i)).copy((Column) icols.get(i));
  294               }
  295               setColumns(cols);
  296           }
  297       }
  298   
  299       /**
  300        * Return true if this info has columns, foreign key information, index
  301        * information, etc.
  302        */
  303       public boolean hasSchemaComponents() {
  304           return (_cols != null && !_cols.isEmpty())
  305               || _idx != null
  306               || _unq != null
  307               || _fk != null
  308               || !_canIdx
  309               || !_canFK
  310               || !_canUnq;
  311       }
  312   
  313       /**
  314        * Assert that the user did not supply any columns, index, unique
  315        * constraint, or foreign key for this mapping.
  316        */
  317       public void assertNoSchemaComponents(MetaDataContext context, boolean die) {
  318           if (_cols == null || _cols.isEmpty()) {
  319               assertNoIndex(context, die);
  320               assertNoUnique(context, die);
  321               assertNoForeignKey(context, die);
  322               return;
  323           }
  324   
  325           Message msg = _loc.get("unexpected-cols", context);
  326           if (die)
  327               throw new MetaDataException(msg);
  328           context.getRepository().getLog().warn(msg);
  329       }
  330   
  331       /**
  332        * Assert that this info has the given strategy or no strategy.
  333        */
  334       public void assertStrategy(MetaDataContext context, Object contextStrat,
  335           Object expected, boolean die) {
  336           if (contextStrat == expected)
  337               return;
  338   
  339           String strat;
  340           if (contextStrat == null) {
  341               if (_strategy == null)
  342                   return;
  343               if (_strategy.equals(expected.getClass().getName()))
  344                   return;
  345               if (expected instanceof Strategy
  346                   && _strategy.equals(((Strategy) expected).getAlias()))
  347                   return;
  348               strat = _strategy;
  349           } else if (contextStrat instanceof Strategy)
  350               strat = ((Strategy) contextStrat).getAlias();
  351           else
  352               strat = contextStrat.getClass().getName();
  353   
  354           Message msg = _loc.get("unexpected-strategy", context, expected,
  355               strat);
  356           if (die)
  357               throw new MetaDataException(msg);
  358           context.getRepository().getLog().warn(msg);
  359       }
  360   
  361       /**
  362        * Assert that the user did not try to place an index on this mapping.
  363        */
  364       public void assertNoIndex(MetaDataContext context, boolean die) {
  365           if (_idx == null)
  366               return;
  367   
  368           Message msg = _loc.get("unexpected-index", context);
  369           if (die)
  370               throw new MetaDataException(msg);
  371           context.getRepository().getLog().warn(msg);
  372       }
  373   
  374       /**
  375        * Assert that the user did not try to place a unique constraint on this
  376        * mapping.
  377        */
  378       public void assertNoUnique(MetaDataContext context, boolean die) {
  379           if (_unq == null)
  380               return;
  381   
  382           Message msg = _loc.get("unexpected-unique", context);
  383           if (die)
  384               throw new MetaDataException(msg);
  385           context.getRepository().getLog().warn(msg);
  386       }
  387   
  388       /**
  389        * Assert that the user did not try to place a foreign key on this mapping.
  390        */
  391       public void assertNoForeignKey(MetaDataContext context, boolean die) {
  392           if (_fk == null)
  393               return;
  394   
  395           Message msg = _loc.get("unexpected-fk", context);
  396           if (die)
  397               throw new MetaDataException(msg);
  398           context.getRepository().getLog().warn(msg);
  399       }
  400   
  401       /**
  402        * Assert that the user did not try to join.
  403        */
  404       public void assertNoJoin(MetaDataContext context, boolean die) {
  405           boolean join = false;
  406           if (_cols != null) {
  407               Column col;
  408               for (int i = 0; !join && i < _cols.size(); i++) {
  409                   col = (Column) _cols.get(i);
  410                   if (col.getTarget() != null)
  411                       join = true;
  412               }
  413           }
  414           if (!join)
  415               return;
  416   
  417           Message msg = _loc.get("unexpected-join", context);
  418           if (die)
  419               throw new MetaDataException(msg);
  420           context.getRepository().getLog().warn(msg);
  421       }
  422   
  423       /**
  424        * Find or generate a table for a mapping.
  425        *
  426        * @param context the mapping that uses the table
  427        * @param def default table name provider
  428        * @param schemaName default schema if known, or null
  429        * @param given given table name
  430        * @param adapt whether we can alter the schema or mappings
  431        */
  432       public Table createTable(MetaDataContext context, TableDefaults def,
  433           String schemaName, String given, boolean adapt) {
  434           MappingRepository repos = (MappingRepository) context.getRepository();
  435           if (given == null && (def == null || (!adapt
  436               && !repos.getMappingDefaults().defaultMissingInfo())))
  437               throw new MetaDataException(_loc.get("no-table", context));
  438   
  439           if (schemaName == null)
  440               schemaName = Schemas.getNewTableSchema((JDBCConfiguration)
  441                   repos.getConfiguration());
  442   
  443           // if no given and adapting or defaulting missing info, use template
  444           SchemaGroup group = repos.getSchemaGroup();
  445           Schema schema = null;
  446           if (given == null) {
  447               schema = group.getSchema(schemaName);
  448               if (schema == null)
  449                   schema = group.addSchema(schemaName);
  450               given = def.get(schema);
  451           }
  452   
  453           String fullName;
  454           int dotIdx = given.lastIndexOf('.');
  455           if (dotIdx == -1)
  456               fullName = (schemaName == null) ? given : schemaName + "." + given;
  457           else {
  458               fullName = given;
  459               schema = null;
  460               schemaName = given.substring(0, dotIdx);
  461               given = given.substring(dotIdx + 1);
  462           }
  463   
  464           // look for named table using full name and findTable, which allows
  465           // the dynamic schema factory to create the table if needed
  466           Table table = group.findTable(fullName);
  467           if (table != null)
  468               return table;
  469           if (!adapt)
  470               throw new MetaDataException(_loc.get("bad-table", given, context));
  471   
  472           // named table doesn't exist; create it
  473           if (schema == null) {
  474               schema = group.getSchema(schemaName);
  475               if (schema == null)
  476                   schema = group.addSchema(schemaName);
  477           }
  478           table = schema.getTable(given);
  479           if (table == null)
  480               table = schema.addTable(given);
  481           return table;
  482       }
  483   
  484       /**
  485        * Retrieve/create columns on the given table by merging the given
  486        * template information with any user-provided information.
  487        *
  488        * @param context the mapping we're retrieving columns for
  489        * @param prefix localized error message key prefix
  490        * @param tmplates template columns
  491        * @param table the table for the columns
  492        * @param adapt whether we can modify the existing mapping or schema
  493        */
  494       protected Column[] createColumns(MetaDataContext context, String prefix,
  495           Column[] tmplates, Table table, boolean adapt) {
  496           assertTable(context, table);
  497           if (prefix == null)
  498               prefix = "generic";
  499   
  500           // the user has to give the right number of expected columns for this
  501           // mapping, or none at all if we're adapting.  can't just given one of
  502           // n columns because we don't know which of the n columns the info
  503           // applies to
  504           List given = getColumns();
  505           boolean fill = ((MappingRepository) context.getRepository()).
  506               getMappingDefaults().defaultMissingInfo();
  507           if ((!given.isEmpty() || (!adapt && !fill))
  508               && given.size() != tmplates.length)
  509               throw new MetaDataException(_loc.get(prefix + "-num-cols",
  510                   context, String.valueOf(tmplates.length),
  511                   String.valueOf(given.size())));
  512   
  513           Column[] cols = new Column[tmplates.length];
  514           _io = null;
  515           Column col;
  516           for (int i = 0; i < tmplates.length; i++) {
  517               col = (given.isEmpty()) ? null : (Column) given.get(i);
  518               cols[i] = mergeColumn(context, prefix, tmplates[i], true, col,
  519                   table, adapt, fill);
  520               setIOFromColumnFlags(col, i);
  521           }
  522           return cols;
  523       }
  524   
  525       /**
  526        * Set the proper internal column I/O metadata for the given column's flags.
  527        */
  528       private void setIOFromColumnFlags(Column col, int i) {
  529           if (col == null || (!col.getFlag(Column.FLAG_UNINSERTABLE)
  530               && !col.getFlag(Column.FLAG_UNUPDATABLE)))
  531               return;
  532   
  533           if (_io == null)
  534               _io = new ColumnIO();
  535           _io.setInsertable(i, !col.getFlag(Column.FLAG_UNINSERTABLE));
  536           _io.setUpdatable(i, !col.getFlag(Column.FLAG_UNUPDATABLE));
  537       }
  538   
  539       /**
  540        * Assert that the given table is non-null.
  541        */
  542       private static void assertTable(MetaDataContext context, Table table) {
  543           if (table == null)
  544               throw new MetaDataException(_loc.get("unmapped", context));
  545       }
  546   
  547       /**
  548        * Merge the given columns if possible.
  549        *
  550        * @param context the mapping we're retrieving columns for
  551        * @param prefix localized error message key prefix
  552        * @param tmplate template for expected column information
  553        * @param compat whether the existing column type must be compatible
  554        * with the type of the template column
  555        * @param given the given column information from mapping info
  556        * @param table the table for the columns
  557        * @param adapt whether we can modify the existing mapping or schema
  558        * @param fill whether to default missing column information
  559        */
  560       protected static Column mergeColumn(MetaDataContext context, String prefix,
  561           Column tmplate, boolean compat, Column given, Table table,
  562           boolean adapt, boolean fill) {
  563           assertTable(context, table);
  564   
  565           // if not adapting must provide column name at a minimum
  566           String colName = (given == null) ? null : given.getName();
  567           if (colName == null && !adapt && !fill)
  568               throw new MetaDataException(_loc.get(prefix + "-no-col-name",
  569                   context));
  570   
  571           // determine the column name based on given info, or template if none;
  572           // also make sure that if the user gave a column name, he didn't try
  573           // to put the column in an unexpected table
  574           if (colName == null)
  575               colName = tmplate.getName();
  576           int dotIdx = colName.lastIndexOf('.');
  577           if (dotIdx == 0)
  578               colName = colName.substring(1);
  579           else if (dotIdx != -1) {
  580               findTable(context, colName.substring(0, dotIdx), table,
  581                   null, null);
  582               colName = colName.substring(dotIdx + 1);
  583           }
  584   
  585           // find existing column
  586           Column col = table.getColumn(colName);
  587           if (col == null && !adapt)
  588               throw new MetaDataException(_loc.get(prefix + "-bad-col-name",
  589                   context, colName, table));
  590   
  591           MappingRepository repos = (MappingRepository) context.getRepository();
  592           DBDictionary dict = repos.getDBDictionary();
  593   
  594           // use information from template column by default, allowing any
  595           // user-given specifics to override it
  596           int type = tmplate.getType();
  597           int size = tmplate.getSize();
  598           if (type == Types.OTHER)
  599               type = dict.getJDBCType(tmplate.getJavaType(), size == -1);
  600           boolean ttype = true;
  601           int otype = type;
  602           String typeName = tmplate.getTypeName();
  603           Boolean notNull = null;
  604           if (tmplate.isNotNullExplicit())
  605               notNull = (tmplate.isNotNull()) ? Boolean.TRUE : Boolean.FALSE;
  606           int decimals = tmplate.getDecimalDigits();
  607           String defStr = tmplate.getDefaultString();
  608           boolean autoAssign = tmplate.isAutoAssigned();
  609           boolean relationId = tmplate.isRelationId();
  610           String targetField = tmplate.getTargetField();
  611           if (given != null) {
  612               // use given type if provided, but warn if it isn't compatible with
  613               // the expected column type
  614               if (given.getType() != Types.OTHER) {
  615                   ttype = false;
  616                   if (compat && !given.isCompatible(type, typeName, size, 
  617                       decimals)) {
  618                       Log log = repos.getLog();
  619                       if (log.isWarnEnabled())
  620                           log.warn(_loc.get(prefix + "-incompat-col",
  621                               context, colName, Schemas.getJDBCName(type)));
  622                   }
  623                   otype = given.getType();
  624                   type = dict.getPreferredType(otype);
  625               }
  626               typeName = given.getTypeName();
  627               size = given.getSize();
  628               decimals = given.getDecimalDigits();
  629   
  630               // leave this info as the template defaults unless the user
  631               // explicitly turns it on in the given column
  632               if (given.isNotNullExplicit())
  633                   notNull = (given.isNotNull()) ? Boolean.TRUE : Boolean.FALSE;
  634               if (given.getDefaultString() != null)
  635                   defStr = given.getDefaultString();
  636               if (given.isAutoAssigned())
  637                   autoAssign = true;
  638               if (given.isRelationId())
  639                   relationId = true;
  640           }
  641   
  642           // default char column size if original type is char (test original
  643           // type rather than final type because orig might be clob, translated
  644           // to an unsized varchar, which is supported by some dbs)
  645           if (size == 0 && (otype == Types.VARCHAR || otype == Types.CHAR))
  646               size = dict.characterColumnSize;
  647   
  648           // create column, or make sure existing column matches expected type
  649           if (col == null) {
  650               col = table.addColumn(colName);
  651               col.setType(type);
  652           } else if ((compat || !ttype) && !col.isCompatible(type, typeName, 
  653               size, decimals)) {
  654               // if existing column isn't compatible with desired type, die if
  655               // can't adapt, else warn and change the existing column type
  656               Message msg = _loc.get(prefix + "-bad-col", context,
  657                   Schemas.getJDBCName(type), col.getDescription());
  658               if (!adapt)
  659                   throw new MetaDataException(msg);
  660               Log log = repos.getLog();
  661               if (log.isWarnEnabled())
  662                   log.warn(msg);
  663   
  664               col.setType(type);
  665           } else if (given != null && given.getType() != Types.OTHER) {
  666               // as long as types are compatible, set column to expected type
  667               col.setType(type);
  668           }
  669   
  670           // always set the java type and autoassign to expected values, even on
  671           // an existing column, since we don't get this from the DB
  672           if (compat)
  673               col.setJavaType(tmplate.getJavaType());
  674           else if (col.getJavaType() == JavaTypes.OBJECT) {
  675               if (given != null && given.getJavaType() != JavaTypes.OBJECT)
  676                   col.setJavaType(given.getJavaType());
  677               else
  678                   col.setJavaType(JavaTypes.getTypeCode
  679                       (Schemas.getJavaType(col.getType(), col.getSize(),
  680                           col.getDecimalDigits())));
  681           }
  682           col.setAutoAssigned(autoAssign);
  683           col.setRelationId(relationId);
  684           col.setTargetField(targetField);
  685   
  686           // we need this for runtime, and the dynamic schema factory might
  687           // not know it, so set it even if not adapting
  688           if (defStr != null)
  689               col.setDefaultString(defStr);
  690           if (notNull != null)
  691               col.setNotNull(notNull.booleanValue());
  692   
  693           // add other details if adapting
  694           if (adapt) {
  695               if (typeName != null)
  696                   col.setTypeName(typeName);
  697               if (size != 0)
  698                   col.setSize(size);
  699               if (decimals != 0)
  700                   col.setDecimalDigits(decimals);
  701           }
  702   
  703           if (tmplate.hasComment())
  704               col.setComment(tmplate.getComment());
  705           return col;
  706       }
  707   
  708       /**
  709        * Find the table named by a column or target.
  710        *
  711        * @param context context for error messages, etc.
  712        * @param name the table name, possibly including schema
  713        * @param expected the expected table; may be null
  714        * @param inverse the possible inverse table; may be null
  715        * @param rel if we're finding the target table of a join, the
  716        * joined-to type; allows us to also look in its superclass tables
  717        */
  718       private static Table findTable(MetaDataContext context, String name,
  719           Table expected, Table inverse, ClassMapping rel) {
  720           // is this the expected table?
  721           if (expected == null && rel != null)
  722               expected = rel.getTable();
  723           if (expected != null && isTableName(name, expected))
  724               return expected;
  725   
  726           // check for inverse
  727           if (inverse != null && isTableName(name, inverse))
  728               return inverse;
  729   
  730           // superclass table?
  731           if (rel != null)
  732               rel = rel.getJoinablePCSuperclassMapping();
  733           while (rel != null) {
  734               if (isTableName(name, rel.getTable()))
  735                   return rel.getTable();
  736               rel = rel.getJoinablePCSuperclassMapping();
  737           }
  738   
  739           // none of the possible tables
  740           throw new MetaDataException(_loc.get("col-wrong-table", context,
  741               expected, name));
  742       }
  743   
  744       /**
  745        * Return whether the given name matches the given table.
  746        */
  747       private static boolean isTableName(String name, Table table) {
  748           return name.equalsIgnoreCase(table.getName())
  749               || name.equalsIgnoreCase(table.getFullName());
  750       }
  751   
  752       /**
  753        * Retrieve/create an index on the given columns by merging the given
  754        * template information with any user-provided information.
  755        *
  756        * @param context the mapping we're retrieving an index for
  757        * @param prefix localized error message key prefix
  758        * @param tmplate template for expected index information
  759        * @param cols the indexed columns
  760        * @param adapt whether we can modify the existing mapping or schema
  761        */
  762       protected Index createIndex(MetaDataContext context, String prefix,
  763           Index tmplate, Column[] cols, boolean adapt) {
  764           if (prefix == null)
  765               prefix = "generic";
  766   
  767           // can't create an index if there are no cols
  768           if (cols == null || cols.length == 0) {
  769               if (_idx != null)
  770                   throw new MetaDataException(_loc.get(prefix
  771                       + "-no-index-cols", context));
  772               return null;
  773           }
  774   
  775           // look for an existing index on these columns
  776           Table table = cols[0].getTable();
  777           Index[] idxs = table.getIndexes();
  778           Index exist = null;
  779           for (int i = 0; i < idxs.length; i++) {
  780               if (idxs[i].columnsMatch(cols)) {
  781                   exist = idxs[i];
  782                   break;
  783               }
  784           }
  785   
  786           // remove existing index?
  787           if (!_canIdx) {
  788               if (exist == null)
  789                   return null;
  790               if (!adapt)
  791                   throw new MetaDataException(_loc.get(prefix + "-index-exists",
  792                       context));
  793               table.removeIndex(exist);
  794               return null;
  795           }
  796   
  797           // if we have an existing index, merge given info into it
  798           if (exist != null) {
  799               if (_idx != null && _idx.isUnique() && !exist.isUnique()) {
  800                   if (!adapt)
  801                       throw new MetaDataException(_loc.get(prefix
  802                           + "-index-not-unique", context));
  803                   exist.setUnique(true);
  804               }
  805               return exist;
  806           }
  807   
  808           // if no defaults return null
  809           MappingRepository repos = (MappingRepository) context.getRepository();
  810           boolean fill = repos.getMappingDefaults().defaultMissingInfo();
  811           if (_idx == null && (tmplate == null || (!adapt && !fill)))
  812               return null;
  813   
  814           String name = null;
  815           boolean unq;
  816           if (_idx != null) {
  817               name = _idx.getName();
  818               unq = _idx.isUnique();
  819               // preserve multiple columns if they are specified in the index
  820               if (_idx.getColumns() != null && _idx.getColumns().length > 1)
  821                   cols = _idx.getColumns();
  822           } else
  823               unq = tmplate.isUnique();
  824   
  825           // if no name provided by user info, make one
  826           if (name == null) {
  827               if (tmplate != null)
  828                   name = tmplate.getName();
  829               else {
  830                   name = cols[0].getName();
  831                   name = repos.getDBDictionary().getValidIndexName(name, table);
  832               }
  833           }
  834   
  835           Index idx = table.addIndex(name);
  836           idx.setUnique(unq);
  837           idx.setColumns(cols);
  838           return idx;
  839       }
  840   
  841       /**
  842        * Retrieve/create a unique constraint on the given columns by merging the
  843        * given template information with any user-provided information.
  844        *
  845        * @param context the mapping we're retrieving a constraint for
  846        * @param prefix localized error message key prefix
  847        * @param tmplate template for expected unique information
  848        * @param cols the constraint columns
  849        * @param adapt whether we can modify the existing mapping or schema
  850        */
  851       protected Unique createUnique(MetaDataContext context, String prefix,
  852           Unique tmplate, Column[] cols, boolean adapt) {
  853           if (prefix == null)
  854               prefix = "generic";
  855   
  856           // can't create a constraint if there are no cols
  857           if (cols == null || cols.length == 0) {
  858               if (_unq != null || tmplate != null)
  859                   throw new MetaDataException(_loc.get(prefix
  860                       + "-no-unique-cols", context));
  861               return null;
  862           }
  863   
  864           // look for an existing constraint on these columns
  865           Table table = cols[0].getTable();
  866           Unique[] unqs = table.getUniques();
  867           Unique exist = null;
  868           for (int i = 0; i < unqs.length; i++) {
  869               if (unqs[i].columnsMatch(cols)) {
  870                   exist = unqs[i];
  871                   break;
  872               }
  873           }
  874   
  875           // remove existing unique?
  876           if (!_canUnq) {
  877               if (exist == null)
  878                   return null;
  879               if (!adapt)
  880                   throw new MetaDataException(_loc.get(prefix
  881                       + "-unique-exists", context));
  882               table.removeUnique(exist);
  883               return null;
  884           }
  885   
  886           // no defaults; return existing constraint (if any)
  887           if (tmplate == null && _unq == null)
  888               return exist;
  889   
  890           MappingRepository repos = (MappingRepository) context.getRepository();
  891           if (exist != null) {
  892               if (_unq != null && _unq.isDeferred() && !exist.isDeferred()) {
  893                   Log log = repos.getLog();
  894                   if (log.isWarnEnabled())
  895                       log.warn(_loc.get(prefix + "-defer-unique", context));
  896               }
  897               return exist;
  898           }
  899   
  900           // dict can't handle unique constraints?
  901           DBDictionary dict = repos.getDBDictionary();
  902           if (_unq != null && !dict.supportsUniqueConstraints) {
  903               Log log = repos.getLog();
  904               if (log.isWarnEnabled())
  905                   log.warn(_loc.get(prefix + "-unique-support", context));
  906               return null;
  907           }
  908   
  909           boolean fill = repos.getMappingDefaults().defaultMissingInfo();
  910           if (!adapt && !fill && _unq == null)
  911               return null;
  912   
  913           String name;
  914           boolean deferred;
  915           if (_unq != null) {
  916               name = _unq.getName();
  917               deferred = _unq.isDeferred();
  918           } else {
  919               name = tmplate.getName();
  920               deferred = tmplate.isDeferred();
  921           }
  922   
  923           if (deferred && !dict.supportsDeferredConstraints) {
  924               Log log = repos.getLog();
  925               if (log.isWarnEnabled())
  926                   log.warn(_loc.get(prefix + "-create-defer-unique",
  927                       context, dict.platform));
  928               deferred = false;
  929           }
  930   
  931           Unique unq = table.addUnique(name);
  932           unq.setDeferred(deferred);
  933           unq.setColumns(cols);
  934           return unq;
  935       }
  936   
  937       /**
  938        * Retrieve/create a foreign key (possibly logical) on the given columns
  939        * by merging the given template information with any user-provided
  940        * information.
  941        *
  942        * @param context the mapping we're retrieving a key for
  943        * @param prefix localized error message key prefix
  944        * @param given the columns given by the user
  945        * @param def defaults provider
  946        * @param table the table for the key
  947        * @param cls type we're joining from
  948        * @param rel target type we're joining to
  949        * @param inversable whether the foreign key can be inversed
  950        * @param adapt whether we can modify the existing mapping or schema
  951        */
  952       protected ForeignKey createForeignKey(MetaDataContext context,
  953           String prefix, List given, ForeignKeyDefaults def, Table table,
  954           ClassMapping cls, ClassMapping rel, boolean inversable, boolean adapt) {
  955           assertTable(context, table);
  956           if (prefix == null)
  957               prefix = "generic";
  958   
  959           // collect the foreign key columns and their targets
  960           Object[][] joins = createJoins(context, prefix, table, cls, rel,
  961               given, def, inversable, adapt);
  962           _join = JOIN_FORWARD;
  963   
  964           // establish local table using any join between two columns; if we only
  965           // find constant joins, then keep default local table (directionless)
  966           Table local = table;
  967           Table foreign = rel.getTable();
  968           Table tmp;
  969           boolean constant = false;
  970           boolean localSet = false;
  971           for (int i = 0; i < joins.length; i++) {
  972               if (joins[i][1]instanceof Column) {
  973                   tmp = ((Column) joins[i][0]).getTable();
  974                   if (!localSet) {
  975                       local = tmp;
  976                       localSet = true;
  977                   } else if (tmp != local)
  978                       throw new MetaDataException(_loc.get(prefix
  979                           + "-mult-fk-tables", context, local, tmp));
  980                   foreign = ((Column) joins[i][1]).getTable();
  981   
  982                   if (joins[i][2] == Boolean.TRUE)
  983                       _join = JOIN_INVERSE;
  984               } else
  985                   constant = true;
  986           }
  987   
  988           // if this is not a constant join, look for existing foreign key
  989           // on local columns
  990           ForeignKey exist = null;
  991           if (!constant && local.getForeignKeys().length > 0) {
  992               Column[] cols = new Column[joins.length];
  993               Column[] pks = new Column[joins.length];
  994               for (int i = 0; i < joins.length; i++) {
  995                   cols[i] = (Column) joins[i][0];
  996                   pks[i] = (Column) joins[i][1];
  997               }
  998   
  999               ForeignKey[] fks = local.getForeignKeys();
 1000               for (int i = 0; i < fks.length; i++) {
 1001                   if (fks[i].getConstantColumns().length == 0
 1002                       && fks[i].getConstantPrimaryKeyColumns().length == 0
 1003                       && fks[i].columnsMatch(cols, pks)) {
 1004                       exist = fks[i];
 1005                       break;
 1006                   }
 1007               }
 1008           }
 1009   
 1010           MappingRepository repos = (MappingRepository) context.getRepository();
 1011           DBDictionary dict = repos.getDBDictionary();
 1012           if (exist != null) {
 1013               // make existing key logical?
 1014               if (!_canFK) {
 1015                   if (exist.getDeleteAction() != exist.ACTION_NONE && !adapt)
 1016                       throw new MetaDataException(_loc.get(prefix
 1017                           + "-fk-exists", context));
 1018                   exist.setDeleteAction(exist.ACTION_NONE);
 1019               }
 1020   
 1021               if (_fk != null && _fk.isDeferred() && !exist.isDeferred()) {
 1022                   Log log = repos.getLog();
 1023                   if (log.isWarnEnabled())
 1024                       log.warn(_loc.get(prefix + "-defer-fk", context));
 1025               }
 1026   
 1027               // allow user-given info to override existing key if we're adapting;
 1028               // template info cannot override existing key
 1029               if (adapt && _fk != null) {
 1030                   if (_fk.getUpdateAction() != ForeignKey.ACTION_NONE)
 1031                       exist.setUpdateAction(_fk.getUpdateAction());
 1032                   if (_fk.getDeleteAction() != ForeignKey.ACTION_NONE)
 1033                       exist.setDeleteAction(_fk.getDeleteAction());
 1034               }
 1035               setIOFromJoins(exist, joins);
 1036               return exist;
 1037           }
 1038   
 1039           String name = null;
 1040           int delAction = ForeignKey.ACTION_NONE;
 1041           int upAction = ForeignKey.ACTION_NONE;
 1042           boolean deferred = false;
 1043           boolean fill = repos.getMappingDefaults().defaultMissingInfo();
 1044           ForeignKey tmplate = (def == null) ? null
 1045               : def.get(local, foreign, _join == JOIN_INVERSE);
 1046           if (_fk != null && (tmplate == null || (!adapt && !fill))) {
 1047               // if not adapting or no template info use given data
 1048               name = _fk.getName();
 1049               delAction = _fk.getDeleteAction();
 1050               upAction = _fk.getUpdateAction();
 1051               deferred = _fk.isDeferred();
 1052           } else if (_canFK && (adapt || fill)) {
 1053               if (_fk == null && tmplate != null) {
 1054                   // no user given info; use template data
 1055                   name = tmplate.getName();
 1056                   delAction = tmplate.getDeleteAction();
 1057                   upAction = tmplate.getUpdateAction();
 1058                   deferred = tmplate.isDeferred();
 1059               } else if (_fk != null && tmplate != null) {
 1060                   // merge user and template data, always letting user info win
 1061                   name = _fk.getName();
 1062                   if (name == null && tmplate.getName() != null)
 1063                       name = tmplate.getName();
 1064                   delAction = _fk.getDeleteAction();
 1065                   if (delAction == ForeignKey.ACTION_NONE)
 1066                       delAction = tmplate.getDeleteAction();
 1067                   upAction = _fk.getUpdateAction();
 1068                   if (upAction == ForeignKey.ACTION_NONE)
 1069                       upAction = tmplate.getUpdateAction();
 1070                   deferred = _fk.isDeferred();
 1071               }
 1072           }
 1073   
 1074           if (!dict.supportsDeleteAction(delAction)
 1075               || !dict.supportsUpdateAction(upAction)) {
 1076               Log log = repos.getLog();
 1077               if (log.isWarnEnabled())
 1078                   log.warn(_loc.get(prefix + "-unsupported-fk-action", context));
 1079               delAction = ForeignKey.ACTION_NONE;
 1080               upAction = ForeignKey.ACTION_NONE;
 1081           }
 1082           if (deferred && !dict.supportsDeferredConstraints) {
 1083               Log log = repos.getLog();
 1084               if (log.isWarnEnabled())
 1085                   log.warn(_loc.get(prefix + "-create-defer-fk",
 1086                       context, dict.platform));
 1087               deferred = false;
 1088           }
 1089   
 1090           // create foreign key with merged info
 1091           ForeignKey fk = local.addForeignKey(name);
 1092           fk.setDeleteAction(delAction);
 1093           fk.setUpdateAction(upAction);
 1094           fk.setDeferred(deferred);
 1095   
 1096           // add joins to key
 1097           Column col;
 1098           for (int i = 0; i < joins.length; i++) {
 1099               col = (Column) joins[i][0];
 1100               if (joins[i][1]instanceof Column)
 1101                   fk.join(col, (Column) joins[i][1]);
 1102               else if ((joins[i][2] == Boolean.TRUE) != (_join == JOIN_INVERSE))
 1103                   fk.joinConstant(joins[i][1], col);
 1104               else
 1105                   fk.joinConstant(col, joins[i][1]);
 1106           }
 1107           setIOFromJoins(fk, joins);
 1108           return fk;
 1109       }
 1110   
 1111       /**
 1112        * Use the join information to populate our internal column I/O data.
 1113        */
 1114       private void setIOFromJoins(ForeignKey fk, Object[][] joins) {
 1115           List cols = getColumns();
 1116           _io = null;
 1117           if (cols.isEmpty())
 1118               return;
 1119   
 1120           int constIdx = 0;
 1121           int idx;
 1122           for (int i = 0; i < joins.length; i++) {
 1123               // const columns are indexed after std join columns in fk IO
 1124               if (joins[i][1]instanceof Column)
 1125                   idx = i - constIdx;
 1126               else if ((joins[i][2] == Boolean.TRUE) == (_join == JOIN_INVERSE))
 1127                   idx = fk.getColumns().length + constIdx++;
 1128               else
 1129                   continue;
 1130               setIOFromColumnFlags((Column) cols.get(i), idx);
 1131           }
 1132       }
 1133   
 1134       /**
 1135        * Create or retrieve the foreign key joins.
 1136        *
 1137        * @param context the mapping we're retrieving a key for
 1138        * @param prefix localized error message key prefix
 1139        * @param table the table for the key
 1140        * @param cls type we're joining from, if applicable
 1141        * @param rel target type we're joining to
 1142        * @param given the columns given by the user
 1143        * @param def foreign key defaults provider
 1144        * @param inversable whether the foreign key can be inversed
 1145        * @param adapt whether we can modify the existing mapping or schema
 1146        * @return array of tuples where the first element is the
 1147        * local column (or in the case of a constant join the
 1148        * sole column), the second is the target column (or
 1149        * constant), and the third is {@link Boolean#TRUE} if
 1150        * this is an inverse join
 1151        */
 1152       private Object[][] createJoins(MetaDataContext context,
 1153           String prefix, Table table, ClassMapping cls, ClassMapping rel,
 1154           List given, ForeignKeyDefaults def, boolean inversable, boolean adapt) {
 1155           MappingRepository repos = (MappingRepository) context.getRepository();
 1156           boolean fill = repos.getMappingDefaults().defaultMissingInfo();
 1157           Object[][] joins;
 1158   
 1159           // if no columns given, just create mirrors of target columns
 1160           if (given.isEmpty()) {
 1161               if (!adapt && !fill)
 1162                   throw new MetaDataException(_loc.get(prefix + "-no-fk-cols",
 1163                       context));
 1164   
 1165               Column[] targets = rel.getPrimaryKeyColumns();
 1166               joins = new Object[targets.length][3];
 1167               Column tmplate;
 1168               for (int i = 0; i < targets.length; i++) {
 1169                   tmplate = new Column();
 1170                   tmplate.setName(targets[i].getName());
 1171                   tmplate.setJavaType(targets[i].getJavaType());
 1172                   tmplate.setType(targets[i].getType());
 1173                   tmplate.setTypeName(targets[i].getTypeName());
 1174                   tmplate.setSize(targets[i].getSize());
 1175                   tmplate.setDecimalDigits(targets[i].getDecimalDigits());
 1176   
 1177                   if (def != null)
 1178                       def.populate(table, rel.getTable(), tmplate, targets[i],
 1179                           false, i, targets.length);
 1180                   joins[i][0] = mergeColumn(context, prefix, tmplate, true,
 1181                       null, table, adapt, fill);
 1182                   joins[i][1] = targets[i];
 1183               }
 1184               return joins;
 1185           }
 1186   
 1187           // use given columns to create join.  we don't try to use any of the
 1188           // template columns, even if the user doesn't give a column linking to
 1189           // every primary key of the target type -- users are allowed to create
 1190           // partial joins.  this means, though, that if a user wants to specify
 1191           // info for one join column, he has to at least create elements for
 1192           // all of them
 1193   
 1194           joins = new Object[given.size()][3];
 1195           Column col;
 1196           for (int i = 0; i < joins.length; i++) {
 1197               col = (Column) given.get(i);
 1198               mergeJoinColumn(context, prefix, col, joins, i, table, cls, rel,
 1199                   def, inversable && !col.getFlag(Column.FLAG_PK_JOIN), adapt,
 1200                   fill);
 1201           }
 1202           return joins;
 1203       }
 1204   
 1205       /**
 1206        * Create or retrieve a foreign key column for a join.
 1207        *
 1208        * @param context the mapping we're retrieving a key for
 1209        * @param prefix localized error message key prefix
 1210        * @param given the given local foreign key column
 1211        * @param joins array of joins
 1212        * @param idx index of the join array to populate
 1213        * @param table the table for the key
 1214        * @param cls the type we're joining from
 1215        * @param rel target type we're joining to
 1216        * @param def foreign key defaults provider;
 1217        * use null to mirror target column names
 1218        * @param inversable whether the foreign key can be inversed
 1219        * @param adapt whether we can modify the existing mapping or schema
 1220        * @param fill whether to default missing column information
 1221        */
 1222       private void mergeJoinColumn(MetaDataContext context, String prefix,
 1223           Column given, Object[][] joins, int idx, Table table, ClassMapping cls,
 1224           ClassMapping rel, ForeignKeyDefaults def, boolean inversable,
 1225           boolean adapt, boolean fill) {
 1226           // default to the primary key column name if this is a pk join
 1227           String name = given.getName();
 1228           if (name == null && given != null
 1229               && given.getFlag(Column.FLAG_PK_JOIN) && cls != null) {
 1230               Column[] pks = cls.getPrimaryKeyColumns();
 1231               if (pks.length == 1)
 1232                   name = pks[0].getName();
 1233           }
 1234   
 1235           // if we can't adapt, then the user must at least give a column name
 1236           if (name == null && !adapt && !fill)
 1237               throw new MetaDataException(_loc.get(prefix + "-no-fkcol-name",
 1238                   context));
 1239   
 1240           // check to see if the column isn't in the expected table; it might
 1241           // be an inverse join or a join to a base class of the target type
 1242           Table local = table;
 1243           Table foreign = rel.getTable();
 1244           boolean fullName = false;
 1245           boolean inverse = false;
 1246           if (name != null) {
 1247               int dotIdx = name.lastIndexOf('.');
 1248               if (dotIdx != -1) {
 1249                   // allow use of '.' without prefix to mean "use expected
 1250                   // foreign table"
 1251                   if (dotIdx == 0)
 1252                       local = foreign;
 1253                   else
 1254                       local = findTable(context, name.substring(0, dotIdx),
 1255                           local, foreign, null);
 1256                   fullName = true;
 1257                   name = name.substring(dotIdx + 1);
 1258   
 1259                   // if inverse join, then swap local and foreign tables
 1260                   if (local != table) {
 1261                       foreign = table;
 1262                       inverse = true;
 1263                   }
 1264               }
 1265           }
 1266           boolean forceInverse = !fullName && _join == JOIN_INVERSE;
 1267           if (forceInverse) {
 1268               local = foreign;
 1269               foreign = table;
 1270               inverse = true;
 1271           }
 1272   
 1273           // determine target
 1274           String targetName = given.getTarget();
 1275           Object target = null;
 1276           Table ttable = null;
 1277           boolean constant = false;
 1278           boolean fullTarget = false;
 1279           if (targetName == null && given.getTargetField() != null) {
 1280               ClassMapping tcls = (inverse) ? cls : rel;
 1281               String fieldName = given.getTargetField();
 1282               int dotIdx = fieldName.lastIndexOf('.');
 1283               fullTarget = dotIdx != -1;
 1284   
 1285               if (dotIdx == 0) {
 1286                   // allow use of '.' without prefix to mean "use expected local
 1287                   // cls"; but if we already inversed no need to switch again
 1288                   if (!inverse)
 1289                       tcls = cls;
 1290                   fieldName = fieldName.substring(1);
 1291               } else if (dotIdx > 0) {
 1292                   // must be class + field name
 1293                   tcls = findClassMapping(context, fieldName.substring
 1294                       (0, dotIdx), cls, rel);
 1295                   fieldName = fieldName.substring(dotIdx + 1);
 1296               }
 1297               if (tcls == null)
 1298                   throw new MetaDataException(_loc.get(prefix
 1299                       + "-bad-fktargetcls", context, fieldName, name));
 1300   
 1301               FieldMapping field = tcls.getFieldMapping(fieldName);
 1302               if (field == null)
 1303                   throw new MetaDataException(_loc.get(prefix
 1304                       + "-bad-fktargetfield", new Object[]{ context, fieldName,
 1305                       name, tcls }));
 1306               if (field.getColumns().length != 1)
 1307                   throw new MetaDataException(_loc.get(prefix
 1308                       + "-fktargetfield-cols", context, fieldName, name));
 1309               ttable = (field.getJoinForeignKey() != null) ? field.getTable()
 1310                   : field.getDefiningMapping().getTable();
 1311               targetName = field.getColumns()[0].getName();
 1312           } else if (targetName != null) {
 1313               if (targetName.charAt(0) == '\'') {
 1314                   constant = true;
 1315                   target = targetName.substring(1, targetName.length() - 1);
 1316               } else if (targetName.charAt(0) == '-'
 1317                   || targetName.charAt(0) == '.'
 1318                   || Character.isDigit(targetName.charAt(0))) {
 1319                   constant = true;
 1320                   try {
 1321                       if (targetName.indexOf('.') == -1)
 1322                           target = new Integer(targetName);
 1323                       else
 1324                           target = new Double(targetName);
 1325                   } catch (RuntimeException re) {
 1326                       throw new MetaDataException(_loc.get(prefix
 1327                           + "-bad-fkconst", context, targetName, name));
 1328                   }
 1329               } else if ("null".equalsIgnoreCase(targetName))
 1330                   constant = true;
 1331               else {
 1332                   int dotIdx = targetName.lastIndexOf('.');
 1333                   fullTarget = dotIdx != -1;
 1334                   if (dotIdx == 0) {
 1335                       // allow use of '.' without prefix to mean "use expected
 1336                       // local table", but ignore if we're already inversed
 1337                       if (!inverse)
 1338                           ttable = local;
 1339                       targetName = targetName.substring(1);
 1340                   } else if (dotIdx != -1) {
 1341                       ttable = findTable(context, targetName.substring(0,
 1342                           dotIdx), foreign, local, (inverse) ? cls : rel);
 1343                       targetName = targetName.substring(dotIdx + 1);
 1344                   }
 1345               }
 1346           }
 1347   
 1348           // use explicit target table if available
 1349           if (ttable == local && local != foreign) {
 1350               // swap, unless user gave incompatible table in column name
 1351               if (fullName)
 1352                   throw new MetaDataException(_loc.get(prefix
 1353                       + "-bad-fktarget-inverse", new Object[]{ context, name,
 1354                       foreign, ttable }));
 1355               local = foreign;
 1356               foreign = ttable;
 1357           } else if (ttable != null) {
 1358               // ttable might be a table of a base class of the target
 1359               foreign = ttable;
 1360           }
 1361   
 1362           // check to see if we inversed; if this is a same-table join, then
 1363           // consider it an implicit inverse if the user includes the table name
 1364           // in the column name, but not in the column target, or if the user
 1365           // gives no column name but a full target name
 1366           inverse = inverse || local != table || (local == foreign
 1367               && ((fullName && !fullTarget) || (name == null && fullTarget)));
 1368           if (!inversable && !constant && inverse) {
 1369               if (local == foreign)
 1370                   throw new MetaDataException(_loc.get(prefix
 1371                       + "-bad-fk-self-inverse", context, local));
 1372               throw new MetaDataException(_loc.get(prefix + "-bad-fk-inverse",
 1373                   context, local, table));
 1374           }
 1375           if (name == null && constant)
 1376               throw new MetaDataException(_loc.get(prefix
 1377                   + "-no-fkcol-name-adapt", context));
 1378   
 1379           if (name == null && targetName == null) {
 1380               // if no name or target is provided and there's more than one likely
 1381               // join possibility, too ambiguous
 1382               PrimaryKey pk = foreign.getPrimaryKey();
 1383               if (joins.length != 1 || pk == null || pk.getColumns().length != 1)
 1384                   throw new MetaDataException(_loc.get(prefix
 1385                       + "-no-fkcol-name-adapt", context));
 1386   
 1387               // assume target is pk column
 1388               targetName = pk.getColumns()[0].getName();
 1389           } else if (name != null && targetName == null) {
 1390               // if one primary key column use it for target; if multiple joins
 1391               // look for a foreign column with same name as local column
 1392               PrimaryKey pk = foreign.getPrimaryKey();
 1393               if (joins.length == 1 && pk != null && pk.getColumns().length == 1)
 1394                   targetName = pk.getColumns()[0].getName();
 1395               else if (foreign.getColumn(name) != null)
 1396                   targetName = name;
 1397               else
 1398                   throw new MetaDataException(_loc.get(prefix
 1399                       + "-no-fkcol-target-adapt", context, name));
 1400           }
 1401   
 1402           // find the target column, and create template for local column based
 1403           // on it
 1404           Column tmplate = new Column();
 1405           tmplate.setName(name);
 1406           if (!constant) {
 1407               Column tcol = foreign.getColumn(targetName);
 1408               if (tcol == null)
 1409                   throw new MetaDataException(_loc.get(prefix + "-bad-fktarget",
 1410                       new Object[]{ context, targetName, name, foreign }));
 1411   
 1412               if (name == null)
 1413                   tmplate.setName(tcol.getName());
 1414               tmplate.setJavaType(tcol.getJavaType());
 1415               tmplate.setType(tcol.getType());
 1416               tmplate.setTypeName(tcol.getTypeName());
 1417               tmplate.setSize(tcol.getSize());
 1418               tmplate.setDecimalDigits(tcol.getDecimalDigits());
 1419               target = tcol;
 1420           } else if (target instanceof String)
 1421               tmplate.setJavaType(JavaTypes.STRING);
 1422           else if (target instanceof Integer)
 1423               tmplate.setJavaType(JavaTypes.INT);
 1424           else if (target instanceof Double)
 1425               tmplate.setJavaType(JavaTypes.DOUBLE);
 1426   
 1427           // populate template, but let user-given name override default name
 1428           if (def != null)
 1429               def.populate(local, foreign, tmplate, target, inverse, idx,
 1430                   joins.length);
 1431           if (name != null)
 1432               tmplate.setName(name);
 1433   
 1434           // create or merge local column
 1435           Column col = mergeColumn(context, prefix, tmplate, true, given, local,
 1436               adapt, fill);
 1437   
 1438           joins[idx][0] = col;
 1439           joins[idx][1] = target;
 1440           if (inverse)
 1441               joins[idx][2] = Boolean.TRUE;
 1442       }
 1443   
 1444       /**
 1445        * Find the target class mapping given the user's class name.
 1446        *
 1447        * @param context for error messages
 1448        * @param clsName class name given by user
 1449        * @param cls original source mapping
 1450        * @param rel original target mapping
 1451        */
 1452       private static ClassMapping findClassMapping(MetaDataContext context,
 1453           String clsName, ClassMapping cls, ClassMapping rel) {
 1454           if (isClassMappingName(clsName, cls))
 1455               return cls;
 1456           if (isClassMappingName(clsName, rel))
 1457               return rel;
 1458           throw new MetaDataException(_loc.get("target-wrong-cls", new Object[]
 1459               { context, clsName, cls, rel }));
 1460       }
 1461   
 1462       /**
 1463        * Return whether the given name matches the given mapping.
 1464        */
 1465       private static boolean isClassMappingName(String name, ClassMapping cls) {
 1466           if (cls == null)
 1467               return false;
 1468           if (name.equals(cls.getDescribedType().getName())
 1469               || name.equals(Strings.getClassName(cls.getDescribedType())))
 1470               return true;
 1471           return isClassMappingName(name, cls.getPCSuperclassMapping());
 1472       }
 1473   
 1474       /**
 1475        * Sets internal column information to match the given mapped columns.
 1476        *
 1477        * @param forceJDBCType whether to force the jdbc-type of the columns
 1478        * to be set, even when it matches the default for the columns' java type
 1479        */
 1480       protected void syncColumns(MetaDataContext context, Column[] cols,
 1481           boolean forceJDBCType) {
 1482           if (cols == null || cols.length == 0)
 1483               _cols = null;
 1484           else {
 1485               _cols = new ArrayList(cols.length);
 1486               Column col;
 1487               for (int i = 0; i < cols.length; i++) {
 1488                   col = syncColumn(context, cols[i], cols.length,
 1489                       forceJDBCType, cols[i].getTable(), null, null, false);
 1490                   setColumnFlagsFromIO(col, i);
 1491                   _cols.add(col);
 1492               }
 1493           }
 1494       }
 1495   
 1496       /**
 1497        * Set I/O flags on the column.
 1498        */
 1499       private void setColumnFlagsFromIO(Column col, int i) {
 1500           if (_io == null)
 1501               return;
 1502           col.setFlag(Column.FLAG_UNUPDATABLE, !_io.isUpdatable(i, false));
 1503           col.setFlag(Column.FLAG_UNINSERTABLE, !_io.isInsertable(i, false));
 1504       }
 1505   
 1506       /**
 1507        * Sets internal index information to match given mapped index.
 1508        */
 1509       protected void syncIndex(MetaDataContext context, Index idx) {
 1510           if (idx == null) {
 1511               _idx = null;
 1512               return;
 1513           }
 1514   
 1515           _canIdx = true;
 1516           _idx = new Index();
 1517           _idx.setName(idx.getName());
 1518           _idx.setUnique(idx.isUnique());
 1519           if (idx.getColumns() != null && idx.getColumns().length > 1)
 1520               _idx.setColumns(idx.getColumns());
 1521       }
 1522   
 1523       /**
 1524        * Sets internal constraint information to match given mapped constraint.
 1525        */
 1526       protected void syncUnique(MetaDataContext context, Unique unq) {
 1527           if (unq == null) {
 1528               _unq = null;
 1529               return;
 1530           }
 1531   
 1532           _canUnq = true;
 1533           _unq = new Unique();
 1534           _unq.setName(unq.getName());
 1535           _unq.setDeferred(unq.isDeferred());
 1536       }
 1537   
 1538       /**
 1539        * Sets internal constraint and column information to match given mapped
 1540        * constraint.
 1541        *
 1542        * @param local default local table
 1543        * @param target default target table
 1544        */
 1545       protected void syncForeignKey(MetaDataContext context, ForeignKey fk,
 1546           Table local, Table target) {
 1547           if (fk == null) {
 1548               _fk = null;
 1549               _cols = null;
 1550               _join = JOIN_NONE;
 1551               return;
 1552           }
 1553           if (_join == JOIN_NONE)
 1554               _join = JOIN_FORWARD;
 1555   
 1556           if (fk.isLogical())
 1557               _fk = null;
 1558           else {
 1559               _canFK = true;
 1560               _fk = new ForeignKey();
 1561               _fk.setName(fk.getName());
 1562               _fk.setDeleteAction(fk.getDeleteAction());
 1563               _fk.setUpdateAction(fk.getUpdateAction());
 1564               _fk.setDeferred(fk.isDeferred());
 1565           }
 1566   
 1567           Column[] cols = fk.getColumns();
 1568           Column[] pkCols = fk.getPrimaryKeyColumns();
 1569           Column[] ccols = fk.getConstantColumns();
 1570           Object[] cs = fk.getConstants();
 1571           Column[] cpkCols = fk.getConstantPrimaryKeyColumns();
 1572           Object[] cpks = fk.getPrimaryKeyConstants();
 1573   
 1574           int size = cols.length + ccols.length + cpkCols.length;
 1575           _cols = new ArrayList(size);
 1576           Column col;
 1577           for (int i = 0; i < cols.length; i++) {
 1578               col = syncColumn(context, cols[i], size, false, local,
 1579                   target, pkCols[i], _join == JOIN_INVERSE);
 1580               setColumnFlagsFromIO(col, i);
 1581               _cols.add(col);
 1582           }
 1583           Object constant;
 1584           for (int i = 0; i < ccols.length; i++) {
 1585               constant = (cs[i] == null) ? NULL : cs[i];
 1586               col = syncColumn(context, ccols[i], size, false, local,
 1587                   target, constant, _join == JOIN_INVERSE);
 1588               setColumnFlagsFromIO(col, cols.length + i);
 1589               _cols.add(col);
 1590           }
 1591           for (int i = 0; i < cpkCols.length; i++) {
 1592               constant = (cpks[i] == null) ? NULL : cpks[i];
 1593               _cols.add(syncColumn(context, cpkCols[i], size, false, target,
 1594                   local, constant, _join != JOIN_INVERSE));
 1595           }
 1596       }
 1597   
 1598       /**
 1599        * Create a copy of the given column with the raw mapping information
 1600        * set correctly, and without settings that match defaults.
 1601        *
 1602        * @param num the number of columns for this mapping
 1603        * @param forceJDBCType whether the jdbc-type of the created column
 1604        * should be set, even if it matches the default
 1605        * for the given column's java type
 1606        * @param colTable expected table for the column
 1607        * @param targetTable expected target table for join column
 1608        * @param target target column or object for join column; for a
 1609        * constant null target, use {@link #NULL}
 1610        * @param inverse whether join column is for inverse join
 1611        */
 1612       protected static Column syncColumn(MetaDataContext context, Column col,
 1613           int num, boolean forceJDBCType, Table colTable, Table targetTable,
 1614           Object target, boolean inverse) {
 1615           // use full name for cols that aren't in the expected table, or that
 1616           // are inverse joins
 1617           DBDictionary dict = ((MappingRepository) context.getRepository()).
 1618               getDBDictionary();
 1619           Column copy = new Column();
 1620           if (col.getTable() != colTable || inverse)
 1621               copy.setName(dict.getFullName(col.getTable(), true)
 1622                   + "." + col.getName());
 1623           else
 1624               copy.setName(col.getName());
 1625   
 1626           // set target if not default
 1627           if (target != null) {
 1628               if (target == NULL)
 1629                   copy.setTarget("null");
 1630               else if (target instanceof Column) {
 1631                   Column tcol = (Column) target;
 1632                   if ((!inverse && tcol.getTable() != targetTable)
 1633                       || (inverse && tcol.getTable() != colTable))
 1634                       copy.setTarget(dict.getFullName(tcol.getTable(), true)
 1635                           + "." + tcol.getName());
 1636                   else if (!defaultTarget(col, tcol, num))
 1637                       copy.setTarget(tcol.getName());
 1638               } else if (target instanceof Number)
 1639                   copy.setTarget(target.toString());
 1640               else
 1641                   copy.setTarget("'" + target + "'");
 1642           } else if (num > 1)
 1643               copy.setTargetField(col.getTargetField());
 1644   
 1645           if (col.getSize() != 0 && col.getSize() != dict.characterColumnSize
 1646               && (col.getSize() != -1 || !col.isLob()))
 1647               copy.setSize(col.getSize());
 1648           if (col.getDecimalDigits() != 0)
 1649               copy.setDecimalDigits(col.getDecimalDigits());
 1650           if (col.getDefaultString() != null)
 1651               copy.setDefaultString(col.getDefaultString());
 1652           if (col.isNotNull() && !col.isPrimaryKey()
 1653               && (!isPrimitive(col.getJavaType()) || isForeignKey(col)))
 1654               copy.setNotNull(true);
 1655   
 1656           // set type name if not default
 1657           String typeName = col.getTypeName();
 1658           if (typeName != null || copy.getSize() != 0
 1659               || copy.getDecimalDigits() != 0) {
 1660               // is this the dict default type? have to ensure jdbc-type set
 1661               // prior to finding dict default
 1662               copy.setType(col.getType());
 1663               String defName = dict.getTypeName(copy);
 1664               copy.setType(Types.OTHER);
 1665   
 1666               // copy should not have size info set if it isn't used in type name
 1667               boolean defSized = defName.indexOf('(') != -1;
 1668               if (!defSized) {
 1669                   if (copy.getSize() > 0)
 1670                       copy.setSize(0);
 1671                   copy.setDecimalDigits(0);
 1672               }
 1673   
 1674               if (typeName != null) {
 1675                   // make sure to strip size for comparison
 1676                   if (typeName.indexOf('(') == -1 && defSized)
 1677                       defName = defName.substring(0, defName.indexOf('('));
 1678                   if (!typeName.equalsIgnoreCase(defName))
 1679                       copy.setTypeName(typeName);
 1680               }
 1681           }
 1682   
 1683           // set jdbc-type if not default or if forced
 1684           if (forceJDBCType
 1685               || (target != null && !(target instanceof Column)
 1686               && col.getType() != Types.VARCHAR)
 1687               || dict.getJDBCType(col.getJavaType(), false) != col.getType())
 1688               copy.setType(col.getType());
 1689   
 1690           return copy;
 1691       }
 1692   
 1693       /** 
 1694        * Return whether the given column belongs to a foreign key.
 1695        */ 
 1696       private static boolean isForeignKey(Column col) 
 1697       {       
 1698           if (col.getTable() == null)
 1699               return false;
 1700           ForeignKey[] fks = col.getTable().getForeignKeys();
 1701           for (int i = 0; i < fks.length; i++) 
 1702               if (fks[i].containsColumn(col) 
 1703                   || fks[i].containsConstantColumn(col))
 1704                   return true;
 1705           return false;
 1706       }
 1707   
 1708       /**
 1709        * Return true if the given type code represents a primitive.
 1710        */
 1711       private static boolean isPrimitive(int type) {
 1712           switch (type) {
 1713               case JavaTypes.BOOLEAN:
 1714               case JavaTypes.BYTE:
 1715               case JavaTypes.CHAR:
 1716               case JavaTypes.DOUBLE:
 1717               case JavaTypes.FLOAT:
 1718               case JavaTypes.INT:
 1719               case JavaTypes.LONG:
 1720               case JavaTypes.SHORT:
 1721                   return true;
 1722               default:
 1723                   return false;
 1724           }
 1725       }
 1726   
 1727       /**
 1728        * Return true if the given target column matches the default.
 1729        * If there is only one column involved in the join and it links to the
 1730        * single target table pk column, or if the column name is the same as
 1731        * the target column name, then the target is the default.
 1732        */
 1733       private static boolean defaultTarget(Column col, Column targetCol,
 1734           int num) {
 1735           if (col.getName().equals(targetCol.getName()))
 1736               return true;
 1737           if (num > 1)
 1738               return false;
 1739   
 1740           PrimaryKey pk = targetCol.getTable().getPrimaryKey();
 1741           if (pk == null || pk.getColumns().length != 1)
 1742               return false;
 1743           return targetCol == pk.getColumns()[0];
 1744       }
 1745   
 1746       /**
 1747        * Supplies default table information.
 1748        */
 1749       public static interface TableDefaults {
 1750   
 1751           /**
 1752            * Return the default table name.
 1753            */
 1754           public String get(Schema schema);
 1755       }
 1756   
 1757       /**
 1758        * Supplies default foreign key information.
 1759        */
 1760       public static interface ForeignKeyDefaults {
 1761   
 1762           /**
 1763            * Return a default foreign key for the given tables, or null to
 1764            * create a logical foreign key only. Do not fill in the columns of
 1765            * the foreign key, only attributes like its name, delete action, etc.
 1766            * Do not add the foreign key to the table.
 1767            */
 1768           public ForeignKey get(Table local, Table foreign, boolean inverse);
 1769   
 1770           /**
 1771            * Populate the given foreign key column with defaults.
 1772            *
 1773            * @param target the target column or constant value
 1774            * @param pos the index of this column in the foreign key
 1775            * @param cols the number of columns in the foreign key
 1776            */
 1777           public void populate(Table local, Table foreign, Column col,
 1778               Object target, boolean inverse, int pos, int cols);
 1779   	}
 1780   }

Save This Page
Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » meta » [javadoc | source]