private void verifyTypeOperation(Value val, Value param, boolean isNotEqual) { if (val.getPath() == null) return; PCPath path = (PCPath) val.getPath(); Discriminator disc = ((Type) val).getDiscriminator(); if (disc == null || !(val.getMetaData().getPCSuperclass() != null || val.getMetaData().getPCSubclasses().length > 0)) throw new UserException( _loc.get( "invalid-type-argument", path.last() != null ? path.getPCPathString() : path.getSchemaAlias())); if (disc.getColumns().length == 0) { if (disc.getStrategy() instanceof NoneDiscriminatorStrategy) { // limited support for table per class inheritance hierarchy if (path.last() != null) throw new UserException(_loc.get("type-argument-unsupported", path.last().getName())); if (isNotEqual) { if (param != null && param instanceof Null) throw new UserException( _loc.get("type-in-expression-unsupported", path.getSchemaAlias())); else throw new UserException(_loc.get("type-not-equal-unsupported", path.getSchemaAlias())); } } if (param != null && param instanceof CollectionParam) throw new UserException(_loc.get("collection-param-unsupported")); } }
public FetchConfiguration setWriteLockLevel(int level) { if (_state.ctx == null) return this; if (level != DEFAULT && level != MixedLockLevels.LOCK_NONE && level != MixedLockLevels.LOCK_READ && level != MixedLockLevels.LOCK_OPTIMISTIC && level != MixedLockLevels.LOCK_WRITE && level != MixedLockLevels.LOCK_OPTIMISTIC_FORCE_INCREMENT && level != MixedLockLevels.LOCK_PESSIMISTIC_READ && level != MixedLockLevels.LOCK_PESSIMISTIC_WRITE && level != MixedLockLevels.LOCK_PESSIMISTIC_FORCE_INCREMENT) throw new IllegalArgumentException( _loc.get("bad-lock-level", Integer.valueOf(level)).getMessage()); lock(); try { assertActiveTransaction(); if (level == DEFAULT) _state.writeLockLevel = _state.ctx.getConfiguration().getWriteLockLevelConstant(); else _state.writeLockLevel = level; } finally { unlock(); } return this; }
public Expression matches(Value v1, Value v2, String single, String multi, String esc) { if (!(v2 instanceof Const)) throw new UserException(_loc.get("const-only", "matches")); if (esc == null && _type.getMappingRepository().getDBDictionary().requiresSearchStringEscapeForLike == true) { esc = _type.getMappingRepository().getDBDictionary().searchStringEscape; } return new MatchesExpression((Val) v1, (Const) v2, single, multi, esc); }
public FetchConfiguration setLockScope(int scope) { if (scope != DEFAULT && scope != LockScopes.LOCKSCOPE_NORMAL && scope != LockScopes.LOCKSCOPE_EXTENDED) throw new IllegalArgumentException( _loc.get("bad-lock-scope", Integer.valueOf(scope)).getMessage()); if (scope == DEFAULT) _state.lockScope = LOCKSCOPE_NORMAL; else _state.lockScope = scope; return this; }
public FetchConfiguration setQueryTimeout(int timeout) { if (timeout == DEFAULT && _state.ctx != null) _state.queryTimeout = _state.ctx.getConfiguration().getQueryTimeout(); else if (timeout != DEFAULT) { if (timeout < -1) { throw new IllegalArgumentException(_loc.get("invalid-timeout", timeout).getMessage()); } else { _state.queryTimeout = timeout; } } return this; }
public FetchConfiguration addField(String field) { if (StringUtils.isEmpty(field)) throw new UserException(_loc.get("null-field")); lock(); try { if (_state.fields == null) _state.fields = new HashSet<String>(); _state.fields.add(field); } finally { unlock(); } return this; }
public void initialize() { Column order = field.getOrderColumn(); _orderInsert = field.getOrderColumnIO().isInsertable(order, false); _orderUpdate = field.getOrderColumnIO().isUpdatable(order, false); ValueMapping elem = field.getElementMapping(); Log log = field.getRepository().getLog(); if (field.getMappedBy() == null && elem.getUseClassCriteria() && log.isWarnEnabled()) { ForeignKey fk = elem.getForeignKey(); if (elem.getColumnIO().isAnyUpdatable(fk, false)) log.warn(_loc.get("class-crit-owner", field)); } }
public FetchConfiguration setFlushBeforeQueries(int flush) { if (flush != DEFAULT && flush != QueryFlushModes.FLUSH_TRUE && flush != QueryFlushModes.FLUSH_FALSE && flush != QueryFlushModes.FLUSH_WITH_CONNECTION) throw new IllegalArgumentException( _loc.get("bad-flush-before-queries", Integer.valueOf(flush)).getMessage()); if (flush == DEFAULT && _state.ctx != null) _state.flushQuery = _state.ctx.getConfiguration().getFlushBeforeQueriesConstant(); else if (flush != DEFAULT) _state.flushQuery = flush; return this; }
/** * Class mapping for embedded objects. * * @author Abe White * @nojavadoc */ public class EmbeddedClassStrategy extends AbstractClassStrategy { private static final Localizer _loc = Localizer.forPackage(EmbeddedClassStrategy.class); public void map(boolean adapt) { ValueMapping vm = cls.getEmbeddingMapping(); if (vm == null || vm.getType() != cls.getDescribedType()) throw new MetaDataException(_loc.get("not-embed", cls)); ClassMappingInfo info = cls.getMappingInfo(); info.assertNoSchemaComponents(cls, true); ClassMapping owner = null; if (vm.getValueMappedByMapping() != null) owner = vm.getValueMappedByMapping().getDefiningMapping(); else owner = vm.getFieldMapping().getDefiningMapping(); cls.setIdentityType(owner.getIdentityType()); cls.setObjectIdType(owner.getObjectIdType(), owner.isObjectIdTypeShared()); cls.setTable(vm.getFieldMapping().getTable()); cls.setPrimaryKeyColumns(owner.getPrimaryKeyColumns()); cls.setColumnIO(owner.getColumnIO()); } /** Return the proper synthetic null indicator value for the given instance. */ public Object getNullIndicatorValue(OpenJPAStateManager sm) { Column[] cols = cls.getEmbeddingMapping().getColumns(); if (cols.length != 1) return null; if (sm == null && !cols[0].isNotNull()) return null; if (sm == null) return JavaSQLTypes.getEmptyValue(cols[0].getJavaType()); return JavaSQLTypes.getNonEmptyValue(cols[0].getJavaType()); } /** Return whether the given null indicator value means the object is null. */ public boolean indicatesNull(Object val) { Column[] cols = cls.getEmbeddingMapping().getColumns(); if (cols.length != 1) return false; if (val == null) return true; if (cols[0].isNotNull() && val.equals(JavaSQLTypes.getEmptyValue(cols[0].getJavaType()))) return true; if (cols[0].getDefaultString() != null && val.toString().equals(cols[0].getDefaultString())) return true; return false; } public boolean isPrimaryKeyObjectId(boolean hasAll) { return cls.getEmbeddingMapping() .getFieldMapping() .getDefiningMapping() .isPrimaryKeyObjectId(hasAll); } }
public FetchConfiguration addFetchGroup(String name) { if (StringUtils.isEmpty(name)) throw new UserException(_loc.get("null-fg")); lock(); try { if (_state.fetchGroups == null) _state.fetchGroups = new HashSet<String>(); _state.fetchGroups.add(name); if (FetchGroup.NAME_ALL.equals(name)) _state.fetchGroupContainsAll = true; else if (FetchGroup.NAME_DEFAULT.equals(name)) _state.fetchGroupContainsDefault = true; } finally { unlock(); } return this; }
public void map(boolean adapt) { ValueMapping vm = cls.getEmbeddingMapping(); if (vm == null || vm.getType() != cls.getDescribedType()) throw new MetaDataException(_loc.get("not-embed", cls)); ClassMappingInfo info = cls.getMappingInfo(); info.assertNoSchemaComponents(cls, true); ClassMapping owner = null; if (vm.getValueMappedByMapping() != null) owner = vm.getValueMappedByMapping().getDefiningMapping(); else owner = vm.getFieldMapping().getDefiningMapping(); cls.setIdentityType(owner.getIdentityType()); cls.setObjectIdType(owner.getObjectIdType(), owner.isObjectIdTypeShared()); cls.setTable(vm.getFieldMapping().getTable()); cls.setPrimaryKeyColumns(owner.getPrimaryKeyColumns()); cls.setColumnIO(owner.getColumnIO()); }
/** * Sets the hint to the given value. If the key corresponds to a known key, then that value is set * via the setter method. Otherwise it is put into opaque hints map. <br> * In either case, the original value is put in the hints map. So essential difference between * setting a value directly by a setter and via a hint is the memory of this original value. <br> * The other important difference is setting lock levels. Setting of lock level via setter method * needs active transaction. But setting via hint does not. * * @param key a hint key. If it is one of the statically registered hint key then the setter is * called. * @param value to be set. The given value type must match the argument type of the setter, if one * exists. * @param original value as specified by the caller. This value is put in the hints map. * @exception IllegalArgumentException if the given value is not acceptable by the setter method, * if one exists corresponds the given hint key. */ public void setHint(String key, Object value, Object original) { if (key == null) return; if (_hintSetters.containsKey(key)) { Method setter = _hintSetters.get(key); String methodName = setter.getName(); try { if ("setReadLockLevel".equals(methodName) && !isActiveTransaction()) { _state.readLockLevel = (Integer) value; } else if ("setWriteLockLevel".equals(methodName) && !isActiveTransaction()) { _state.writeLockLevel = (Integer) value; } else { setter.invoke(this, Filters.convertToMatchMethodArgument(value, setter)); } } catch (Exception e) { String message = _loc.get("bad-hint-value", key, toString(value), toString(original)).getMessage(); if (e instanceof IllegalArgumentException) { throw new IllegalArgumentException(message); } throw new IllegalArgumentException(message, e); } } addHint(key, original); }
public void map(boolean adapt) { OpenJPAConfiguration conf = field.getRepository().getConfiguration(); boolean isNonDefaultMappingAllowed = field.getRepository().getMetaDataFactory().getDefaults().isNonDefaultMappingAllowed(conf); FieldMapping mapped = field.getMappedByMapping(); // JPA 2.0 allows non-default mapping: Uni-/1-M/@JoinColumn ==> foreign key strategy // Bi-/1-M/@JoinColumn should result in exception if (!isNonDefaultMappingAllowed || mapped != null) { field.getValueInfo().assertNoSchemaComponents(field, !adapt); } field.getKeyMapping().getValueInfo().assertNoSchemaComponents(field.getKey(), !adapt); ValueMapping elem = field.getElementMapping(); if (elem.getTypeCode() != JavaTypes.PC || elem.isEmbeddedPC() || !elem.getTypeMapping().isMapped()) throw new MetaDataException(_loc.get("not-elem-relation", field)); // check for named inverse FieldMappingInfo finfo = field.getMappingInfo(); ValueMappingInfo vinfo = elem.getValueInfo(); boolean criteria = vinfo.getUseClassCriteria(); if (mapped != null) { mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING); if (!(mapped.getStrategy() instanceof RelationFieldStrategy || mapped.getHandler() instanceof UntypedPCValueHandler)) throw new MetaDataException(_loc.get("not-inv-relation", field, mapped)); vinfo.assertNoSchemaComponents(elem, !adapt); elem.setForeignKey(mapped.getForeignKey(field.getDefiningMapping())); elem.setColumns(mapped.getDefiningMapping().getPrimaryKeyColumns()); elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE); elem.setUseClassCriteria(criteria); ForeignKey fk = mapped.getForeignKey(); /** * Foreign key may be null if declared type of the mapped field is abstract and under * table-per-class inheritance strategy will have no mapped table. */ if (fk != null) { field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(), adapt)); field.setOrderColumnIO(finfo.getColumnIO()); } return; } else { if (field.getValueInfo().getColumns().size() > 0 && field.getAccessType() == FieldMetaData.ONE_TO_MANY) { _uni1MFK = true; } } // map inverse foreign key in related table ForeignKey fk = vinfo.getInverseTypeJoin(elem, field.getName(), adapt); if (_uni1MFK) { Column[] locals = fk.getColumns(); for (int i = 0; i < locals.length; i++) locals[i].setUni1MFK(true); } elem.setForeignKey(fk); elem.setColumnIO(vinfo.getColumnIO()); elem.setColumns(elem.getTypeMapping().getPrimaryKeyColumns()); elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE); elem.setUseClassCriteria(criteria); elem.mapConstraints(field.getName(), adapt); field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(), adapt)); field.setOrderColumnIO(finfo.getColumnIO()); }
/** * Maps a relation to a set of other objects using an inverse foreign key in the related object * table. * * @author Abe White */ public abstract class RelationToManyInverseKeyFieldStrategy extends StoreCollectionFieldStrategy { private static final Localizer _loc = Localizer.forPackage(RelationToManyInverseKeyFieldStrategy.class); private boolean _orderInsert = false; private boolean _orderUpdate = false; private boolean _uni1MFK = false; protected ClassMapping[] getIndependentElementMappings(boolean traverse) { return field.getElementMapping().getIndependentTypeMappings(); } protected ForeignKey getJoinForeignKey(ClassMapping elem) { return field.getElementMapping().getForeignKey(elem); } protected void selectElement( Select sel, ClassMapping elem, JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode, Joins joins) { sel.select( elem, field.getElementMapping().getSelectSubclasses(), store, fetch, eagerMode, joins); } protected Object loadElement( OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch, Result res, Joins joins) throws SQLException { ClassMapping elem = res.getBaseMapping(); if (elem == null) elem = field.getElementMapping().getIndependentTypeMappings()[0]; return res.load(elem, store, fetch, joins); } protected Joins join(Joins joins, ClassMapping elem) { ValueMapping vm = field.getElementMapping(); ForeignKey fk = vm.getForeignKey(elem); ClassMapping owner = field.getDefiningMapping(); while (fk.getPrimaryKeyTable() != owner.getTable()) { joins = owner.joinSuperclass(joins, false); owner = owner.getJoinablePCSuperclassMapping(); if (owner == null) throw new InternalException(); } return joins.joinRelation(field.getName(), fk, elem, vm.getSelectSubclasses(), true, true); } protected Joins joinElementRelation(Joins joins, ClassMapping elem) { return joinRelation(joins, false, false); } public void map(boolean adapt) { OpenJPAConfiguration conf = field.getRepository().getConfiguration(); boolean isNonDefaultMappingAllowed = field.getRepository().getMetaDataFactory().getDefaults().isNonDefaultMappingAllowed(conf); FieldMapping mapped = field.getMappedByMapping(); // JPA 2.0 allows non-default mapping: Uni-/1-M/@JoinColumn ==> foreign key strategy // Bi-/1-M/@JoinColumn should result in exception if (!isNonDefaultMappingAllowed || mapped != null) { field.getValueInfo().assertNoSchemaComponents(field, !adapt); } field.getKeyMapping().getValueInfo().assertNoSchemaComponents(field.getKey(), !adapt); ValueMapping elem = field.getElementMapping(); if (elem.getTypeCode() != JavaTypes.PC || elem.isEmbeddedPC() || !elem.getTypeMapping().isMapped()) throw new MetaDataException(_loc.get("not-elem-relation", field)); // check for named inverse FieldMappingInfo finfo = field.getMappingInfo(); ValueMappingInfo vinfo = elem.getValueInfo(); boolean criteria = vinfo.getUseClassCriteria(); if (mapped != null) { mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING); if (!(mapped.getStrategy() instanceof RelationFieldStrategy || mapped.getHandler() instanceof UntypedPCValueHandler)) throw new MetaDataException(_loc.get("not-inv-relation", field, mapped)); vinfo.assertNoSchemaComponents(elem, !adapt); elem.setForeignKey(mapped.getForeignKey(field.getDefiningMapping())); elem.setColumns(mapped.getDefiningMapping().getPrimaryKeyColumns()); elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE); elem.setUseClassCriteria(criteria); ForeignKey fk = mapped.getForeignKey(); /** * Foreign key may be null if declared type of the mapped field is abstract and under * table-per-class inheritance strategy will have no mapped table. */ if (fk != null) { field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(), adapt)); field.setOrderColumnIO(finfo.getColumnIO()); } return; } else { if (field.getValueInfo().getColumns().size() > 0 && field.getAccessType() == FieldMetaData.ONE_TO_MANY) { _uni1MFK = true; } } // map inverse foreign key in related table ForeignKey fk = vinfo.getInverseTypeJoin(elem, field.getName(), adapt); if (_uni1MFK) { Column[] locals = fk.getColumns(); for (int i = 0; i < locals.length; i++) locals[i].setUni1MFK(true); } elem.setForeignKey(fk); elem.setColumnIO(vinfo.getColumnIO()); elem.setColumns(elem.getTypeMapping().getPrimaryKeyColumns()); elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE); elem.setUseClassCriteria(criteria); elem.mapConstraints(field.getName(), adapt); field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(), adapt)); field.setOrderColumnIO(finfo.getColumnIO()); } public void initialize() { Column order = field.getOrderColumn(); _orderInsert = field.getOrderColumnIO().isInsertable(order, false); _orderUpdate = field.getOrderColumnIO().isUpdatable(order, false); ValueMapping elem = field.getElementMapping(); Log log = field.getRepository().getLog(); if (field.getMappedBy() == null && elem.getUseClassCriteria() && log.isWarnEnabled()) { ForeignKey fk = elem.getForeignKey(); if (elem.getColumnIO().isAnyUpdatable(fk, false)) log.warn(_loc.get("class-crit-owner", field)); } } public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm) throws SQLException { if (field.getMappedBy() == null || _orderInsert || _orderUpdate) insert(sm, rm, sm.fetchObject(field.getIndex())); } private void insert(OpenJPAStateManager sm, RowManager rm, Object vals) throws SQLException { if (field.getMappedBy() != null && !_orderInsert && !_orderUpdate) return; Collection coll = toCollection(vals); if (coll == null || coll.isEmpty()) return; ClassMapping rel = field.getElementMapping().getTypeMapping(); int idx = 0; for (Iterator itr = coll.iterator(); itr.hasNext(); idx++) updateInverse(sm.getContext(), itr.next(), rel, rm, sm, idx); } public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm) throws SQLException { if (field.getMappedBy() != null && !_orderInsert && !_orderUpdate) return; Object obj = sm.fetchObject(field.getIndex()); ChangeTracker ct = null; if (obj instanceof Proxy) { Proxy proxy = (Proxy) obj; if (Proxies.isOwner(proxy, sm, field.getIndex())) ct = proxy.getChangeTracker(); } Column order = field.getOrderColumn(); // if no fine-grained change tracking then just delete and reinsert // if no fine-grained change tracking or if an item was removed // from an ordered collection, delete and reinsert if (ct == null || !ct.isTracking() || (order != null && !ct.getRemoved().isEmpty())) { delete(sm, store, rm); insert(sm, rm, obj); return; } // null inverse columns for deletes and update them with our oid for // inserts ClassMapping rel = field.getElementMapping().getTypeMapping(); StoreContext ctx = store.getContext(); if (field.getMappedBy() == null) { Collection rem = ct.getRemoved(); for (Iterator itr = rem.iterator(); itr.hasNext(); ) updateInverse(ctx, itr.next(), rel, rm, null, 0); } Collection add = ct.getAdded(); int seq = ct.getNextSequence(); for (Iterator itr = add.iterator(); itr.hasNext(); seq++) updateInverse(ctx, itr.next(), rel, rm, sm, seq); if (order != null) ct.setNextSequence(seq); } public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm) throws SQLException { if (field.getMappedBy() != null) return; // if nullable, null any existing inverse columns that refer to this obj ValueMapping elem = field.getElementMapping(); ColumnIO io = elem.getColumnIO(); ForeignKey fk = elem.getForeignKey(); if (!elem.getUseClassCriteria() && io.isAnyUpdatable(fk, true)) { assertInversable(); Row row = rm.getAllRows(fk.getTable(), Row.ACTION_UPDATE); row.setForeignKey(fk, io, null); row.whereForeignKey(fk, sm); rm.flushAllRows(row); return; } if (!sm.getLoaded().get(field.getIndex())) return; // update fk on each field value row ClassMapping rel = field.getElementMapping().getTypeMapping(); StoreContext ctx = store.getContext(); Collection objs = toCollection(sm.fetchObject(field.getIndex())); if (objs != null && !objs.isEmpty()) for (Iterator itr = objs.iterator(); itr.hasNext(); ) updateInverse(ctx, itr.next(), rel, rm, sm, 0); } /** This method updates the inverse columns of a 1-M related object with the given oid. */ private void updateInverse( StoreContext ctx, Object inverse, ClassMapping rel, RowManager rm, OpenJPAStateManager sm, int idx) throws SQLException { OpenJPAStateManager invsm = RelationStrategies.getStateManager(inverse, ctx); if (invsm == null) return; ValueMapping elem = field.getElementMapping(); ForeignKey fk = elem.getForeignKey(); ColumnIO io = elem.getColumnIO(); Column order = field.getOrderColumn(); int action; boolean writeable; boolean orderWriteable; if (invsm.isNew() && !invsm.isFlushed()) { // no need to null inverse columns of new instance if (sm == null || sm.isDeleted()) return; writeable = io.isAnyInsertable(fk, false); orderWriteable = _orderInsert; action = Row.ACTION_INSERT; } else if (invsm.isDeleted()) { // no need to null inverse columns of deleted instance if (invsm.isFlushed() || sm == null || !sm.isDeleted()) return; writeable = true; orderWriteable = false; action = Row.ACTION_DELETE; } else { if (sm != null && sm.isDeleted()) sm = null; writeable = io.isAnyUpdatable(fk, sm == null); orderWriteable = field.getOrderColumnIO().isUpdatable(order, sm == null); action = Row.ACTION_UPDATE; } if (!writeable && !orderWriteable) return; assertInversable(); // if this is an update, this might be the only mod to the row, so // make sure the where condition is set Row row = rm.getRow(fk.getTable(), action, invsm, true); if (action == Row.ACTION_UPDATE) row.wherePrimaryKey(invsm); // update the inverse pointer with our oid value if (writeable) row.setForeignKey(fk, io, sm); if (orderWriteable) row.setInt(order, idx); } public Object toDataStoreValue(Object val, JDBCStore store) { ClassMapping cm = field.getElementMapping().getTypeMapping(); return cm.toDataStoreValue(val, cm.getPrimaryKeyColumns(), store); } public Joins join(Joins joins, boolean forceOuter) { ValueMapping elem = field.getElementMapping(); ClassMapping[] clss = elem.getIndependentTypeMappings(); if (clss.length != 1) throw RelationStrategies.unjoinable(elem); if (forceOuter) return joins.outerJoinRelation( field.getName(), elem.getForeignKey(clss[0]), clss[0], elem.getSelectSubclasses(), true, true); return joins.joinRelation( field.getName(), elem.getForeignKey(clss[0]), clss[0], elem.getSelectSubclasses(), true, true); } private void assertInversable() { ValueMapping elem = field.getElementMapping(); if (elem.getIndependentTypeMappings().length != 1) throw RelationStrategies.uninversable(elem); } }
/** * Allows configuration and optimization of how objects are loaded from the data store. * * @since 0.3.0 * @author Abe White * @author Pinaki Poddar * @nojavadoc */ @SuppressWarnings("serial") public class FetchConfigurationImpl implements FetchConfiguration, Cloneable { private static final Localizer _loc = Localizer.forPackage(FetchConfigurationImpl.class); private static Map<String, Method> _hintSetters = new HashMap<String, Method>(); /** * Registers hint keys that have a corresponding setter method. The hint keys are registered in * <code>openjpa.FetchPlan</code> and <code>openjpa</code> as prefix. Also some keys are * registered in <code>javax.persistence</code> namespace. */ static { String[] prefixes = {"openjpa.FetchPlan", "openjpa"}; Class<?> target = FetchConfiguration.class; populateHintSetter(target, "ExtendedPathLookup", boolean.class, prefixes); populateHintSetter(target, "FetchBatchSize", int.class, prefixes); populateHintSetter(target, "FlushBeforeQueries", int.class, prefixes); populateHintSetter(target, "LockScope", int.class, prefixes); populateHintSetter(target, "LockTimeout", int.class, prefixes); populateHintSetter(target, "setLockTimeout", "timeout", int.class, "javax.persistence.lock"); populateHintSetter(target, "MaxFetchDepth", int.class, prefixes); populateHintSetter(target, "QueryTimeout", int.class, prefixes); populateHintSetter(target, "setQueryTimeout", "timeout", int.class, "javax.persistence.query"); populateHintSetter(target, "ReadLockLevel", int.class, prefixes); populateHintSetter(target, "setReadLockLevel", "ReadLockMode", int.class, prefixes); populateHintSetter(target, "WriteLockLevel", int.class, prefixes); populateHintSetter(target, "setWriteLockLevel", "WriteLockMode", int.class, prefixes); } /** * Populate static registry of hints. * * @param target The name of the target class that will receive this hint. * @param hint the simple name of the hint without a prefix. * @param type the value argument type of the target setter method. * @param prefixes the prefixes will be added to the simple hint name. */ protected static void populateHintSetter( Class<?> target, String hint, Class<?> type, String... prefixes) { populateHintSetter(target, "set" + hint, hint, type, prefixes); } /** * Populate static registry of hints. * * @param target The name of the target class that will receive this hint. * @param method The name of the method in the target class that will receive this hint. * @param hint the simple name of the hint without a prefix. * @param type the value argument type of the target setter method. * @param prefixes the prefixes will be added to the simple hint name. */ protected static void populateHintSetter( Class<?> target, String method, String hint, Class<?> type, String... prefixes) { try { Method setter = target.getMethod(method, type); for (String prefix : prefixes) { _hintSetters.put(prefix + "." + hint, setter); } } catch (Exception e) { // should not reach throw new InternalException( "setter for " + hint + " with argument " + type + " does not exist"); } } /** Configurable state shared throughout a traversal chain. */ protected static class ConfigurationState implements Serializable { public transient StoreContext ctx = null; public int fetchBatchSize = 0; public int maxFetchDepth = 1; public boolean queryCache = true; public int flushQuery = 0; public int lockTimeout = -1; public int queryTimeout = -1; public int lockScope = LOCKSCOPE_NORMAL; public int readLockLevel = LOCK_NONE; public int writeLockLevel = LOCK_NONE; public Set<String> fetchGroups = null; public Set<String> fields = null; public Set<Class<?>> rootClasses; public Set<Object> rootInstances; public Map<String, Object> hints = null; public boolean fetchGroupContainsDefault = false; public boolean fetchGroupContainsAll = false; public boolean extendedPathLookup = false; public DataCacheRetrieveMode cacheRetrieveMode = DataCacheRetrieveMode.USE; public DataCacheStoreMode cacheStoreMode = DataCacheStoreMode.USE; } private final ConfigurationState _state; private FetchConfigurationImpl _parent; private String _fromField; private Class<?> _fromType; private String _directRelationOwner; private boolean _load = true; private int _availableRecursion; private int _availableDepth; public FetchConfigurationImpl() { this(null); } protected FetchConfigurationImpl(ConfigurationState state) { _state = (state == null) ? new ConfigurationState() : state; _availableDepth = _state.maxFetchDepth; } public StoreContext getContext() { return _state.ctx; } public void setContext(StoreContext ctx) { // can't reset non-null context to another context if (ctx != null && _state.ctx != null && ctx != _state.ctx) throw new InternalException(); _state.ctx = ctx; if (ctx == null) return; // initialize to conf info OpenJPAConfiguration conf = ctx.getConfiguration(); setFetchBatchSize(conf.getFetchBatchSize()); setFlushBeforeQueries(conf.getFlushBeforeQueriesConstant()); setLockTimeout(conf.getLockTimeout()); setQueryTimeout(conf.getQueryTimeout()); clearFetchGroups(); addFetchGroups(Arrays.asList(conf.getFetchGroupsList())); setMaxFetchDepth(conf.getMaxFetchDepth()); } /** Clone this instance. */ public Object clone() { FetchConfigurationImpl clone = newInstance(null); clone._state.ctx = _state.ctx; clone._parent = _parent; clone._fromField = _fromField; clone._fromType = _fromType; clone._directRelationOwner = _directRelationOwner; clone._load = _load; clone._availableRecursion = _availableRecursion; clone._availableDepth = _availableDepth; clone.copy(this); return clone; } /** Return a new hollow instance. */ protected FetchConfigurationImpl newInstance(ConfigurationState state) { return new FetchConfigurationImpl(state); } public void copy(FetchConfiguration fetch) { setFetchBatchSize(fetch.getFetchBatchSize()); setMaxFetchDepth(fetch.getMaxFetchDepth()); setQueryCacheEnabled(fetch.getQueryCacheEnabled()); setFlushBeforeQueries(fetch.getFlushBeforeQueries()); setExtendedPathLookup(fetch.getExtendedPathLookup()); setLockTimeout(fetch.getLockTimeout()); setQueryTimeout(fetch.getQueryTimeout()); setLockScope(fetch.getLockScope()); clearFetchGroups(); addFetchGroups(fetch.getFetchGroups()); clearFields(); copyHints(fetch); setCacheRetrieveMode(fetch.getCacheRetrieveMode()); setCacheStoreMode(fetch.getCacheStoreMode()); addFields(fetch.getFields()); // don't use setters because require active transaction _state.readLockLevel = fetch.getReadLockLevel(); _state.writeLockLevel = fetch.getWriteLockLevel(); } void copyHints(FetchConfiguration fetch) { if (fetch instanceof FetchConfigurationImpl == false) return; FetchConfigurationImpl from = (FetchConfigurationImpl) fetch; if (from._state == null || from._state.hints == null) return; if (this._state == null) return; if (this._state.hints == null) this._state.hints = new HashMap<String, Object>(); this._state.hints.putAll(from._state.hints); } public int getFetchBatchSize() { return _state.fetchBatchSize; } public FetchConfiguration setFetchBatchSize(int fetchBatchSize) { if (fetchBatchSize == DEFAULT && _state.ctx != null) fetchBatchSize = _state.ctx.getConfiguration().getFetchBatchSize(); if (fetchBatchSize != DEFAULT) _state.fetchBatchSize = fetchBatchSize; return this; } public int getMaxFetchDepth() { return _state.maxFetchDepth; } public FetchConfiguration setMaxFetchDepth(int depth) { if (depth == DEFAULT && _state.ctx != null) depth = _state.ctx.getConfiguration().getMaxFetchDepth(); if (depth != DEFAULT) { _state.maxFetchDepth = depth; if (_parent == null) _availableDepth = depth; } return this; } public boolean getQueryCacheEnabled() { return _state.queryCache; } public FetchConfiguration setQueryCacheEnabled(boolean cache) { _state.queryCache = cache; return this; } public int getFlushBeforeQueries() { return _state.flushQuery; } public boolean getExtendedPathLookup() { return _state.extendedPathLookup; } public FetchConfiguration setExtendedPathLookup(boolean flag) { _state.extendedPathLookup = flag; return this; } public FetchConfiguration setFlushBeforeQueries(int flush) { if (flush != DEFAULT && flush != QueryFlushModes.FLUSH_TRUE && flush != QueryFlushModes.FLUSH_FALSE && flush != QueryFlushModes.FLUSH_WITH_CONNECTION) throw new IllegalArgumentException( _loc.get("bad-flush-before-queries", Integer.valueOf(flush)).getMessage()); if (flush == DEFAULT && _state.ctx != null) _state.flushQuery = _state.ctx.getConfiguration().getFlushBeforeQueriesConstant(); else if (flush != DEFAULT) _state.flushQuery = flush; return this; } public Set<String> getFetchGroups() { if (_state.fetchGroups == null) return Collections.emptySet(); return _state.fetchGroups; } public boolean hasFetchGroup(String group) { return _state.fetchGroups != null && (_state.fetchGroups.contains(group) || _state.fetchGroups.contains(FetchGroup.NAME_ALL)); } public boolean hasFetchGroupDefault() { // Fetch group All includes fetch group Default by definition return _state.fetchGroupContainsDefault || _state.fetchGroupContainsAll; } public boolean hasFetchGroupAll() { return _state.fetchGroupContainsAll; } public FetchConfiguration addFetchGroup(String name) { if (StringUtils.isEmpty(name)) throw new UserException(_loc.get("null-fg")); lock(); try { if (_state.fetchGroups == null) _state.fetchGroups = new HashSet<String>(); _state.fetchGroups.add(name); if (FetchGroup.NAME_ALL.equals(name)) _state.fetchGroupContainsAll = true; else if (FetchGroup.NAME_DEFAULT.equals(name)) _state.fetchGroupContainsDefault = true; } finally { unlock(); } return this; } public FetchConfiguration addFetchGroups(Collection<String> groups) { if (groups == null || groups.isEmpty()) return this; for (String group : groups) addFetchGroup(group); return this; } public FetchConfiguration removeFetchGroup(String group) { lock(); try { if (_state.fetchGroups != null) { _state.fetchGroups.remove(group); if (FetchGroup.NAME_ALL.equals(group)) _state.fetchGroupContainsAll = false; else if (FetchGroup.NAME_DEFAULT.equals(group)) _state.fetchGroupContainsDefault = false; } } finally { unlock(); } return this; } public FetchConfiguration removeFetchGroups(Collection<String> groups) { lock(); try { if (_state.fetchGroups != null && groups != null) for (String group : groups) removeFetchGroup(group); } finally { unlock(); } return this; } public FetchConfiguration clearFetchGroups() { lock(); try { if (_state.fetchGroups != null) { _state.fetchGroups.clear(); _state.fetchGroupContainsAll = false; _state.fetchGroupContainsDefault = true; } } finally { unlock(); } return this; } public FetchConfiguration resetFetchGroups() { clearFetchGroups(); if (_state.ctx != null) addFetchGroups(Arrays.asList(_state.ctx.getConfiguration().getFetchGroupsList())); return this; } public Set<String> getFields() { if (_state.fields == null) return Collections.emptySet(); return _state.fields; } public boolean hasField(String field) { return _state.fields != null && _state.fields.contains(field); } public FetchConfiguration addField(String field) { if (StringUtils.isEmpty(field)) throw new UserException(_loc.get("null-field")); lock(); try { if (_state.fields == null) _state.fields = new HashSet<String>(); _state.fields.add(field); } finally { unlock(); } return this; } public FetchConfiguration addFields(Collection<String> fields) { if (fields == null || fields.isEmpty()) return this; lock(); try { if (_state.fields == null) _state.fields = new HashSet<String>(); _state.fields.addAll(fields); } finally { unlock(); } return this; } public FetchConfiguration removeField(String field) { lock(); try { if (_state.fields != null) _state.fields.remove(field); } finally { unlock(); } return this; } public FetchConfiguration removeFields(Collection<String> fields) { lock(); try { if (_state.fields != null) _state.fields.removeAll(fields); } finally { unlock(); } return this; } public FetchConfiguration clearFields() { lock(); try { if (_state.fields != null) _state.fields.clear(); } finally { unlock(); } return this; } public DataCacheRetrieveMode getCacheRetrieveMode() { return _state.cacheRetrieveMode; } public DataCacheStoreMode getCacheStoreMode() { return _state.cacheStoreMode; } public void setCacheRetrieveMode(DataCacheRetrieveMode mode) { _state.cacheRetrieveMode = mode; } public void setCacheStoreMode(DataCacheStoreMode mode) { _state.cacheStoreMode = mode; } public int getLockTimeout() { return _state.lockTimeout; } public FetchConfiguration setLockTimeout(int timeout) { if (timeout == DEFAULT && _state.ctx != null) _state.lockTimeout = _state.ctx.getConfiguration().getLockTimeout(); else if (timeout != DEFAULT) { if (timeout < -1) { throw new IllegalArgumentException(_loc.get("invalid-timeout", timeout).getMessage()); } else { _state.lockTimeout = timeout; } } return this; } public int getQueryTimeout() { return _state.queryTimeout; } public FetchConfiguration setQueryTimeout(int timeout) { if (timeout == DEFAULT && _state.ctx != null) _state.queryTimeout = _state.ctx.getConfiguration().getQueryTimeout(); else if (timeout != DEFAULT) { if (timeout < -1) { throw new IllegalArgumentException(_loc.get("invalid-timeout", timeout).getMessage()); } else { _state.queryTimeout = timeout; } } return this; } public int getLockScope() { return _state.lockScope; } public FetchConfiguration setLockScope(int scope) { if (scope != DEFAULT && scope != LockScopes.LOCKSCOPE_NORMAL && scope != LockScopes.LOCKSCOPE_EXTENDED) throw new IllegalArgumentException( _loc.get("bad-lock-scope", Integer.valueOf(scope)).getMessage()); if (scope == DEFAULT) _state.lockScope = LOCKSCOPE_NORMAL; else _state.lockScope = scope; return this; } public int getReadLockLevel() { return _state.readLockLevel; } public FetchConfiguration setReadLockLevel(int level) { if (_state.ctx == null) return this; if (level != DEFAULT && level != MixedLockLevels.LOCK_NONE && level != MixedLockLevels.LOCK_READ && level != MixedLockLevels.LOCK_OPTIMISTIC && level != MixedLockLevels.LOCK_WRITE && level != MixedLockLevels.LOCK_OPTIMISTIC_FORCE_INCREMENT && level != MixedLockLevels.LOCK_PESSIMISTIC_READ && level != MixedLockLevels.LOCK_PESSIMISTIC_WRITE && level != MixedLockLevels.LOCK_PESSIMISTIC_FORCE_INCREMENT) throw new IllegalArgumentException( _loc.get("bad-lock-level", Integer.valueOf(level)).getMessage()); lock(); try { if (level != MixedLockLevels.LOCK_NONE) assertActiveTransaction(); if (level == DEFAULT) _state.readLockLevel = _state.ctx.getConfiguration().getReadLockLevelConstant(); else _state.readLockLevel = level; } finally { unlock(); } return this; } public int getWriteLockLevel() { return _state.writeLockLevel; } public FetchConfiguration setWriteLockLevel(int level) { if (_state.ctx == null) return this; if (level != DEFAULT && level != MixedLockLevels.LOCK_NONE && level != MixedLockLevels.LOCK_READ && level != MixedLockLevels.LOCK_OPTIMISTIC && level != MixedLockLevels.LOCK_WRITE && level != MixedLockLevels.LOCK_OPTIMISTIC_FORCE_INCREMENT && level != MixedLockLevels.LOCK_PESSIMISTIC_READ && level != MixedLockLevels.LOCK_PESSIMISTIC_WRITE && level != MixedLockLevels.LOCK_PESSIMISTIC_FORCE_INCREMENT) throw new IllegalArgumentException( _loc.get("bad-lock-level", Integer.valueOf(level)).getMessage()); lock(); try { assertActiveTransaction(); if (level == DEFAULT) _state.writeLockLevel = _state.ctx.getConfiguration().getWriteLockLevelConstant(); else _state.writeLockLevel = level; } finally { unlock(); } return this; } public ResultList<?> newResultList(ResultObjectProvider rop) { if (rop instanceof ListResultObjectProvider) return new SimpleResultList(rop); if (_state.fetchBatchSize < 0) return new EagerResultList(rop); if (rop.supportsRandomAccess()) return new SimpleResultList(rop); return new WindowResultList(rop); } /** Throw an exception if no transaction is active. */ private void assertActiveTransaction() { if (!isActiveTransaction()) throw new NoTransactionException(_loc.get("not-active")); } private boolean isActiveTransaction() { return (_state.ctx != null && _state.ctx.isActive()); } /** * Gets the current hints set on this receiver. The values designate the actual value specified by * the caller and not the values that may have been actually set on the state variables of this * receiver. */ public Map<String, Object> getHints() { if (_state.hints == null) return Collections.emptyMap(); return Collections.unmodifiableMap(_state.hints); } /** Affirms if the given key is set as a hint. */ public boolean isHintSet(String key) { return _state.hints != null && _state.hints.containsKey(key); } /** Removes the given keys and their hint value. */ public void removeHint(String... keys) { if (keys == null || _state.hints == null) return; for (String key : keys) { _state.hints.remove(key); } } public Collection<String> getSupportedHints() { return _hintSetters.keySet(); } /** * Same as <code>setHint(key, value, value)</code>. * * @see #setHint(String, Object, Object) */ public void setHint(String key, Object value) { setHint(key, value, value); } /** * Sets the hint to the given value. If the key corresponds to a known key, then that value is set * via the setter method. Otherwise it is put into opaque hints map. <br> * In either case, the original value is put in the hints map. So essential difference between * setting a value directly by a setter and via a hint is the memory of this original value. <br> * The other important difference is setting lock levels. Setting of lock level via setter method * needs active transaction. But setting via hint does not. * * @param key a hint key. If it is one of the statically registered hint key then the setter is * called. * @param value to be set. The given value type must match the argument type of the setter, if one * exists. * @param original value as specified by the caller. This value is put in the hints map. * @exception IllegalArgumentException if the given value is not acceptable by the setter method, * if one exists corresponds the given hint key. */ public void setHint(String key, Object value, Object original) { if (key == null) return; if (_hintSetters.containsKey(key)) { Method setter = _hintSetters.get(key); String methodName = setter.getName(); try { if ("setReadLockLevel".equals(methodName) && !isActiveTransaction()) { _state.readLockLevel = (Integer) value; } else if ("setWriteLockLevel".equals(methodName) && !isActiveTransaction()) { _state.writeLockLevel = (Integer) value; } else { setter.invoke(this, Filters.convertToMatchMethodArgument(value, setter)); } } catch (Exception e) { String message = _loc.get("bad-hint-value", key, toString(value), toString(original)).getMessage(); if (e instanceof IllegalArgumentException) { throw new IllegalArgumentException(message); } throw new IllegalArgumentException(message, e); } } addHint(key, original); } private void addHint(String name, Object value) { lock(); try { if (_state.hints == null) _state.hints = new HashMap<String, Object>(); _state.hints.put(name, value); } finally { unlock(); } } public Object getHint(String name) { return (_state.hints == null) ? null : _state.hints.get(name); } public Object removeHint(String name) { return (_state.hints == null) ? null : _state.hints.remove(name); } public Set<Class<?>> getRootClasses() { if (_state.rootClasses == null) return Collections.emptySet(); return _state.rootClasses; } public FetchConfiguration setRootClasses(Collection<Class<?>> classes) { lock(); try { if (_state.rootClasses != null) _state.rootClasses.clear(); if (classes != null && !classes.isEmpty()) { if (_state.rootClasses == null) _state.rootClasses = new HashSet<Class<?>>(classes); else _state.rootClasses.addAll(classes); } } finally { unlock(); } return this; } public Set<Object> getRootInstances() { if (_state.rootInstances == null) return Collections.emptySet(); return _state.rootInstances; } public FetchConfiguration setRootInstances(Collection<?> instances) { lock(); try { if (_state.rootInstances != null) _state.rootInstances.clear(); if (instances != null && !instances.isEmpty()) { if (_state.rootInstances == null) { _state.rootInstances = new HashSet<Object>(instances); } else { _state.rootInstances.addAll(instances); } } } finally { unlock(); } return this; } public void lock() { if (_state.ctx != null) _state.ctx.lock(); } public void unlock() { if (_state.ctx != null) _state.ctx.unlock(); } ///////////// // Traversal ///////////// public int requiresFetch(FieldMetaData fm) { if (!includes(fm)) return FETCH_NONE; Class<?> type = fm.getRelationType(); if (type == null) return FETCH_LOAD; if (_availableDepth == 0) return FETCH_NONE; // we can skip calculating recursion depth if this is a top-level conf: // the field is in our fetch groups, so can't possibly not select if (_parent == null) return FETCH_LOAD; String fieldName = fm.getFullName(false); int rdepth = getAvailableRecursionDepth(fm, type, fieldName, false); if (rdepth != FetchGroup.DEPTH_INFINITE && rdepth <= 0) return FETCH_NONE; if (StringUtils.equals(_directRelationOwner, fieldName)) return FETCH_REF; return FETCH_LOAD; } public boolean requiresLoad() { return _load; } public FetchConfiguration traverse(FieldMetaData fm) { Class<?> type = fm.getRelationType(); if (type == null) return this; FetchConfigurationImpl clone = newInstance(_state); clone._parent = this; clone._availableDepth = reduce(_availableDepth); clone._fromField = fm.getFullName(false); clone._fromType = type; clone._availableRecursion = getAvailableRecursionDepth(fm, type, fm.getFullName(false), true); if (StringUtils.equals(_directRelationOwner, fm.getFullName(false))) clone._load = false; else clone._load = _load; FieldMetaData owner = fm.getMappedByMetaData(); if (owner != null && owner.getTypeCode() == JavaTypes.PC) clone._directRelationOwner = owner.getFullName(false); return clone; } /** Whether our configuration state includes the given field. */ private boolean includes(FieldMetaData fmd) { if ((hasFetchGroupDefault() && fmd.isInDefaultFetchGroup()) || hasFetchGroupAll() || hasField(fmd.getFullName(false)) || hasExtendedLookupPath(fmd)) return true; String[] fgs = fmd.getCustomFetchGroups(); for (int i = 0; i < fgs.length; i++) if (hasFetchGroup(fgs[i])) return true; return false; } private boolean hasExtendedLookupPath(FieldMetaData fmd) { return getExtendedPathLookup() && (hasField(fmd.getRealName()) || (_fromField != null && hasField(_fromField + "." + fmd.getName()))); } /** * Return the available recursion depth via the given field for the given type. * * @param traverse whether we're traversing the field */ private int getAvailableRecursionDepth( FieldMetaData fm, Class<?> type, String fromField, boolean traverse) { // see if there's a previous limit int avail = Integer.MIN_VALUE; for (FetchConfigurationImpl f = this; f != null; f = f._parent) { if (StringUtils.equals(f._fromField, fromField) && ImplHelper.isAssignable(f._fromType, type)) { avail = f._availableRecursion; if (traverse) avail = reduce(avail); break; } } if (avail == 0) return 0; // calculate fetch groups max ClassMetaData meta = fm.getDefiningMetaData(); int max = Integer.MIN_VALUE; if (fm.isInDefaultFetchGroup()) max = meta.getFetchGroup(FetchGroup.NAME_DEFAULT).getRecursionDepth(fm); String[] groups = fm.getCustomFetchGroups(); int cur; for (int i = 0; max != FetchGroup.DEPTH_INFINITE && i < groups.length; i++) { // ignore custom groups that are inactive in this configuration if (!this.hasFetchGroup(groups[i])) continue; cur = meta.getFetchGroup(groups[i]).getRecursionDepth(fm); if (cur == FetchGroup.DEPTH_INFINITE || cur > max) max = cur; } // reduce max if we're traversing a self-type relation if (traverse && max != Integer.MIN_VALUE && ImplHelper.isAssignable(meta.getDescribedType(), type)) max = reduce(max); // take min/defined of previous avail and fetch group max if (avail == Integer.MIN_VALUE && max == Integer.MIN_VALUE) { int def = FetchGroup.RECURSION_DEPTH_DEFAULT; return (traverse && ImplHelper.isAssignable(meta.getDescribedType(), type)) ? def - 1 : def; } if (avail == Integer.MIN_VALUE || avail == FetchGroup.DEPTH_INFINITE) return max; if (max == Integer.MIN_VALUE || max == FetchGroup.DEPTH_INFINITE) return avail; return Math.min(max, avail); } /** Reduce the given logical depth by 1. */ private static int reduce(int d) { if (d == 0) return 0; if (d != FetchGroup.DEPTH_INFINITE) d--; return d; } ///////////////// // Debug methods ///////////////// FetchConfiguration getParent() { return _parent; } boolean isRoot() { return _parent == null; } FetchConfiguration getRoot() { return (isRoot()) ? this : _parent.getRoot(); } int getAvailableFetchDepth() { return _availableDepth; } int getAvailableRecursionDepth() { return _availableRecursion; } String getTraversedFromField() { return _fromField; } Class<?> getTraversedFromType() { return _fromType; } List<FetchConfigurationImpl> getPath() { if (isRoot()) return Collections.emptyList(); return trackPath(new ArrayList<FetchConfigurationImpl>()); } List<FetchConfigurationImpl> trackPath(List<FetchConfigurationImpl> path) { if (_parent != null) _parent.trackPath(path); path.add(this); return path; } public String toString() { return "FetchConfiguration@" + System.identityHashCode(this) + " (" + _availableDepth + ")" + getPathString(); } private String getPathString() { List<FetchConfigurationImpl> path = getPath(); if (path.isEmpty()) return ""; StringBuilder buf = new StringBuilder().append(": "); for (Iterator<FetchConfigurationImpl> itr = path.iterator(); itr.hasNext(); ) { buf.append(itr.next().getTraversedFromField()); if (itr.hasNext()) buf.append("->"); } return buf.toString(); } protected String toString(Object o) { return o == null ? "null" : o.toString() + "[" + o.getClass().getName() + "]"; } }
/** Throw an exception if no transaction is active. */ private void assertActiveTransaction() { if (!isActiveTransaction()) throw new NoTransactionException(_loc.get("not-active")); }
/** * Expression factory implementation that can be used to execute queries via SQL. * * @author Abe White * @nojavadoc */ public class JDBCExpressionFactory implements ExpressionFactory, Serializable { private static final Val NULL = new Null(); private static final Localizer _loc = Localizer.forPackage(JDBCExpressionFactory.class); private final ClassMapping _type; private final SelectConstructor _cons = new SelectConstructor(); private int _getMapValueAlias = 0; private boolean _isBooleanLiteralAsNumeric = true; /** Constructor. Supply the type we're querying against. */ public JDBCExpressionFactory(ClassMapping type) { _type = type; } public void setBooleanLiteralAsNumeric(boolean isBooleanLiteralAsNumeric) { _isBooleanLiteralAsNumeric = isBooleanLiteralAsNumeric; } /** Use to create SQL select. */ public SelectConstructor getSelectConstructor() { return _cons; } public Expression emptyExpression() { return new EmptyExpression(); } public Expression asExpression(Value v) { return equal(v, newLiteral(Boolean.TRUE, Literal.TYPE_BOOLEAN)); } public Expression equal(Value v1, Value v2) { // if we're comparing an unaccessed bound variable, like in: // coll.contains (var) && var == x, then translate into: // coll.contains (x) if (v1 instanceof PCPath && ((PCPath) v1).isUnaccessedVariable()) return contains(v1, v2); if (v2 instanceof PCPath && ((PCPath) v2).isUnaccessedVariable()) return contains(v2, v1); if (v1 instanceof Type || v2 instanceof Type) { Value val = v1 instanceof Type ? v1 : v2; verifyTypeOperation(val, null, false); return new EqualTypeExpression((Val) v1, (Val) v2); } return new EqualExpression((Val) v1, (Val) v2); } private void verifyTypeOperation(Value val, Value param, boolean isNotEqual) { if (val.getPath() == null) return; PCPath path = (PCPath) val.getPath(); Discriminator disc = ((Type) val).getDiscriminator(); if (disc == null || !(val.getMetaData().getPCSuperclass() != null || val.getMetaData().getPCSubclasses().length > 0)) throw new UserException( _loc.get( "invalid-type-argument", path.last() != null ? path.getPCPathString() : path.getSchemaAlias())); if (disc.getColumns().length == 0) { if (disc.getStrategy() instanceof NoneDiscriminatorStrategy) { // limited support for table per class inheritance hierarchy if (path.last() != null) throw new UserException(_loc.get("type-argument-unsupported", path.last().getName())); if (isNotEqual) { if (param != null && param instanceof Null) throw new UserException( _loc.get("type-in-expression-unsupported", path.getSchemaAlias())); else throw new UserException(_loc.get("type-not-equal-unsupported", path.getSchemaAlias())); } } if (param != null && param instanceof CollectionParam) throw new UserException(_loc.get("collection-param-unsupported")); } } public Expression notEqual(Value v1, Value v2) { if (v1 instanceof Type || v2 instanceof Type) { Value val = v1 instanceof Type ? v1 : v2; Value param = val == v1 ? (v2 instanceof Null ? v2 : null) : (v1 instanceof Null ? v1 : null); verifyTypeOperation(val, param, true); return new NotEqualTypeExpression((Val) v1, (Val) v2); } return new NotEqualExpression((Val) v1, (Val) v2); } public Expression lessThan(Value v1, Value v2) { return new CompareExpression((Val) v1, (Val) v2, CompareExpression.LESS); } public Expression greaterThan(Value v1, Value v2) { return new CompareExpression((Val) v1, (Val) v2, CompareExpression.GREATER); } public Expression lessThanEqual(Value v1, Value v2) { return new CompareExpression((Val) v1, (Val) v2, CompareExpression.LESS_EQUAL); } public Expression greaterThanEqual(Value v1, Value v2) { return new CompareExpression((Val) v1, (Val) v2, CompareExpression.GREATER_EQUAL); } public Expression isEmpty(Value val) { return new IsEmptyExpression((Val) val); } public Expression isNotEmpty(Value val) { return new IsNotEmptyExpression((Val) val); } public Expression contains(Value map, Value arg) { if (map instanceof Const) { if (arg instanceof Type) { // limited support for table per class inheritance verifyTypeOperation(arg, map, false); if (((ClassMapping) arg.getMetaData()).getDiscriminator().getColumns().length == 0) return new EqualTypeExpression((Val) arg, (Val) map); } return new InExpression((Val) arg, (Const) map); } if (map instanceof SubQ) return new InSubQExpression((Val) arg, (SubQ) map); return new ContainsExpression((Val) map, (Val) arg); } public Expression containsKey(Value map, Value arg) { if (map instanceof Const) return new InKeyExpression((Val) arg, (Const) map); return new ContainsKeyExpression((Val) map, (Val) arg); } public Expression containsValue(Value map, Value arg) { if (map instanceof Const) return new InValueExpression((Val) arg, (Const) map); return new ContainsExpression((Val) map, (Val) arg); } public Expression isInstance(Value val, Class c) { if (val instanceof Const) return new ConstInstanceofExpression((Const) val, c); return new InstanceofExpression((PCPath) val, c); } public Expression and(Expression exp1, Expression exp2) { if (exp1 instanceof BindVariableExpression) return new BindVariableAndExpression((BindVariableExpression) exp1, (Exp) exp2); if (exp2 instanceof BindVariableExpression) return new BindVariableAndExpression((BindVariableExpression) exp2, (Exp) exp1); return new AndExpression((Exp) exp1, (Exp) exp2); } public Expression or(Expression exp1, Expression exp2) { return new OrExpression((Exp) exp1, (Exp) exp2); } public Expression not(Expression exp) { if (!(exp instanceof IsNotEmptyExpression) && !(exp instanceof InSubQExpression) && HasContainsExpressionVisitor.hasContains(exp)) return new NotContainsExpression((Exp) exp); return new NotExpression((Exp) exp); } public Expression bindVariable(Value var, Value val) { // handle the strange case of using a constant path to bind a // variable; in these cases the variable acts like an unbound // variable that we limit by using an IN clause on the constant // value collection if (val instanceof Const) { PCPath path = new PCPath(_type, (Variable) var); path.setMetaData(var.getMetaData()); return new InExpression(path, (Const) val); } return new BindVariableExpression((Variable) var, (PCPath) val, false); } public Expression bindKeyVariable(Value var, Value val) { // handle the strange case of using a constant path to bind a // variable; in these cases the variable acts like an unbound // variable that we limit by using an IN clause on the constant // value collection if (val instanceof Const) { PCPath path = new PCPath(_type, (Variable) var); path.setMetaData(var.getMetaData()); return new InKeyExpression(path, (Const) val); } return new BindVariableExpression((Variable) var, (PCPath) val, true); } public Expression bindValueVariable(Value var, Value val) { return bindVariable(var, val); } public Expression startsWith(Value v1, Value v2) { return new StartsWithExpression((Val) v1, (Val) v2); } public Expression endsWith(Value v1, Value v2) { return new EndsWithExpression((Val) v1, (Val) v2); } public Expression notMatches(Value v1, Value v2, String single, String multi, String esc) { return not(matches(v1, v2, single, multi, esc)); } public Expression matches(Value v1, Value v2, String single, String multi, String esc) { if (!(v2 instanceof Const)) throw new UserException(_loc.get("const-only", "matches")); if (esc == null && _type.getMappingRepository().getDBDictionary().requiresSearchStringEscapeForLike == true) { esc = _type.getMappingRepository().getDBDictionary().searchStringEscape; } return new MatchesExpression((Val) v1, (Const) v2, single, multi, esc); } public Subquery newSubquery(ClassMetaData candidate, boolean subs, String alias) { DBDictionary dict = _type.getMappingRepository().getDBDictionary(); dict.assertSupport(dict.supportsSubselect, "SupportsSubselect"); return new SubQ((ClassMapping) candidate, subs, alias); } public Path newPath() { return new PCPath(_type); } public Path newPath(Value val) { if (val instanceof Const) return new ConstPath((Const) val); if (val instanceof SubQ) return new PCPath((SubQ) val); return new PCPath(_type, (Variable) val); } public Literal newLiteral(Object val, int ptype) { return new Lit(val, ptype); } public Literal newTypeLiteral(Object val, int ptype) { return new TypeLit(val, ptype); } public Value getThis() { return new PCPath(_type); } public Value getNull() { return NULL; } public <T extends Date> Value getCurrentDate(Class<T> dateType) { return new CurrentDate(dateType); } public <T extends Date> Value getCurrentTime(Class<T> dateType) { return new CurrentDate(dateType); } public <T extends Date> Value getCurrentTimestamp(Class<T> dateType) { return new CurrentDate(dateType); } public Parameter newParameter(Object name, Class type) { return new Param(name, type); } public Parameter newCollectionValuedParameter(Object key, Class type) { return new CollectionParam(key, type); } public Value newExtension(FilterListener listener, Value target, Value arg) { return new Extension((JDBCFilterListener) listener, (Val) target, (Val) arg, _type); } public Value newAggregate(AggregateListener listener, Value arg) { return new Aggregate((JDBCAggregateListener) listener, (Val) arg, _type); } public Arguments newArgumentList(Value v1, Value v2) { return new Args((Val) v1, (Val) v2); } public Arguments newArgumentList(Value... vs) { if (vs == null) return new Args(null); Val[] vals = new Val[vs.length]; int i = 0; for (Value v : vs) { vals[i++] = (Val) v; } return new Args(vals); } public Value newUnboundVariable(String name, Class type) { return new Variable(name, type); } public Value newBoundVariable(String name, Class type) { return newUnboundVariable(name, type); } public Value cast(Value val, Class cls) { val.setImplicitType(cls); return val; } public Value add(Value v1, Value v2) { return new Math((Val) v1, (Val) v2, Math.ADD); } public Value subtract(Value v1, Value v2) { return new Math((Val) v1, (Val) v2, Math.SUBTRACT); } public Value multiply(Value v1, Value v2) { return new Math((Val) v1, (Val) v2, Math.MULTIPLY); } public Value divide(Value v1, Value v2) { return new Math((Val) v1, (Val) v2, Math.DIVIDE); } public Value mod(Value v1, Value v2) { return new Math((Val) v1, (Val) v2, Math.MOD); } public Value abs(Value val) { return new Abs((Val) val); } public Value indexOf(Value v1, Value v2) { return new IndexOf((Val) v1, (Val) v2); } public Value concat(Value v1, Value v2) { return new Concat((Val) v1, (Val) v2); } public Value stringLength(Value str) { return new StringLength((Val) str); } public Value trim(Value str, Value trimChar, Boolean where) { return new Trim((Val) str, (Val) trimChar, where); } public Value sqrt(Value val) { return new Sqrt((Val) val); } public Value substring(Value v1, Value v2) { return new Substring((Val) v1, (Val) v2); } public Value toUpperCase(Value val) { return new ToUpperCase((Val) val); } public Value toLowerCase(Value val) { return new ToLowerCase((Val) val); } public Value avg(Value val) { return new Avg((Val) val); } public Value count(Value val) { return new Count((Val) val); } public Value distinct(Value val) { return new Distinct((Val) val); } public Value max(Value val) { return new Max((Val) val); } public Value min(Value val) { return new Min((Val) val); } public Value sum(Value val) { return new Sum((Val) val); } public Value any(Value val) { return new Any((Val) val); } public Value all(Value val) { return new All((Val) val); } public Value size(Value val) { return new Size((Val) val); } public Value index(Value val) { ((PCPath) val).verifyIndexedField(); return new Index((Val) val); } public Value type(Value val) { return new Type((Val) val); } public Value mapEntry(Value key, Value val) { return new MapEntry((Val) key, (Val) val); } public Value mapKey(Value key, Value val) { return new MapKey((Val) key); } public Value getKey(Value val) { ((PCPath) val).getKey(); return val; } public Value getObjectId(Value val) { if (val instanceof Const) return new ConstGetObjectId((Const) val); return new GetObjectId((PCPath) val); } public Value getMapValue(Value map, Value arg) { return new GetMapValue((Val) map, (Val) arg, "gmv" + _getMapValueAlias++); } private Value getLiteralRawString(Value val) { if (val instanceof Lit) { Lit lit = (Lit) val; StringBuilder value = new StringBuilder(); int pType = lit.getParseType(); if (pType == Literal.TYPE_SQ_STRING || pType == Literal.TYPE_STRING) value.append("'").append(lit.getValue().toString()).append("'"); else if (pType == Literal.TYPE_BOOLEAN) { Boolean boolVal = (Boolean) lit.getValue(); if (_isBooleanLiteralAsNumeric) value.append(boolVal ? "1" : "0"); else value.append(boolVal ? "true" : "false"); } else if (pType == Literal.TYPE_ENUM) { lit.setRaw(true); return val; } else value.append(lit.getValue().toString()); lit.setValue(new Raw(value.toString())); return lit; } return val; } public Value simpleCaseExpression(Value caseOperand, Expression[] exp, Value val1) { Exp[] exps = new Exp[exp.length]; for (int i = 0; i < exp.length; i++) exps[i] = (Exp) exp[i]; val1 = getLiteralRawString(val1); return new SimpleCaseExpression((Val) caseOperand, exps, (Val) val1); } public Value generalCaseExpression(Expression[] exp, Value val) { Exp[] exps = new Exp[exp.length]; for (int i = 0; i < exp.length; i++) exps[i] = (Exp) exp[i]; val = getLiteralRawString(val); return new GeneralCaseExpression(exps, (Val) val); } public Expression whenCondition(Expression exp, Value val) { val = getLiteralRawString(val); return new WhenCondition((Exp) exp, (Val) val); } public Expression whenScalar(Value val1, Value val2) { val1 = getLiteralRawString(val1); val2 = getLiteralRawString(val2); return new WhenScalar((Val) val1, (Val) val2); } public Value coalesceExpression(Value[] vals) { ; Object[] values = new Val[vals.length]; for (int i = 0; i < vals.length; i++) { values[i] = getLiteralRawString(vals[i]); } return new CoalesceExpression((Val[]) values); } public Value nullIfExpression(Value val1, Value val2) { val1 = getLiteralRawString(val1); val2 = getLiteralRawString(val2); return new NullIfExpression((Val) val1, (Val) val2); } public Value newFunction(String functionName, Class<?> resultType, Value... args) { return new DatastoreFunction(functionName, resultType, newArgumentList(args)); } public boolean isVerticalType(Value val) { if (!(val instanceof Type)) return false; ClassMapping cm = (ClassMapping) ((Type) val).getMetaData(); String strat = cm.getMappingInfo().getHierarchyStrategy(); if (strat != null && strat.equals(VerticalClassStrategy.ALIAS)) return true; return false; } }