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.sql.SQLException;
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import org.apache.openjpa.jdbc.schema.Column;
26 import org.apache.openjpa.jdbc.schema.ColumnIO;
27 import org.apache.openjpa.jdbc.schema.ForeignKey;
28 import org.apache.openjpa.jdbc.schema.Index;
29 import org.apache.openjpa.jdbc.schema.Schemas;
30 import org.apache.openjpa.jdbc.schema.Table;
31 import org.apache.openjpa.jdbc.schema.Unique;
32 import org.apache.openjpa.jdbc.sql.Row;
33 import org.apache.openjpa.jdbc.sql.Select;
34 import org.apache.openjpa.kernel.OpenJPAStateManager;
35 import org.apache.openjpa.lib.util.Localizer;
36 import org.apache.openjpa.meta.JavaTypes;
37 import org.apache.openjpa.meta.ValueMetaDataImpl;
38 import org.apache.openjpa.util.InternalException;
39 import org.apache.openjpa.util.MetaDataException;
40
41 /**
42 * Standalone {@link ValueMapping} implementation.
43 *
44 * @author Abe White
45 * @since 0.4.0
46 */
47 public class ValueMappingImpl
48 extends ValueMetaDataImpl
49 implements ValueMapping {
50
51 private static final Localizer _loc = Localizer.forPackage
52 (ValueMappingImpl.class);
53
54 private ValueMappingInfo _info;
55 private ValueHandler _handler = null;
56 private ClassMapping[] _typeArr = null;
57
58 private Column[] _cols = Schemas.EMPTY_COLUMNS;
59 private ColumnIO _io = null;
60 private ForeignKey _fk = null;
61 private Map _targetFKs = null;
62 private Index _idx = null;
63 private Unique _unq = null;
64 private int _join = JOIN_FORWARD;
65 private boolean _criteria = false;
66 private int _poly = POLY_TRUE;
67
68 /**
69 * Constructor. Supply owning mapping.
70 */
71 public ValueMappingImpl(FieldMapping owner) {
72 super(owner);
73 _info = owner.getMappingRepository().newMappingInfo(this);
74 _info.setUseClassCriteria(owner.getMappingRepository().
75 getMappingDefaults().useClassCriteria());
76 }
77
78 /**
79 * Constructor for deserialization.
80 */
81 protected ValueMappingImpl() {
82 super();
83 }
84
85 public ValueMappingInfo getValueInfo() {
86 return _info;
87 }
88
89 public ValueHandler getHandler() {
90 return _handler;
91 }
92
93 public void setHandler(ValueHandler handler) {
94 _handler = handler;
95 }
96
97 public MappingRepository getMappingRepository() {
98 return (MappingRepository) getRepository();
99 }
100
101 public FieldMapping getFieldMapping() {
102 return (FieldMapping) getFieldMetaData();
103 }
104
105 public ClassMapping getTypeMapping() {
106 return (ClassMapping) getTypeMetaData();
107 }
108
109 public ClassMapping getDeclaredTypeMapping() {
110 return (ClassMapping) getDeclaredTypeMetaData();
111 }
112
113 public ClassMapping getEmbeddedMapping() {
114 return (ClassMapping) getEmbeddedMetaData();
115 }
116
117 public FieldMapping getValueMappedByMapping() {
118 return (FieldMapping) getValueMappedByMetaData();
119 }
120
121 public Column[] getColumns() {
122 if (_cols.length != 0)
123 return _cols;
124 if (_fk != null)
125 return _fk.getColumns();
126 if (getValueMappedBy() != null)
127 return getValueMappedByMapping().getColumns();
128 return _cols;
129 }
130
131 public void setColumns(Column[] cols) {
132 if (cols == null)
133 cols = Schemas.EMPTY_COLUMNS;
134 _cols = cols;
135 }
136
137 public ColumnIO getColumnIO() {
138 if (_cols.length == 0 && _fk == null && getValueMappedBy() != null)
139 return getValueMappedByMapping().getColumnIO();
140 return (_io == null) ? ColumnIO.UNRESTRICTED : _io;
141 }
142
143 public void setColumnIO(ColumnIO io) {
144 _io = io;
145 }
146
147 public ForeignKey getForeignKey() {
148 if (_fk == null && getValueMappedBy() != null)
149 return getValueMappedByMapping().getForeignKey();
150 return _fk;
151 }
152
153 public void setForeignKey(ForeignKey fk) {
154 _fk = fk;
155 if (fk == null)
156 _join = JOIN_FORWARD;
157 }
158
159 public ForeignKey getForeignKey(ClassMapping target) {
160 if (_fk == null && getValueMappedBy() != null)
161 return getValueMappedByMapping().getForeignKey(target);
162 if (target == null)
163 return _fk;
164 if (_fk == null && _cols.length == 0)
165 return null;
166
167 // always use least-derived joinable type
168 for (ClassMapping sup = target; sup != null;
169 sup = sup.getJoinablePCSuperclassMapping()) {
170 if (sup == getTypeMetaData())
171 return _fk;
172 target = sup;
173 }
174
175 synchronized (this) {
176 if (_targetFKs != null) {
177 Object cachedFK = _targetFKs.get(target);
178 if (cachedFK != null)
179 return (ForeignKey) cachedFK;
180 } else
181 _targetFKs = new HashMap();
182
183 ForeignKey newfk = (_join == JOIN_FORWARD)
184 ? newForwardForeignKey(target) : newInverseForeignKey(target);
185 _targetFKs.put(target, newfk);
186 return newfk;
187 }
188 }
189
190 /**
191 * Create a forward foreign key to the given target.
192 */
193 private ForeignKey newForwardForeignKey(ClassMapping target) {
194 Table table;
195 Column[] cols;
196 if (_fk == null) {
197 table = _cols[0].getTable();
198 cols = _cols;
199 } else {
200 table = _fk.getTable();
201 cols = _fk.getColumns();
202 }
203
204 // gather target cols before adding foreign key to table in case
205 // there is an error while looking for a target col
206 Column[] tcols = new Column[cols.length];
207 for (int i = 0; i < cols.length; i++) {
208 if (cols[i].getTargetField() != null)
209 tcols[i] = getEquivalentColumn(cols[i], target,
210 cols[i].getTargetField());
211 else if (_fk != null)
212 tcols[i] = getEquivalentColumn(_fk.getPrimaryKeyColumn
213 (cols[i]).getName(), target, true);
214 else if (cols[i].getTarget() != null)
215 tcols[i] = getEquivalentColumn(cols[i].getTarget(), target,
216 true);
217 else
218 tcols[i] = getEquivalentColumn(cols[i].getName(), target,
219 false);
220 }
221
222 ForeignKey newfk = table.addForeignKey();
223 newfk.setJoins(cols, tcols);
224 if (_fk != null) {
225 cols = _fk.getConstantColumns();
226 for (int i = 0; i < cols.length; i++)
227 newfk.joinConstant(cols[i], _fk.getConstant(cols[i]));
228
229 cols = _fk.getConstantPrimaryKeyColumns();
230 for (int i = 0; i < cols.length; i++)
231 newfk.joinConstant(_fk.getPrimaryKeyConstant(cols[i]),
232 getEquivalentColumn(cols[i].getName(), target, true));
233 }
234 return newfk;
235 }
236
237 /**
238 * Return the given mapping's equivalent to the given column, using the
239 * target field.
240 */
241 private Column getEquivalentColumn(Column col, ClassMapping target,
242 String fieldName) {
243 fieldName = fieldName.substring(fieldName.indexOf('.') + 1);
244 FieldMapping field = target.getFieldMapping(fieldName);
245 if (field == null)
246 throw new MetaDataException(_loc.get("no-equiv-field",
247 new Object[]{ this, target, fieldName, col }));
248
249 Column[] cols = field.getColumns();
250 if (cols.length != 1)
251 throw new MetaDataException(_loc.get("bad-equiv-field",
252 new Object[]{ this, target, fieldName, col }));
253
254 return cols[0];
255 }
256
257 /**
258 * Return the given mapping's equivalent of the given column.
259 */
260 private Column getEquivalentColumn(String colName, ClassMapping target,
261 boolean explicit) {
262 // if there was no explicit target, use single pk column
263 if (!explicit) {
264 for (ClassMapping cls = target; cls != null;
265 cls = cls.getJoinablePCSuperclassMapping()) {
266 if (cls.getTable() != null) {
267 if (cls.getPrimaryKeyColumns().length == 1)
268 return cls.getPrimaryKeyColumns()[0];
269 break;
270 }
271 }
272 }
273
274 Column ret;
275 for (ClassMapping cls = target; cls != null;
276 cls = cls.getJoinablePCSuperclassMapping()) {
277 if (cls.getTable() != null) {
278 ret = cls.getTable().getColumn(colName);
279 if (ret != null)
280 return ret;
281 }
282 }
283
284 throw new MetaDataException(_loc.get("no-equiv-col", this, target,
285 colName));
286 }
287
288 /**
289 * Return an inverse foreign key from the given related type to our table.
290 */
291 private ForeignKey newInverseForeignKey(ClassMapping target) {
292 FieldMapping field = getFieldMapping();
293 FieldMapping mapped = field.getMappedByMapping();
294 if (mapped == null)
295 throw new MetaDataException(_loc.get("cant-inverse", this));
296
297 mapped = target.getFieldMapping(mapped.getIndex());
298 if (mapped == null || mapped.getTypeCode() != JavaTypes.PC)
299 throw new MetaDataException(_loc.get("no-equiv-mapped-by",
300 this, target, field.getMappedBy()));
301 return mapped.getForeignKey();
302 }
303
304 public int getJoinDirection() {
305 if (_fk == null && getValueMappedBy() != null)
306 return getValueMappedByMapping().getJoinDirection();
307 return _join;
308 }
309
310 public void setJoinDirection(int direction) {
311 _join = direction;
312 }
313
314 public void setForeignKey(Row row, OpenJPAStateManager rel)
315 throws SQLException {
316 if (rel != null)
317 row.setForeignKey(getForeignKey((ClassMapping) rel.getMetaData()),
318 _io, rel);
319 else if (_fk != null)
320 row.setForeignKey(_fk, _io, null);
321 else {
322 for (int i = 0; i < _cols.length; i++) {
323 if (_io == null || (row.getAction() == Row.ACTION_INSERT
324 && _io.isInsertable(i, true))
325 || (row.getAction() != Row.ACTION_INSERT
326 && _io.isUpdatable(i, true)))
327 row.setNull(_cols[i]);
328 }
329 }
330 }
331
332 public void whereForeignKey(Row row, OpenJPAStateManager rel)
333 throws SQLException {
334 if (rel != null)
335 row.whereForeignKey(getForeignKey((ClassMapping)
336 rel.getMetaData()), rel);
337 else if (_fk != null)
338 row.whereForeignKey(_fk, null);
339 else
340 for (int i = 0; i < _cols.length; i++)
341 row.whereNull(_cols[i]);
342 }
343
344 public ClassMapping[] getIndependentTypeMappings() {
345 ClassMapping rel = getTypeMapping();
346 if (rel == null)
347 return ClassMapping.EMPTY_MAPPINGS;
348 if (_poly != POLY_TRUE) {
349 if (!rel.isMapped())
350 return ClassMapping.EMPTY_MAPPINGS;
351 if (_typeArr == null)
352 _typeArr = new ClassMapping[]{ rel };
353 return _typeArr;
354 }
355 return rel.getIndependentAssignableMappings();
356 }
357
358 public int getSelectSubclasses() {
359 ClassMapping rel = getTypeMapping();
360 if (rel == null || !rel.isMapped())
361 return -1;
362
363 switch (_poly) {
364 case POLY_FALSE:
365 return (_criteria) ? Select.SUBS_NONE : Select.SUBS_EXACT;
366 case POLY_TRUE:
367 ClassMapping[] assign = rel.getIndependentAssignableMappings();
368 if (assign.length != 1 || assign[0] != rel)
369 return -1;
370 // no break
371 case POLY_JOINABLE:
372 return (_criteria) ? Select.SUBS_JOINABLE
373 : Select.SUBS_ANY_JOINABLE;
374 default:
375 throw new InternalException();
376 }
377 }
378
379 public Unique getValueUnique() {
380 return _unq;
381 }
382
383 public void setValueUnique(Unique unq) {
384 _unq = unq;
385 }
386
387 public Index getValueIndex() {
388 return _idx;
389 }
390
391 public void setValueIndex(Index idx) {
392 _idx = idx;
393 }
394
395 public boolean getUseClassCriteria() {
396 if (_fk == null && getValueMappedBy() != null)
397 return getValueMappedByMapping().getUseClassCriteria();
398 return _criteria;
399 }
400
401 public void setUseClassCriteria(boolean criteria) {
402 _criteria = criteria;
403 }
404
405 public int getPolymorphic() {
406 return _poly;
407 }
408
409 public void setPolymorphic(int poly) {
410 _poly = poly;
411 }
412
413 public void refSchemaComponents() {
414 for (int i = 0; i < _cols.length; i++)
415 _cols[i].ref();
416 if (_fk != null) {
417 _fk.ref();
418 _fk.refColumns();
419 }
420
421 ClassMapping embed = getEmbeddedMapping();
422 if (embed != null)
423 embed.refSchemaComponents();
424 }
425
426 public void mapConstraints(String name, boolean adapt) {
427 _unq = _info.getUnique(this, name, adapt);
428 _idx = _info.getIndex(this, name, adapt);
429 }
430
431 public void clearMapping() {
432 _handler = null;
433 _cols = Schemas.EMPTY_COLUMNS;
434 _unq = null;
435 _idx = null;
436 _fk = null;
437 _join = JOIN_FORWARD;
438 _info.clear();
439 setResolve(MODE_MAPPING | MODE_MAPPING_INIT, false);
440 }
441
442 public void syncMappingInfo() {
443 if (getValueMappedBy() != null)
444 _info.clear();
445 else {
446 _info.syncWith(this);
447 ClassMapping embed = getEmbeddedMapping();
448 if (embed != null)
449 embed.syncMappingInfo();
450 }
451 }
452
453 public void copyMappingInfo(ValueMapping vm) {
454 setValueMappedBy(vm.getValueMappedBy());
455 setPolymorphic(vm.getPolymorphic());
456 _info.copy(vm.getValueInfo());
457
458 ClassMapping embed = vm.getEmbeddedMapping();
459 if (embed != null && getEmbeddedMapping() != null) {
460 FieldMapping[] tmplates = embed.getFieldMappings();
461 FieldMapping[] fms = getEmbeddedMapping().getFieldMappings();
462 if (tmplates.length == fms.length)
463 for (int i = 0; i < fms.length; i++)
464 fms[i].copyMappingInfo(tmplates[i]);
465 }
466 }
467
468 public boolean resolve(int mode) {
469 int cur = getResolve();
470 if (super.resolve(mode))
471 return true;
472 ClassMapping embed = getEmbeddedMapping();
473 if (embed != null)
474 embed.resolve(mode);
475 if ((mode & MODE_MAPPING) != 0 && (cur & MODE_MAPPING) == 0)
476 resolveMapping();
477 if ((mode & MODE_MAPPING_INIT) != 0 && (cur & MODE_MAPPING_INIT) == 0)
478 initializeMapping();
479 return false;
480 }
481
482 /**
483 * Setup mapping. Our handler will already have been set by our owning
484 * field.
485 */
486 private void resolveMapping() {
487 // mark mapped columns
488 Column[] cols;
489 int insertFlag;
490 if (_fk != null) {
491 cols = _fk.getColumns();
492 insertFlag = Column.FLAG_FK_INSERT;
493 } else {
494 cols = getColumns();
495 insertFlag = Column.FLAG_DIRECT_INSERT;
496 }
497 ColumnIO io = getColumnIO();
498 for (int i = 0; i < cols.length; i++) {
499 if (io.isInsertable(i, false))
500 cols[i].setFlag(insertFlag, true);
501 if (io.isUpdatable(i, false))
502 cols[i].setFlag(insertFlag, true);
503 }
504 }
505
506 /**
507 * Prepare mapping for runtime use.
508 */
509 private void initializeMapping() {
510 if (_fk == null)
511 return;
512
513 // if our fk cols are direct mapped by other values, make them
514 // non-nullable
515 Column[] cols = _fk.getColumns();
516 for (int i = 0; i < cols.length; i++) {
517 if (cols[i].getFlag(Column.FLAG_DIRECT_INSERT))
518 newIO().setNullInsertable(i, false);
519 if (cols[i].getFlag(Column.FLAG_DIRECT_UPDATE))
520 newIO().setNullUpdatable(i, false);
521 }
522
523 // if anything maps our constant fk cols, make them read only
524 int len = cols.length;
525 cols = _fk.getConstantColumns();
526 for (int i = 0; i < cols.length; i++) {
527 if (cols[i].getFlag(Column.FLAG_DIRECT_INSERT)
528 || cols[i].getFlag(Column.FLAG_FK_INSERT))
529 newIO().setInsertable(len + i, false);
530 if (cols[i].getFlag(Column.FLAG_DIRECT_UPDATE)
531 || cols[i].getFlag(Column.FLAG_FK_UPDATE))
532 newIO().setUpdatable(len + i, false);
533 }
534 }
535
536 /**
537 * Return the column I/O information, creating it if necessary.
538 */
539 private ColumnIO newIO() {
540 if (_io == null)
541 _io = new ColumnIO();
542 return _io;
543 }
544 }