| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.jdbc.meta.strats; |
| |
| import java.sql.SQLException; |
| |
| import org.apache.openjpa.jdbc.kernel.EagerFetchModes; |
| import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; |
| import org.apache.openjpa.jdbc.kernel.JDBCStore; |
| import org.apache.openjpa.jdbc.meta.Embeddable; |
| import org.apache.openjpa.jdbc.meta.Joinable; |
| import org.apache.openjpa.jdbc.meta.ValueHandler; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.ColumnIO; |
| import org.apache.openjpa.jdbc.schema.ForeignKey; |
| import org.apache.openjpa.jdbc.schema.PrimaryKey; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.Joins; |
| import org.apache.openjpa.jdbc.sql.Result; |
| import org.apache.openjpa.jdbc.sql.Row; |
| import org.apache.openjpa.jdbc.sql.RowManager; |
| import org.apache.openjpa.jdbc.sql.SQLBuffer; |
| import org.apache.openjpa.jdbc.sql.Select; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.ValueStrategies; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.MetaDataException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Mapping for a single-valued field that delegates to a {@link ValueHandler}. |
| * |
| * @author Abe White |
| * @since 0.4.0 |
| */ |
| public class HandlerFieldStrategy |
| extends AbstractFieldStrategy |
| implements Joinable, Embeddable { |
| |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final Object NULL = new Object(); |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (HandlerFieldStrategy.class); |
| |
| protected Column[] _cols = null; |
| protected ColumnIO _io = null; |
| protected Object[] _args = null; |
| protected boolean _load = false; |
| protected boolean _lob = false; |
| |
| @Override |
| public void map(boolean adapt) { |
| if (field.getHandler() == null) |
| throw new MetaDataException(_loc.get("no-handler", field)); |
| assertNotMappedBy(); |
| |
| // map join key (if any) |
| field.mapJoin(adapt, false); |
| field.getKeyMapping().getValueInfo().assertNoSchemaComponents |
| (field.getKey(), !adapt); |
| field.getElementMapping().getValueInfo().assertNoSchemaComponents |
| (field.getElement(), !adapt); |
| |
| _io = new ColumnIO(); |
| _cols = HandlerStrategies.map(field, field.getName(), _io, adapt); |
| if (field.getValueStrategy() == ValueStrategies.AUTOASSIGN) { |
| // first see if any columns already marked autoassign; if not mark |
| // them all |
| boolean marked = false; |
| for (int i = 0; !marked && i < _cols.length; i++) |
| if (_cols[i].isAutoAssigned()) |
| marked = true; |
| if (!marked) |
| for (Column col : _cols) { |
| col.setAutoAssigned(true); |
| } |
| } |
| |
| // add primary key columns to table pk if logical |
| field.mapPrimaryKey(adapt); |
| PrimaryKey pk = field.getTable().getPrimaryKey(); |
| if (field.isPrimaryKey() && pk != null && (adapt || pk.isLogical())) |
| for (Column col : _cols) { |
| pk.addColumn(col); |
| } |
| |
| // set joinable |
| if (!field.getHandler().objectValueRequiresLoad(field)) |
| for (Column col : _cols) { |
| field.getDefiningMapping().setJoinable(col, this); |
| } |
| } |
| |
| @Override |
| public void initialize() { |
| _load = field.getHandler().objectValueRequiresLoad(field); |
| if (_load) |
| field.setUsesIntermediate(true); |
| for (int i = 0; !_lob && i < _cols.length; i++) |
| _lob = _cols[i].isLob(); |
| |
| Object args = field.getHandler().getResultArgument(field); |
| if (args == null) |
| _args = null; |
| else if (_cols.length == 1) |
| _args = new Object[]{ args }; |
| else |
| _args = (Object[]) args; |
| } |
| |
| @Override |
| public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| if (field.getColumnIO().isInsertable(0, false)) { |
| Row row = field.getRow(sm, store, rm, Row.ACTION_INSERT); |
| if (row != null) { |
| Object value = sm.fetch(field.getIndex()); |
| if (!HandlerStrategies.set(field, value, store, row, _cols, |
| _io, field.getNullValue() == FieldMetaData.NULL_NONE)) |
| if (field.getValueStrategy() != ValueStrategies.AUTOASSIGN) |
| throw new UserException(_loc.get("cant-set-value", row |
| .getFailedObject(), field, value)); |
| } |
| } |
| } |
| |
| @Override |
| public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| if (field.getColumnIO().isUpdatable(0, false)) { |
| Row row = field.getRow(sm, store, rm, Row.ACTION_UPDATE); |
| if (row != null) { |
| Object value = sm.fetch(field.getIndex()); |
| if (!HandlerStrategies.set(field, value, store, row, _cols, |
| _io, field.getNullValue() == FieldMetaData.NULL_NONE)) |
| if (field.getValueStrategy() != ValueStrategies.AUTOASSIGN) |
| throw new UserException(_loc.get("cant-set-value", row |
| .getFailedObject(), field, value)); |
| } |
| } |
| } |
| |
| @Override |
| public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| field.deleteRow(sm, store, rm); |
| } |
| |
| @Override |
| public int supportsSelect(Select sel, int type, OpenJPAStateManager sm, |
| JDBCStore store, JDBCFetchConfiguration fetch) { |
| if ((type == Select.TYPE_JOINLESS && sel.isSelected(field.getTable())) |
| || (_load && type == Select.TYPE_TWO_PART)) |
| return 1; |
| return 0; |
| } |
| |
| @Override |
| public int select(Select sel, OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, int eagerMode) { |
| if (_cols.length == 0) |
| return -1; |
| |
| if (sm != null && sm.getIntermediate(field.getIndex()) != null) |
| return -1; |
| if (_lob && !field.isPrimaryKey() && (sel.isDistinct() || |
| eagerMode == EagerFetchModes.EAGER_NONE)) |
| return -1; |
| sel.select(_cols, field.join(sel)); |
| return 1; |
| } |
| |
| @Override |
| public void load(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, Result res) |
| throws SQLException { |
| if (_cols.length == 0 || !res.containsAll(_cols)) |
| return; |
| |
| Object val = HandlerStrategies.loadDataStore(field, res, null, _cols); |
| if (!_load) |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, val)); |
| else { |
| if (val == null) |
| val = NULL; |
| sm.setIntermediate(field.getIndex(), val); |
| } |
| } |
| |
| @Override |
| public void load(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch) |
| throws SQLException { |
| // even if no columns, allow a handler to load a generated value |
| if (_cols.length == 0) { |
| if (_load) |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, null, sm, store, fetch)); |
| else |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, null)); |
| return; |
| } |
| |
| // load cached intermediate value? |
| if (_load) { |
| Object ds = sm.getIntermediate(field.getIndex()); |
| if (ds != null) { |
| if (ds == NULL) |
| ds = null; |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, ds, sm, store, fetch)); |
| return; |
| } |
| } |
| |
| Select sel = store.getSQLFactory().newSelect(); |
| sel.select(_cols); |
| field.wherePrimaryKey(sel, sm, store); |
| |
| Result res = sel.execute(store, fetch); |
| Object val = null; |
| try { |
| if (res.next()) |
| val = HandlerStrategies.loadDataStore(field, res, null, _cols); |
| } finally { |
| res.close(); |
| } |
| |
| loadEmbedded(sm, store, fetch, val); |
| } |
| |
| @Override |
| public Object toDataStoreValue(Object val, JDBCStore store) { |
| return HandlerStrategies.toDataStoreValue(field, val, _cols, store); |
| } |
| |
| @Override |
| public void appendIsNull(SQLBuffer sql, Select sel, Joins joins) { |
| joins = join(joins, false); |
| for (int i = 0; i < _cols.length; i++) { |
| if (i > 0) { |
| sql.append(" AND "); |
| } |
| // Some databases do not allow IS NULL for every column type - let the DBDictionary decide. |
| DBDictionary dict = sel.getDictionary(); |
| sql.append(dict.getIsNullSQL(sel.getColumnAlias(_cols[i], joins), _cols[i].getType())); |
| } |
| } |
| |
| @Override |
| public void appendIsNotNull(SQLBuffer sql, Select sel, Joins joins) { |
| joins = join(joins, false); |
| if (_cols.length > 1) { |
| sql.append("("); |
| } |
| for (int i = 0; i < _cols.length; i++) { |
| if (i > 0) { |
| sql.append(" OR "); |
| } |
| // Some databases do not allow IS NOT NULL for every column type - let the DBDictionary decide. |
| DBDictionary dict = sel.getDictionary(); |
| sql.append(dict.getIsNotNullSQL(sel.getColumnAlias(_cols[i], joins), _cols[i].getType())); |
| } |
| if (_cols.length > 1) |
| sql.append(")"); |
| } |
| |
| @Override |
| public Joins join(Joins joins, boolean forceOuter) { |
| return field.join(joins, forceOuter, false); |
| } |
| |
| @Override |
| public Joins joinRelation(Joins joins, boolean forceOuter, |
| boolean traverse) { |
| if (traverse) |
| HandlerStrategies.assertJoinable(field); |
| return joins; |
| } |
| |
| @Override |
| public Object loadProjection(JDBCStore store, JDBCFetchConfiguration fetch, |
| Result res, Joins joins) |
| throws SQLException { |
| return HandlerStrategies.loadObject(field, null, store, fetch, res, |
| joins, _cols, _load); |
| } |
| |
| @Override |
| public boolean isVersionable() { |
| return !_lob && !field.isJoinOuter() |
| && field.getHandler().isVersionable(field); |
| } |
| |
| @Override |
| public void where(OpenJPAStateManager sm, JDBCStore store, RowManager rm, |
| Object prevValue) |
| throws SQLException { |
| Row row = field.getRow(sm, store, rm, Row.ACTION_UPDATE); |
| if (row != null) |
| HandlerStrategies.where(field, prevValue, store, row, _cols); |
| } |
| |
| /////////////////////////// |
| // Joinable implementation |
| /////////////////////////// |
| |
| @Override |
| public int getFieldIndex() { |
| return field.getIndex(); |
| } |
| |
| @Override |
| public Object getPrimaryKeyValue(Result res, Column[] cols, ForeignKey fk, |
| JDBCStore store, Joins joins) |
| throws SQLException { |
| Column col; |
| Object val = null; |
| if (cols.length == 1) { |
| col = cols[0]; |
| if (fk != null){ |
| col = fk.getColumn(col); |
| } |
| |
| //OJ-1793: Get the args from the handler and first check to see if the |
| //args are null. If they aren't null then use the first element in the args |
| //array rather than passing into 'getObject' the entire args array. This is |
| //akin to what is done in the 'else if' leg below. |
| Object[] args = (Object[]) field.getHandler().getResultArgument(field); |
| val = res.getObject(col, (args == null) ? null : args[0], |
| joins); |
| } else if (cols.length > 1) { |
| Object[] vals = new Object[cols.length]; |
| Object[] args = (Object[]) field.getHandler(). |
| getResultArgument(field); |
| for (int i = 0; i < vals.length; i++) { |
| col = cols[i]; |
| if (fk != null) |
| col = fk.getColumn(col); |
| vals[i] = res.getObject(col, (args == null) ? null : args[i], |
| joins); |
| } |
| val = vals; |
| } |
| return field.getHandler().toObjectValue(field, val); |
| } |
| |
| @Override |
| public Column[] getColumns() { |
| return _cols; |
| } |
| |
| @Override |
| public Object[] getResultArguments() { |
| return _args; |
| } |
| |
| @Override |
| public Object getJoinValue(Object fieldVal, Column col, JDBCStore store) { |
| Object val = HandlerStrategies.toDataStoreValue(field, fieldVal, |
| _cols, store); |
| if (val == null || _cols.length < 2) |
| return val; |
| |
| for (int i = 0; i < _cols.length; i++) |
| if (_cols[i] == col) |
| return ((Object[]) val)[i]; |
| throw new InternalException(); |
| } |
| |
| @Override |
| public Object getJoinValue(OpenJPAStateManager sm, Column col, |
| JDBCStore store) { |
| return getJoinValue(sm.fetch(field.getIndex()), col, store); |
| } |
| |
| @Override |
| public void setAutoAssignedValue(OpenJPAStateManager sm, JDBCStore store, |
| Column col, Object autoInc) { |
| Object data; |
| if (_cols.length == 1) |
| data = JavaTypes.convert(autoInc, col.getJavaType()); |
| else { |
| // multiple columns; have to get current value, replace this col's |
| // value with the given one, and reset |
| data = field.getHandler().toDataStoreValue(field, |
| sm.fetch(field.getIndex()), store); |
| if (data == null) |
| data = new Object[_cols.length]; |
| for (int i = 0; i < _cols.length; i++) { |
| if (_cols[i] == col) { |
| ((Object[]) data)[i] = JavaTypes.convert(autoInc, |
| col.getJavaType()); |
| break; |
| } |
| } |
| } |
| |
| Object val = field.getHandler().toObjectValue(field, data); |
| sm.store(field.getIndex(), val); |
| } |
| |
| ///////////////////////////// |
| // Embeddable implementation |
| ///////////////////////////// |
| |
| @Override |
| public ColumnIO getColumnIO() { |
| return _io; |
| } |
| |
| @Override |
| public Object toEmbeddedDataStoreValue(Object val, JDBCStore store) { |
| // don't use HandlerStrategies.toDataStoreValue b/c we want relation ids |
| // to be represented by state managers, not the serialized id value |
| val = field.getHandler().toDataStoreValue(field, val, store); |
| if (val == null && _cols.length > 1) |
| return new Object[_cols.length]; |
| return val; |
| } |
| |
| @Override |
| public Object toEmbeddedObjectValue(Object val) { |
| if (!_load) |
| return field.getHandler().toObjectValue(field, val); |
| return UNSUPPORTED; |
| } |
| |
| @Override |
| public void loadEmbedded(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, Object val) |
| throws SQLException { |
| if (val == null && _cols.length > 1) |
| val = new Object[_cols.length]; |
| if (_load) |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, val, sm, store, fetch)); |
| else |
| sm.store(field.getIndex(), field.getHandler(). |
| toObjectValue(field, val)); |
| } |
| } |