/* * 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.meta; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedActionException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TimeZone; import org.apache.commons.collections.comparators.ComparatorChain; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.lib.conf.Configurations; import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.ClassUtil; import org.apache.openjpa.lib.util.J2DoPrivHelper; import org.apache.openjpa.lib.util.JavaVersions; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Options; import org.apache.openjpa.lib.util.StringUtil; import org.apache.openjpa.lib.xml.Commentable; import org.apache.openjpa.util.Exceptions; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.MetaDataException; import org.apache.openjpa.util.OpenJPAException; import org.apache.openjpa.util.ProxyManager; import org.apache.openjpa.util.UnsupportedException; import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.UserException; /** * Metadata for a managed class field. * * @author Abe White */ @SuppressWarnings("serial") public class FieldMetaData extends Extensions implements ValueMetaData, MetaDataContext, MetaDataModes, Commentable { private static final long serialVersionUID = -566180883009883198L; /** * Constant specifying that no null-value was given. */ public static final int NULL_UNSET = -1; /** * Constant specifying to use a datastore null to persist null values * in object fields. */ public static final int NULL_NONE = 0; /** * Constant specifying to use a datastore default value to persist null * values in object fields. */ public static final int NULL_DEFAULT = 1; /** * Constant specifying to throw an exception when attempting to persist * null values in object fields. */ public static final int NULL_EXCEPTION = 2; /** * Constant specifying the management level of a field. */ public static final int MANAGE_PERSISTENT = 3; /** * Constant specifying the management level of a field. */ public static final int MANAGE_TRANSACTIONAL = 1; /** * Constant specifying the management level of a field. */ public static final int MANAGE_NONE = 0; public static final int ONE_TO_ONE = 1; public static final int ONE_TO_MANY = 2; public static final int MANY_TO_ONE = 3; public static final int MANY_TO_MANY = 4; private static final Localizer _loc = Localizer.forPackage (FieldMetaData.class); private static final int DFG_FALSE = 1; private static final int DFG_TRUE = 2; private static final int DFG_EXPLICIT = 4; private static final Method DEFAULT_METHOD; static { try { DEFAULT_METHOD = Object.class.getMethod("wait", (Class[]) null); } catch (Exception e) { // shouldn't ever happen throw new InternalException(e); } } // name and type private final ValueMetaData _val; private final ValueMetaData _key; private final ValueMetaData _elem; private final ClassMetaData _owner; private final String _name; private Class<?> _dec = null; private ClassMetaData _decMeta = null; private String _fullName = null; private String _embedFullName = null; private int _resMode = MODE_NONE; private String _mappedByIdValue = null; private int _access = AccessCode.UNKNOWN; // load/store info private String[] _comments = null; private int _listIndex = -1; //////////////////////////////////////////////////////////////////// // Note: if you add additional state, make sure to add it to copy() //////////////////////////////////////////////////////////////////// // misc info private Class<?> _proxyClass = null; private Object _initializer = null; private boolean _transient = false; private boolean _primKey = false; private Boolean _version = null; private int _nullValue = NULL_UNSET; private int _manage = MANAGE_PERSISTENT; private int _index = -1; private int _decIndex = -1; private int _pkIndex = -1; private boolean _explicit = false; private int _dfg = 0; private Set<String> _fgSet = null; private String[] _fgs = null; private String _lfg = null; private Boolean _lrs = null; private Boolean _stream = null; private String _extName = null; private String _factName = null; private String _extString = null; private Map _extValues = Collections.EMPTY_MAP; private Map _fieldValues = Collections.EMPTY_MAP; private Boolean _enumField = null; private Boolean _lobField = null; private Boolean _serializableField = null; private boolean _generated = false; private boolean _useSchemaElement = true; // Members aren't serializable. Use a proxy that can provide a Member // to avoid writing the full Externalizable implementation. private MemberProvider _backingMember = null; // Members aren't serializable. Initializing _extMethod and _factMethod to // DEFAULT_METHOD is sufficient to trigger lazy population of these fields. private transient Method _extMethod = DEFAULT_METHOD; private transient Member _factMethod = DEFAULT_METHOD; // intermediate and impl data private boolean _intermediate = true; private Boolean _implData = Boolean.TRUE; // value generation private int _valStrategy = -1; private int _upStrategy = -1; private String _seqName = ClassMetaData.DEFAULT_STRING; private SequenceMetaData _seqMeta = null; // inverses private String _mappedBy = null; private FieldMetaData _mappedByMeta = null; private FieldMetaData[] _inverses = null; private String _inverse = ClassMetaData.DEFAULT_STRING; // ordering on load private Order[] _orders = null; private String _orderDec = null; // indicate if this field is used by other field as "order by" value private boolean _usedInOrderBy = false; private boolean _isElementCollection = false; private int _associationType; private boolean _persistentCollection = false; private Boolean _delayCapable = null; /** * Constructor. * * @param name the field name * @param type the field type * @param owner the owning class metadata */ protected FieldMetaData(String name, Class<?> type, ClassMetaData owner) { _name = name; _owner = owner; _dec = null; _decMeta = null; _val = owner.getRepository().newValueMetaData(this); _key = owner.getRepository().newValueMetaData(this); _elem = owner.getRepository().newValueMetaData(this); setDeclaredType(type); } /** * Supply the backing member object; this allows us to utilize * parameterized type information if available. * Sets the access style of this receiver based on whether the given * member represents a field or getter method. */ public void backingMember(Member member) { if (member == null) return; if (Modifier.isTransient(member.getModifiers())) _transient = true; _backingMember = new MemberProvider(member); Class<?> type; Class<?>[] types; if (member instanceof Field) { Field f = (Field) member; type = f.getType(); types = JavaVersions.getParameterizedTypes(f); setAccessType(AccessCode.FIELD); } else { Method meth = (Method) member; type = meth.getReturnType(); types = JavaVersions.getParameterizedTypes(meth); setAccessType(AccessCode.PROPERTY); } setDeclaredType(type); if (Collection.class.isAssignableFrom(type) && _elem.getDeclaredType() == Object.class && types.length == 1) { _elem.setDeclaredType(types[0]); } else if (Map.class.isAssignableFrom(type) && types.length == 2) { if (_key.getDeclaredType() == Object.class) _key.setDeclaredType(types[0]); if (_elem.getDeclaredType() == Object.class) _elem.setDeclaredType(types[1]); } } /** * Return the backing member supplied in {@link #backingMember}. */ public Member getBackingMember() { return (_backingMember == null) ? null : _backingMember.getMember(); } /** * The metadata repository. */ public MetaDataRepository getRepository() { return _owner.getRepository(); } /** * The class that defines the metadata for this field. */ public ClassMetaData getDefiningMetaData() { return _owner; } /** * The declaring class. */ public Class<?> getDeclaringType() { return (_dec == null) ? _owner.getDescribedType() : _dec; } /** * The declaring class. */ public void setDeclaringType(Class<?> cls) { _dec = cls; _decMeta = null; _fullName = null; _embedFullName = null; } /** * The declaring class. */ public ClassMetaData getDeclaringMetaData() { if (_dec == null) return _owner; if (_decMeta == null) _decMeta = getRepository().getMetaData(_dec, _owner.getEnvClassLoader(), true); return _decMeta; } /** * The field name. */ public String getName() { return _name; } /** * The field name, qualified by the owning class. * @deprecated Use getFullName(boolean) instead. */ public String getFullName() { return getFullName(false); } /** * The field name, qualified by the owning class and optionally the * embedding owner's name (if any). */ public String getFullName(boolean embedOwner) { if (_fullName == null) _fullName = getDeclaringType().getName() + "." + _name; if (embedOwner && _embedFullName == null) { if (_owner.getEmbeddingMetaData() == null) _embedFullName = _fullName; else _embedFullName = _owner.getEmbeddingMetaData(). getFieldMetaData().getFullName(true) + "." + _fullName; } return (embedOwner) ? _embedFullName : _fullName; } /** * The field name, qualified by the defining class. */ public String getRealName() { // Added to support OPENJPA-704 return getDefiningMetaData().getDescribedType().getName() + "." + _name; } /** * MetaData about the field value. */ public ValueMetaData getValue() { return _val; } /** * Metadata about the key value. */ public ValueMetaData getKey() { return _key; } /** * Metadata about the element value. */ public ValueMetaData getElement() { return _elem; } /** * Return whether this field is mapped to the datastore. By default, * returns true for all persistent fields whose defining class is mapped. */ public boolean isMapped() { return _manage == MANAGE_PERSISTENT && _owner.isMapped(); } /** * The type this field was initialized with, and therefore the * type to use for proxies when loading data into this field. */ public Class<?> getProxyType() { return (_proxyClass == null) ? getDeclaredType() : _proxyClass; } /** * The type this field was initialized with, and therefore the * type to use for proxies when loading data into this field. */ public void setProxyType(Class<?> type) { _proxyClass = type; } /** * The initializer used by the field, or null if none. This * is additional information for initializing the field, such as * a custom {@link Comparator} used by a {@link Set} or * a {@link TimeZone} used by a {@link Calendar}. */ public Object getInitializer() { return _initializer; } /** * The initializer used by the field, or null if none. This * is additional information for initializing the field, such as * a custom {@link Comparator} used by a {@link Set} or * a {@link TimeZone} used by a {@link Calendar}. */ public void setInitializer(Object initializer) { _initializer = initializer; } /** * Return whether this is a transient field. */ public boolean isTransient() { return _transient; } /** * Return whether this is a transient field. */ public void setTransient(boolean trans) { _transient = trans; } /** * The absolute index of this persistent/transactional field. */ public int getIndex() { return _index; } /** * The absolute index of this persistent/transactional field. */ public void setIndex(int index) { _index = index; } /** * The relative index of this persistent/transactional field. */ public int getDeclaredIndex() { return _decIndex; } /** * The relative index of this persistent/transactional field. */ public void setDeclaredIndex(int index) { _decIndex = index; } /** * The index in which this field was listed in the metadata. Defaults to * <code>-1</code> if this field was not listed in the metadata. */ public int getListingIndex() { return _listIndex; } /** * The index in which this field was listed in the metadata. Defaults to * <code>-1</code> if this field was not listed in the metadata. */ public void setListingIndex(int index) { _listIndex = index; } /** * The absolute primary key index for this field, or -1 if not a primary * key. The first primary key field has index 0, the second index 1, etc. */ public int getPrimaryKeyIndex() { return _pkIndex; } /** * The absolute primary key index for this field, or -1 if not a primary * key. The first primary key field has index 0, the second index 1, etc. */ public void setPrimaryKeyIndex(int index) { _pkIndex = index; } /** * Return the management level for the field. Will be one of: * <ul> * <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li> * <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not * persistent</li> * <li>{@link #MANAGE_NONE}: the field is not managed</li> * </ul> Defaults to {@link #MANAGE_PERSISTENT}. */ public int getManagement() { return _manage; } /** * Return the management level for the field. Will be one of: * <ul> * <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li> * <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not * persistent</li> * <li>{@link #MANAGE_NONE}: the field is not managed</li> * </ul> * Defaults to {@link #MANAGE_PERSISTENT}. */ public void setManagement(int manage) { if ((_manage == MANAGE_NONE) != (manage == MANAGE_NONE)) _owner.clearFieldCache(); _manage = manage; } /** * Whether this is a primary key field. */ public boolean isPrimaryKey() { return _primKey; } /** * Whether this is a primary key field. */ public void setPrimaryKey(boolean primKey) { _primKey = primKey; } /** * For a primary key field, return the type of the corresponding object id * class field. */ public int getObjectIdFieldTypeCode() { ClassMetaData relmeta = getDeclaredTypeMetaData(); if (relmeta == null) return getDeclaredTypeCode(); if (relmeta.getIdentityType() == ClassMetaData.ID_DATASTORE) { boolean unwrap = getRepository().getMetaDataFactory().getDefaults(). isDataStoreObjectIdFieldUnwrapped(); return (unwrap) ? JavaTypes.LONG : JavaTypes.OBJECT; } if (relmeta.isOpenJPAIdentity()) return relmeta.getPrimaryKeyFields()[0].getObjectIdFieldTypeCode(); return JavaTypes.OBJECT; } /** * For a primary key field, return the type of the corresponding object id * class field. */ public Class<?> getObjectIdFieldType() { ClassMetaData relmeta = getDeclaredTypeMetaData(); if (relmeta == null || getValue().isEmbedded()) return getDeclaredType(); switch (relmeta.getIdentityType()) { case ClassMetaData.ID_DATASTORE: boolean unwrap = getRepository().getMetaDataFactory(). getDefaults().isDataStoreObjectIdFieldUnwrapped(); return (unwrap) ? long.class : Object.class; case ClassMetaData.ID_APPLICATION: if (relmeta.isOpenJPAIdentity()) return relmeta.getPrimaryKeyFields()[0]. getObjectIdFieldType(); return (relmeta.getObjectIdType() == null) ? Object.class : relmeta.getObjectIdType(); default: return Object.class; } } /** * Whether this field holds optimistic version information. */ public boolean isVersion() { return _version == Boolean.TRUE; } /** * Whether this field holds optimistic version information. */ public void setVersion(boolean version) { _version = (version) ? Boolean.TRUE : Boolean.FALSE; } /** * Whether this field is in the default fetch group. */ public boolean isInDefaultFetchGroup() { if (_dfg == 0) { if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion()) _dfg = DFG_FALSE; else { // field left as default; dfg setting depends on type switch (getTypeCode()) { case JavaTypes.OBJECT: if (isSerializable() || isEnum()) _dfg = DFG_TRUE; else _dfg = DFG_FALSE; break; case JavaTypes.ARRAY: if (isLobArray()) _dfg = DFG_TRUE; else _dfg = DFG_FALSE; break; case JavaTypes.COLLECTION: case JavaTypes.MAP: case JavaTypes.PC: case JavaTypes.PC_UNTYPED: _dfg = DFG_FALSE; break; default: _dfg = DFG_TRUE; } } } return (_dfg & DFG_TRUE) > 0; } private boolean isEnum() { if (_enumField == null) { Class<?> decl = getDeclaredType(); _enumField = Enum.class.isAssignableFrom(decl) ? Boolean.TRUE : Boolean.FALSE; } return _enumField.booleanValue(); } private boolean isSerializable() { if (_serializableField == null) { Class<?> decl = getDeclaredType(); if (Serializable.class.isAssignableFrom(decl)) _serializableField = Boolean.TRUE; else _serializableField = Boolean.FALSE; } return _serializableField.booleanValue(); } private boolean isLobArray() { // check for byte[], Byte[], char[], Character[] if (_lobField == null) { Class<?> decl = getDeclaredType(); if (decl == byte[].class || decl == Byte[].class || decl == char[].class || decl == Character[].class) _lobField = Boolean.TRUE; else _lobField = Boolean.FALSE; } return _lobField.booleanValue(); } /** * Whether this field is in the default fetch group. */ public void setInDefaultFetchGroup(boolean dfg) { if (dfg) _dfg = DFG_TRUE; else _dfg = DFG_FALSE; _dfg |= DFG_EXPLICIT; } /** * Whether the default fetch group setting is explicit. */ public boolean isDefaultFetchGroupExplicit() { return (_dfg & DFG_EXPLICIT) > 0; } /** * Whether the default fetch group setting is explicit. Allow setting * for testing. */ public void setDefaultFetchGroupExplicit(boolean explicit) { if (explicit) _dfg |= DFG_EXPLICIT; else _dfg &= ~DFG_EXPLICIT; } /** * Gets the name of the custom fetch groups those are associated to this * receiver. This does not include the "default" and "all" fetch groups. * * @return the set of fetch group names, not including the default and * all fetch groups. */ public String[] getCustomFetchGroups() { if (_fgs == null) { if (_fgSet == null || _manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion()) _fgs = new String[0]; else _fgs = (String[]) _fgSet.toArray(new String[_fgSet.size()]); } return _fgs; } /** * The fetch group that is to be loaded when this receiver is loaded, or * null if none set. */ public String getLoadFetchGroup () { return _lfg; } /** * The fetch group that is to be loaded when this receiver is loaded, or * null if none set. */ public void setLoadFetchGroup (String lfg) { if ("".equals(lfg)) lfg = null; _lfg = lfg; } /** * Whether this field is in the given fetch group. */ public boolean isInFetchGroup(String fg) { if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion()) return false; if (FetchGroup.NAME_ALL.equals(fg)) return true; if (FetchGroup.NAME_DEFAULT.equals(fg)) return isInDefaultFetchGroup(); return _fgSet != null && _fgSet.contains(fg); } /** * Set whether this field is in the given fetch group. * * @param fg is the name of a fetch group that must be present in the * class that declared this field or any of its persistent superclasses. */ public void setInFetchGroup(String fg, boolean in) { if (StringUtil.isEmpty(fg)) throw new MetaDataException(_loc.get("empty-fg-name", this)); if (fg.equals(FetchGroup.NAME_ALL)) return; if (fg.equals(FetchGroup.NAME_DEFAULT)) { setInDefaultFetchGroup(in); return; } if (_owner.getFetchGroup(fg) == null) throw new MetaDataException(_loc.get("unknown-fg", fg, this)); if (in && _fgSet == null) _fgSet = new HashSet<String>(); if ((in && _fgSet.add(fg)) || (!in && _fgSet != null && _fgSet.remove(fg))) _fgs = null; } /** * How the data store should treat null values for this field: * <ul> * <li>{@link #NULL_UNSET}: no value supplied</li> * <li>{@link #NULL_NONE}: leave null values as null in the data store</li> * <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null * at commit</li> * <li>{@link #NULL_DEFAULT}: use the database default if this field is * null at commit</li> * </ul> Defaults to {@link #NULL_UNSET}. */ public int getNullValue() { return _nullValue; } /** * How the data store should treat null values for this field: * <ul> * <li>{@link #NULL_UNSET}: no value supplied</li> * <li>{@link #NULL_NONE}: leave null values as null in the data store</li> * <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null * at commit</li> * <li>{@link #NULL_DEFAULT}: use the database default if this field is * null at commit</li> * </ul> Defaults to {@link #NULL_UNSET}. */ public void setNullValue(int nullValue) { _nullValue = nullValue; } /** * Whether this field is explicitly declared in the metadata. */ public boolean isExplicit() { return _explicit; } /** * Whether this field is explicitly declared in the metadata. */ public void setExplicit(boolean explicit) { _explicit = explicit; } /** * The field that this field shares a mapping with. */ public String getMappedBy() { return _mappedBy; } /** * The field that this field shares a mapping with. */ public void setMappedBy(String mapped) { _mappedBy = mapped; _mappedByMeta = null; } /** * The field that this field shares a mapping with. */ public FieldMetaData getMappedByMetaData() { if (_mappedBy != null && _mappedByMeta == null) { ClassMetaData meta = null; switch (getTypeCode()) { case JavaTypes.PC: meta = getTypeMetaData(); break; case JavaTypes.ARRAY: case JavaTypes.COLLECTION: case JavaTypes.MAP: meta = _elem.getTypeMetaData(); break; } FieldMetaData field = (meta == null) ? null : getMappedByField(meta, _mappedBy); if (field == null) throw new MetaDataException(_loc.get("no-mapped-by", this, _mappedBy)); if (field.getMappedBy() != null) throw new MetaDataException(_loc.get("circ-mapped-by", this, _mappedBy)); OpenJPAConfiguration conf = getRepository().getConfiguration(); boolean isAbstractMappingUniDirectional = getRepository().getMetaDataFactory(). getDefaults().isAbstractMappingUniDirectional(conf); if (isAbstractMappingUniDirectional) { if (field.getDeclaringMetaData().isAbstract()) throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", field, field.getDeclaringMetaData())); if (this.getDeclaringMetaData().isAbstract()) throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", this, this.getDeclaringMetaData())); } _mappedByMeta = field; } return _mappedByMeta; } public FieldMetaData getMappedByField(ClassMetaData meta, String mappedBy) { FieldMetaData field = meta.getField(mappedBy); if (field != null) return field; int dotIdx = mappedBy.indexOf("."); if ( dotIdx == -1) return null; String fieldName = mappedBy.substring(0, dotIdx); FieldMetaData field1 = meta.getField(fieldName); if (field1 == null) return null; ClassMetaData meta1 = field1.getEmbeddedMetaData(); if (meta1 == null) return null; String mappedBy1 = mappedBy.substring(dotIdx + 1); return getMappedByField(meta1, mappedBy1); } /** * Logical inverse field. */ public String getInverse() { if (ClassMetaData.DEFAULT_STRING.equals(_inverse)) _inverse = null; return _inverse; } /** * Logical inverse field. */ public void setInverse(String inverse) { _inverses = null; _inverse = inverse; } /** * Return all inverses of this field. */ public FieldMetaData[] getInverseMetaDatas() { if (_inverses == null) { // can't declare both an inverse owner and a logical inverse String inv = getInverse(); if (_mappedBy != null && inv != null && !_mappedBy.equals(inv)) throw new MetaDataException(_loc.get("mapped-not-inverse", this)); // get the metadata for the type on the other side of this relation ClassMetaData meta = null; switch (getTypeCode()) { case JavaTypes.PC: meta = getTypeMetaData(); break; case JavaTypes.ARRAY: case JavaTypes.COLLECTION: case JavaTypes.MAP: meta = _elem.getTypeMetaData(); break; } Collection<FieldMetaData> inverses = null; if (meta != null) { // add mapped by and named inverse, if any FieldMetaData field = getMappedByMetaData(); if (field != null) { // mapped by field isn't necessarily a pc type, but all // inverses must be if (field.getTypeCode() == JavaTypes.PC || field.getElement().getTypeCode() == JavaTypes.PC) { inverses = new ArrayList<FieldMetaData>(3); inverses.add(field); } } else if (inv != null) { field = meta.getField(inv); if (field == null) throw new MetaDataException(_loc.get("no-inverse", this, inv)); inverses = new ArrayList<FieldMetaData>(3); inverses.add(field); } // scan rel type for fields that name this field as an inverse FieldMetaData[] fields = meta.getFields(); Class<?> type = getDeclaringMetaData().getDescribedType(); for (int i = 0; i < fields.length; i++) { // skip fields that aren't compatible with our owning class switch (fields[i].getTypeCode()) { case JavaTypes.PC: if (!type.isAssignableFrom(fields[i].getType())) continue; break; case JavaTypes.COLLECTION: case JavaTypes.ARRAY: if (!type.isAssignableFrom(fields[i]. getElement().getType())) continue; break; default: continue; } // if the field declares us as its inverse and we haven't // already added it (we might have if we also declared it // as our inverse), add it now if (_name.equals(fields[i].getMappedBy()) || _name.equals(fields[i].getInverse())) { if (inverses == null) inverses = new ArrayList<FieldMetaData>(3); if (!inverses.contains(fields[i])) inverses.add(fields[i]); } } } MetaDataRepository repos = getRepository(); if (inverses == null) _inverses = repos.EMPTY_FIELDS; else _inverses = inverses.toArray (repos.newFieldMetaDataArray(inverses.size())); } return _inverses; } /** * The strategy to use for insert value generation. * One of the constants from {@link ValueStrategies}. */ public int getValueStrategy() { if (_valStrategy == -1) _valStrategy = ValueStrategies.NONE; return _valStrategy; } /** * The strategy to use for insert value generation. * One of the constants from {@link ValueStrategies}. */ public void setValueStrategy(int strategy) { _valStrategy = strategy; if (strategy != ValueStrategies.SEQUENCE) setValueSequenceName(null); } /** * The value sequence name, or null for none. */ public String getValueSequenceName() { if (ClassMetaData.DEFAULT_STRING.equals(_seqName)) _seqName = null; return _seqName; } /** * The value sequence name, or null for none. */ public void setValueSequenceName(String seqName) { _seqName = seqName; _seqMeta = null; if (seqName != null) setValueStrategy(ValueStrategies.SEQUENCE); } /** * Metadata for the value sequence. */ public SequenceMetaData getValueSequenceMetaData() { if (_seqMeta == null && getValueSequenceName() != null) _seqMeta = getRepository().getSequenceMetaData(_owner, getValueSequenceName(), true); return _seqMeta; } /** * The strategy to use when updating the field. */ public int getUpdateStrategy() { if (isVersion()) return UpdateStrategies.RESTRICT; if (_upStrategy == -1) _upStrategy = UpdateStrategies.NONE; return _upStrategy; } /** * Set the update strategy. */ public void setUpdateStrategy(int strategy) { _upStrategy = strategy; } /** * Whether this field is backed by a large result set. */ public boolean isLRS() { return _lrs == Boolean.TRUE && _manage == MANAGE_PERSISTENT; } /** * Whether this field is backed by a large result set. */ public void setLRS(boolean lrs) { _lrs = (lrs) ? Boolean.TRUE : Boolean.FALSE; } /** * Whether this field is backed by a stream. * * @since 1.1.0 */ public boolean isStream() { return _stream == Boolean.TRUE && _manage == MANAGE_PERSISTENT; } /** * Whether this field is backed by a stream. * * @since 1.1.0 */ public void setStream(boolean stream) { _stream = (stream) ? Boolean.TRUE : Boolean.FALSE; } /** * Whether this field uses intermediate data when loading/storing * information through a {@link OpenJPAStateManager}. Defaults to true. * * @see OpenJPAStateManager#setIntermediate(int,Object) */ public boolean usesIntermediate() { return _intermediate; } /** * Whether this field uses intermediate data when loading/storing * information through a {@link OpenJPAStateManager}. Defaults to true. * * @see OpenJPAStateManager#setIntermediate(int,Object) */ public void setUsesIntermediate(boolean intermediate) { _intermediate = intermediate; _owner.clearExtraFieldDataTable(); } /** * Whether this field uses impl data in conjunction with standard * field data when acting on a {@link OpenJPAStateManager}. * Defaults to {@link Boolean#TRUE} (non-cachable impl data). * * @return {@link Boolean#FALSE} if this field does not use impl data, * {@link Boolean#TRUE} if this field uses non-cachable impl * data, or <code>null</code> if this field uses impl data that * should be cached across instances * @see OpenJPAStateManager#setImplData(int,Object) */ public Boolean usesImplData() { return _implData; } /** * Whether this field uses impl data in conjunction with standard * field data when acting on a {@link OpenJPAStateManager}. * * @see OpenJPAStateManager#setImplData(int,Object) * @see #usesImplData */ public void setUsesImplData(Boolean implData) { _implData = implData; _owner.clearExtraFieldDataTable(); } /** * The orderings for this field to be applied on load, or empty array. */ public Order[] getOrders() { if (_orders == null) { if (_orderDec == null) _orders = getRepository().EMPTY_ORDERS; else { String[] decs = StringUtil.split(_orderDec, ",", 0); Order[] orders = getRepository().newOrderArray(decs.length); int spc; boolean asc; for (int i = 0; i < decs.length; i++) { decs[i] = decs[i].trim(); spc = decs[i].indexOf(' '); if (spc == -1) asc = true; else { asc = decs[i].substring(spc + 1).trim(). toLowerCase().startsWith("asc"); decs[i] = decs[i].substring(0, spc); } orders[i] = getRepository().newOrder(this, decs[i], asc); //set "isUsedInOrderBy" to the field ClassMetaData elemCls = getElement() .getDeclaredTypeMetaData(); if (elemCls != null) { FieldMetaData fmd = elemCls.getDeclaredField(decs[i]); if (fmd != null) fmd.setUsedInOrderBy(true); } } _orders = orders; } } return _orders; } /** * The orderings for this field to be applied on load. */ public void setOrders(Order[] orders) { _orderDec = null; _orders = orders; } /** * String declaring the orderings for this field to be applied on load, * or null. The string is of the form:<br /> * <code>orderable[ asc|desc][, ...]</code><br /> * The orderable <code>#element</code> is used to denote the value of * the field's elements. */ public String getOrderDeclaration() { if (_orderDec == null && _orders != null) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < _orders.length; i++) { if (i > 0) buf.append(", "); buf.append(_orders[i].getName()).append(" "); buf.append((_orders[i].isAscending()) ? "asc" : "desc"); } _orderDec = buf.toString(); } return _orderDec; } /** * String declaring the orderings for this field to be applied on load, * or null. The string is of the form:<br /> * <code>orderable[ asc|desc][, ...]</code><br /> * The orderable <code>#element</code> is used to denote the value of * the field's elements. */ public void setOrderDeclaration(String dec) { _orderDec = StringUtil.trimToNull(dec); _orders = null; } /** * Order this field value when it is loaded. */ public Object order(Object val) { if (val == null) return null; Order[] orders = getOrders(); if (orders.length == 0) return val; // create a comparator for the elements of the value Comparator<?> comp; if (orders.length == 1) comp = orders[0].getComparator(); else { List<Comparator<?>> comps = null; Comparator<?> curComp; for (int i = 0; i < orders.length; i++) { curComp = orders[i].getComparator(); if (curComp != null) { if (comps == null) comps = new ArrayList<Comparator<?>>(orders.length); if (i != comps.size()) throw new MetaDataException(_loc.get ("mixed-inmem-ordering", this)); comps.add(curComp); } } if (comps == null) comp = null; else comp = new ComparatorChain(comps); } if (comp == null) return val; // sort switch (getTypeCode()) { case JavaTypes.ARRAY: List l = JavaTypes.toList(val, _elem.getType(), true); Collections.sort(l, (Comparator<? super Order>) comp); return JavaTypes.toArray(l, _elem.getType()); case JavaTypes.COLLECTION: if (val instanceof List) Collections.sort((List) val, (Comparator<? super Order>) comp); return val; default: throw new MetaDataException(_loc.get("cant-order", this)); } } /** * Whether the field is externalized. */ public boolean isExternalized() { return getExternalizerMethod() != null || getExternalValueMap() != null; } /** * Convert the given field value to its external value through the * provided externalizer, or return the value as-is if no externalizer. */ public Object getExternalValue(Object val, StoreContext ctx) { Map extValues = getExternalValueMap(); if (extValues != null) { Object foundVal = extValues.get(val); if (foundVal == null) { throw new UserException(_loc.get("bad-externalized-value", new Object[] { val, extValues.keySet(), this })) .setFatal(true).setFailedObject(val); } else { return foundVal; } } Method externalizer = getExternalizerMethod(); if (externalizer == null) return val; // special case for queries: allow the given value to pass through // as-is if it is already in externalized form if (val != null && getType().isInstance(val) && (!getDeclaredType().isInstance(val) || getDeclaredType() == Object.class)) return val; try { // either invoke the static toExternal(val[, ctx]) method, or the // non-static val.toExternal([ctx]) method if (Modifier.isStatic(externalizer.getModifiers())) { if (externalizer.getParameterTypes().length == 1) return externalizer.invoke(null, new Object[]{ val }); return externalizer.invoke(null, new Object[]{ val, ctx }); } if (val == null) return null; if (externalizer.getParameterTypes().length == 0) return externalizer.invoke(val, (Object[]) null); return externalizer.invoke(val, new Object[]{ ctx }); } catch (OpenJPAException ke) { throw ke; } catch (Exception e) { throw new MetaDataException(_loc.get("externalizer-err", this, Exceptions.toString(val), e.toString())).setCause(e); } } /** * Return the result of passing the given external value through the * factory to get the field value. If no factory is present, * the given value is returned as-is. */ public Object getFieldValue(Object val, StoreContext ctx) { Map fieldValues = getFieldValueMap(); if (fieldValues != null) return fieldValues.get(val); Member factory = getFactoryMethod(); if (factory == null) return val; try { if (val == null && getNullValue() == NULL_DEFAULT) return AccessController.doPrivileged( J2DoPrivHelper.newInstanceAction(getDeclaredType())); // invoke either the constructor for the field type, // or the static type.toField(val[, ctx]) method if (factory instanceof Constructor) { if (val == null) return null; return ((Constructor) factory).newInstance (new Object[]{ val }); } Method meth = (Method) factory; if (meth.getParameterTypes().length == 1) return meth.invoke(null, new Object[]{ val }); return meth.invoke(null, new Object[]{ val, ctx }); } catch (Exception e) { // unwrap cause if (e instanceof InvocationTargetException) { Throwable t = ((InvocationTargetException) e). getTargetException(); if (t instanceof Error) throw (Error) t; e = (Exception) t; // allow null values to cause NPEs and illegal arg exceptions // without error if (val == null && (e instanceof NullPointerException || e instanceof IllegalArgumentException)) return null; } if (e instanceof OpenJPAException) throw (OpenJPAException) e; if (e instanceof PrivilegedActionException) e = ((PrivilegedActionException) e).getException(); throw new MetaDataException(_loc.get("factory-err", this, Exceptions.toString(val), e.toString())).setCause(e); } } /** * The name of this field's externalizer, or null if none. */ public String getExternalizer() { return _extName; } /** * The name of this field's externalizer, or null if none. */ public void setExternalizer(String externalizer) { _extName = externalizer; _extMethod = DEFAULT_METHOD; } /** * The name of this field's factory, or null if none. */ public String getFactory() { return _factName; } /** * The name of this field's factory, or null if none. */ public void setFactory(String factory) { _factName = factory; _factMethod = DEFAULT_METHOD; } /** * Properties string mapping field values to external values. */ public String getExternalValues() { return _extString; } /** * Properties string mapping field values to external values. */ public void setExternalValues(String values) { _extString = values; _extValues = null; } /** * Return the mapping of field values to external values. */ public Map getExternalValueMap() { parseExternalValues(); return _extValues; } /** * Return the mapping of external values to field values. */ public Map getFieldValueMap() { parseExternalValues(); return _fieldValues; } /** * Parse external values into maps. */ private void parseExternalValues() { if (_extValues != Collections.EMPTY_MAP && _fieldValues != Collections.EMPTY_MAP) return; if (_extString == null) { _extValues = null; _fieldValues = null; return; } // parse string into options; this takes care of proper trimming etc Options values = Configurations.parseProperties(_extString); if (values.isEmpty()) throw new MetaDataException(_loc.get("no-external-values", this, _extString)); Map extValues = new HashMap((int) (values.size() * 1.33 + 1)); Map fieldValues = new HashMap((int) (values.size() * 1.33 + 1)); Map.Entry entry; Object extValue, fieldValue; for (Iterator itr = values.entrySet().iterator(); itr.hasNext();) { entry = (Map.Entry) itr.next(); fieldValue = transform((String) entry.getKey(), getDeclaredTypeCode()); extValue = transform((String) entry.getValue(), getTypeCode()); extValues.put(fieldValue, extValue); fieldValues.put(extValue, fieldValue); } _extValues = extValues; _fieldValues = fieldValues; } /** * Return the string value converted to the given type code. The string * must be non-null and trimmed. */ private Object transform(String val, int typeCode) { if ("null".equals(val)) return null; switch (typeCode) { case JavaTypes.BOOLEAN: case JavaTypes.BOOLEAN_OBJ: return Boolean.valueOf(val); case JavaTypes.BYTE: case JavaTypes.BYTE_OBJ: return Byte.valueOf(val); case JavaTypes.INT: case JavaTypes.INT_OBJ: return Integer.valueOf(val); case JavaTypes.LONG: case JavaTypes.LONG_OBJ: return Long.valueOf(val); case JavaTypes.SHORT: case JavaTypes.SHORT_OBJ: return Short.valueOf(val); case JavaTypes.DOUBLE: case JavaTypes.DOUBLE_OBJ: return Double.valueOf(val); case JavaTypes.FLOAT: case JavaTypes.FLOAT_OBJ: return Float.valueOf(val); case JavaTypes.CHAR: case JavaTypes.CHAR_OBJ: return Character.valueOf(val.charAt(0)); case JavaTypes.STRING: return val; case JavaTypes.ENUM: return Enum.valueOf((Class<? extends Enum>)getDeclaredType(), val); } throw new MetaDataException(_loc.get("bad-external-type", this)); } /** * The externalizer method. */ public Method getExternalizerMethod() { if (_manage != MANAGE_PERSISTENT) return null; if (_extMethod == DEFAULT_METHOD) { if (_extName != null) { _extMethod = findMethod(_extName); if (_extMethod == null) throw new MetaDataException(_loc.get("bad-externalizer", this, _extName)); } else _extMethod = null; } return _extMethod; } /** * The factory method or constructor. */ public Member getFactoryMethod() { if (_manage != MANAGE_PERSISTENT) return null; if (_factMethod == DEFAULT_METHOD) { if (getExternalizerMethod() == null) _factMethod = null; else { try { if (_factName == null) _factMethod = getDeclaredType().getConstructor (new Class[]{ getType() }); else _factMethod = findMethodByNameAndType(_factName, getType()); } catch (OpenJPAException ke) { throw ke; } catch (Exception e) { } if (!(_factMethod instanceof Constructor) && !(_factMethod instanceof Method)) throw new MetaDataException(_loc.get("bad-factory", this)); } } return _factMethod; } /** * Find the method for the specified name. Possible forms are: * <ul> * <li>toExternalString</li> * <li>MyFactoryClass.toExternalString</li> * <li>com.company.MyFactoryClass.toExternalString</li> * </ul> * * @param method the name of the method to locate * @return the method for invocation */ private Method findMethod(String method) { return findMethodByNameAndType(method, null); } /** * Find the method for the specified name and type. Possible forms are: * <ul> * <li>toExternalString</li> * <li>MyFactoryClass.toExternalString</li> * <li>com.company.MyFactoryClass.toExternalString</li> * </ul> * * @param method the name of the method to locate * @param type The type of the parameter which will pass the object from the database. * @return the method for invocation */ private Method findMethodByNameAndType(String method, Class<?> type) { if (StringUtil.isEmpty(method)) return null; // get class name and get package name divide on the last '.', so the // names don't apply in this case, but the methods do what we want String methodName = ClassUtil.getClassName(method); String clsName = ClassUtil.getPackageName(method); Class<?> cls = null; Class<?> owner = _owner.getDescribedType(); if (clsName.length() == 0) cls = getDeclaredType(); else if (clsName.equals(owner.getName()) || clsName.equals(ClassUtil.getClassName(owner))) cls = owner; else cls = JavaTypes.classForName(clsName, this); // find the named method Method[] methods = cls.getMethods(); Class<?>[] params; for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(methodName)) { params = methods[i].getParameterTypes(); // static factory methods require one argument or one argument // plus a context; non-static methods require zero arguments or // just a context if (Modifier.isStatic(methods[i].getModifiers()) && (params.length == 1 || (params.length == 2 && isStoreContextParameter(params[1])))) if (type == null) { return methods[i]; } else if (isConvertibleToByMethodInvocationConversion(type, params[0])) { return methods[i]; } if (!Modifier.isStatic(methods[i].getModifiers()) && (params.length == 0 || (params.length == 1 && isStoreContextParameter(params[0])))) return methods[i]; } } return null; } /** * Test if the {@code sourceType} is convertible to the {@code destType}. * Convertible follows the rules in Java Language Specification, 3rd Ed, s5.3 and means that: * <ul> * <li>{@code sourceType} and {@code destType} are the same type (identity conversion)</li> * <li>For primitive types: that {@code sourceType} can be widened into {@code destType} * or that {@code sourceType} can be boxed into a class assignable to {@code destType}.</li> * <li>For non-primitive types: that the {@code sourceType} can be unboxed into a primitive * that is the same as, or can be widened into, * {@code destType} or {@code sourceType} can be assigned to {@code destType}.</li> * * @return True iff the conditions above are true. */ private boolean isConvertibleToByMethodInvocationConversion(Class<?> sourceType, Class<?> destType) { // Note that class.isAssignableFrom is a widening reference conversion test if (sourceType.isPrimitive()) { return isConvertibleToByIdentityPrimitiveConversion(sourceType, destType) || isConvertibleToByWideningPrimitive(sourceType, destType) || destType.isAssignableFrom(box(sourceType)); } else { // Note that unbox will return null if the sourceType is not a wrapper. // The identity primitive conversion and widening primitive handle this. return isConvertibleToByIdentityPrimitiveConversion(unbox(sourceType), destType) || isConvertibleToByWideningPrimitive(unbox(sourceType), destType) || destType.isAssignableFrom(sourceType); } } /** * @return The results of unboxing {@code sourceType} following Java Language Specification, 3rd Ed, s5.1.8 */ private Class<?> unbox(Class<?> sourceType) { if (sourceType == java.lang.Boolean.class) { return java.lang.Boolean.TYPE; } else if (sourceType == java.lang.Byte.class) { return java.lang.Byte.TYPE; } else if (sourceType == java.lang.Short.class) { return java.lang.Short.TYPE; } else if (sourceType == java.lang.Character.class) { return java.lang.Character.TYPE; } else if (sourceType == java.lang.Integer.class) { return java.lang.Integer.TYPE; } else if (sourceType == java.lang.Long.class) { return java.lang.Long.TYPE; } else if (sourceType == java.lang.Float.class) { return java.lang.Float.TYPE; } else if (sourceType == java.lang.Double.class) { return java.lang.Double.TYPE; } else { return null; } } /** * @return The results of unboxing {@code sourceType} following Java Language Specification, 3rd Ed, s5.1.7 */ private Class<?> box(Class<?> sourceType) { if (sourceType.isPrimitive()) { if (sourceType == java.lang.Boolean.TYPE) { return java.lang.Boolean.class; } else if (sourceType == java.lang.Byte.TYPE) { return java.lang.Byte.class; } else if (sourceType == java.lang.Short.TYPE) { return java.lang.Short.class; } else if (sourceType == java.lang.Character.TYPE) { return java.lang.Character.class; } else if (sourceType == java.lang.Integer.TYPE) { return java.lang.Integer.class; } else if (sourceType == java.lang.Long.TYPE) { return java.lang.Long.class; } else if (sourceType == java.lang.Float.TYPE) { return java.lang.Float.class; } else if (sourceType == java.lang.Double.TYPE) { return java.lang.Double.class; } return null; // Should never be reached because all primitives are accounted for above. } else { throw new IllegalArgumentException("Cannot box a type that is not a primitive."); } } /** * @return true if {@code sourceType} can be converted by a widening primitive conversion * following Java Language Specification, 3rd Ed, s5.1.2 */ private boolean isConvertibleToByWideningPrimitive(Class<?> sourceType, Class<?> destType) { // Widening conversion following Java Language Specification, s5.1.2. if (sourceType == java.lang.Byte.TYPE) { return destType == java.lang.Short.TYPE || destType == java.lang.Integer.TYPE || destType == java.lang.Long.TYPE || destType == java.lang.Float.TYPE || destType == java.lang.Double.TYPE; } else if (sourceType == java.lang.Short.TYPE) { return destType == java.lang.Integer.TYPE || destType == java.lang.Long.TYPE || destType == java.lang.Float.TYPE || destType == java.lang.Double.TYPE; } else if (sourceType == java.lang.Character.TYPE) { return destType == java.lang.Integer.TYPE || destType == java.lang.Long.TYPE || destType == java.lang.Float.TYPE || destType == java.lang.Double.TYPE; } else if (sourceType == java.lang.Integer.TYPE) { return destType == java.lang.Long.TYPE || destType == java.lang.Float.TYPE || destType == java.lang.Double.TYPE; } else if (sourceType == java.lang.Long.TYPE) { return destType == java.lang.Float.TYPE || destType == java.lang.Double.TYPE; } else if (sourceType == java.lang.Float.TYPE) { return destType == java.lang.Double.TYPE; } return false; } /** * Returns true iff the sourceType is a primitive that can be converted to * destType using an identity conversion - i.e. sourceType and destType are the same type. * following Java Language Specification, 3rd Ed, s5.1.1 */ private boolean isConvertibleToByIdentityPrimitiveConversion(Class<?> sourceType, Class<?> destType) { return sourceType != null && sourceType.isPrimitive() && sourceType == destType; } /** * Return true if the given type is a store context type; we can't * use the standard <code>isAssignableFrom</code> because of classloader * oddness. */ private static boolean isStoreContextParameter(Class<?> type) { return StoreContext.class.getName().equals(type.getName()); } public boolean equals(Object other) { if (other == this) return true; if (!(other instanceof FieldMetaData)) return false; return getFullName(true).equals(((FieldMetaData) other). getFullName(true)); } public int hashCode() { return getFullName(true).hashCode(); } public int compareTo(Object other) { if (other == null) return 1; return getFullName(true).compareTo(((FieldMetaData) other). getFullName(true)); } public String toString() { return getFullName(true); } //////////////////////// // Resolve and validate //////////////////////// /** * Resolve mode for this field. */ public int getResolve() { return _resMode; } /** * Resolve mode for this field. */ public void setResolve(int mode) { _resMode = mode; } /** * Resolve mode for this field. */ public void setResolve(int mode, boolean on) { if (mode == MODE_NONE) _resMode = mode; else if (on) _resMode |= mode; else _resMode &= ~mode; } /** * Resolve and validate metadata. Return true if already resolved. */ public boolean resolve(int mode) { if ((_resMode & mode) == mode) return true; int cur = _resMode; _resMode |= mode; Log log = getRepository().getLog(); if (log.isTraceEnabled()) log.trace(_loc.get("resolve-field", _owner + "@" + System.identityHashCode(_owner) + "." + _name)); // we only perform actions for metadata mode if ((mode & MODE_META) == 0 || (cur & MODE_META) != 0) return false; Method externalizer = getExternalizerMethod(); if (externalizer != null) setType(externalizer.getReturnType()); // only pass on metadata resolve mode so that metadata is always // resolved before any other resolve modes our subclasses pass along _val.resolve(MODE_META); _key.resolve(MODE_META); _elem.resolve(MODE_META); MetaDataRepository repos = getRepository(); int validate = repos.getValidate(); if ((validate & MetaDataRepository.VALIDATE_META) != 0 && (!ImplHelper.isManagedType(repos.getConfiguration(), _owner.getDescribedType()) || (validate & MetaDataRepository.VALIDATE_UNENHANCED) == 0)) { validateLRS(); if ((validate & MetaDataRepository.VALIDATE_RUNTIME) == 0) validateSupportedType(); validateValue(); validateExtensionKeys(); } return false; } /** * Validate that this field can be used for LRS. */ private void validateLRS() { if (!isLRS()) return; // can't use lrs for arrays if (getTypeCode() == JavaTypes.ARRAY) throw new MetaDataException(_loc.get("bad-lrs-array", this)); // can't use lrs for externalized vals if (getExternalizerMethod() != null) throw new MetaDataException(_loc.get("bad-lrs-extern", this)); // can't use lrs for concrete types if (getType() != Collection.class && getType() != Map.class && getType() != Set.class) throw new MetaDataException(_loc.get("bad-lrs-concrete", this)); } /** * Validate that this field is supported by the runtime. */ private void validateSupportedType() { // log warnings about things we don't handle OpenJPAConfiguration conf = getRepository().getConfiguration(); Collection<String> opts = conf.supportedOptions(); Log log = conf.getLog(OpenJPAConfiguration.LOG_METADATA); switch (getTypeCode()) { case JavaTypes.PC: if (isEmbedded() && !opts.contains( OpenJPAConfiguration.OPTION_EMBEDDED_RELATION)) { setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed", this)); } else if (isEmbedded() && getDeclaredTypeCode() != JavaTypes.PC) { setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed-extern", this)); } break; case JavaTypes.COLLECTION: if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_COLLECTION)) throw new UnsupportedException( _loc.get("type-not-supported", "Collection", this)); if (_elem.isEmbeddedPC() && !opts.contains( OpenJPAConfiguration.OPTION_EMBEDDED_COLLECTION_RELATION)){ _elem.setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed-element", this)); } break; case JavaTypes.ARRAY: if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_ARRAY)) throw new UnsupportedException( _loc.get("type-not-supported", "Array", this)); if (_elem.isEmbeddedPC() && !opts.contains( OpenJPAConfiguration.OPTION_EMBEDDED_COLLECTION_RELATION)) { _elem.setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed-element", this)); } break; case JavaTypes.MAP: if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_MAP)) throw new UnsupportedException( _loc.get("type-not-supported", "Map", this)); if (_elem.isEmbeddedPC() && !opts.contains( OpenJPAConfiguration.OPTION_EMBEDDED_MAP_RELATION)) { _elem.setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed-element", this)); } if (_key.isEmbeddedPC() && !opts.contains( OpenJPAConfiguration.OPTION_EMBEDDED_MAP_RELATION)) { _key.setEmbedded(false); if (log.isWarnEnabled()) log.warn(_loc.get("cant-embed-key", this)); } break; } } /** * Validate our value strategy. */ private void validateValue() { if (getExternalizerMethod() != null && getExternalValueMap() != null) throw new MetaDataException(_loc.get("extern-externvalues", this)); if (getValueStrategy() == ValueStrategies.SEQUENCE && getValueSequenceName() == null) throw new MetaDataException(_loc.get("no-seq-name", this)); ValueStrategies.assertSupported(getValueStrategy(), this, "value strategy"); } /** * Copy state from the given field to this one. Do not copy mapping * information. */ public void copy(FieldMetaData field) { super.copy(field); _intermediate = field.usesIntermediate(); _implData = field.usesImplData(); // copy field-level info; use get methods to force resolution of // lazy data _proxyClass = field.getProxyType(); _initializer = field.getInitializer(); _transient = field.isTransient(); _nullValue = field.getNullValue(); _manage = field.getManagement(); _explicit = field.isExplicit(); _extName = field.getExternalizer(); _extMethod = DEFAULT_METHOD; _factName = field.getFactory(); _factMethod = DEFAULT_METHOD; _extString = field.getExternalValues(); _extValues = Collections.EMPTY_MAP; _fieldValues = Collections.EMPTY_MAP; _primKey = field.isPrimaryKey(); _backingMember = field._backingMember; _enumField = field._enumField; _lobField = field._lobField; _serializableField = field._serializableField; _generated = field._generated; _mappedByIdValue = field._mappedByIdValue; _isElementCollection = field._isElementCollection; _access = field._access; _orderDec = field._orderDec; _useSchemaElement = field._useSchemaElement; // embedded fields can't be versions if (_owner.getEmbeddingMetaData() == null && _version == null) _version = (field.isVersion()) ? Boolean.TRUE : Boolean.FALSE; // only copy this data if not already set explicitly in this instance if (_dfg == 0) { _dfg = (field.isInDefaultFetchGroup()) ? DFG_TRUE : DFG_FALSE; if (field.isDefaultFetchGroupExplicit()) _dfg |= DFG_EXPLICIT; } if (_fgSet == null && field._fgSet != null) _fgSet = new HashSet(field._fgSet); if (_lfg == null) _lfg = field.getLoadFetchGroup(); if (_lrs == null) _lrs = (field.isLRS()) ? Boolean.TRUE : Boolean.FALSE; if (_valStrategy == -1) _valStrategy = field.getValueStrategy(); if (_upStrategy == -1) _upStrategy = field.getUpdateStrategy(); if (ClassMetaData.DEFAULT_STRING.equals(_seqName)) { _seqName = field.getValueSequenceName(); _seqMeta = null; } if (ClassMetaData.DEFAULT_STRING.equals(_inverse)) _inverse = field.getInverse(); // copy value metadata _val.copy(field); _key.copy(field.getKey()); _elem.copy(field.getElement()); } protected void addExtensionKeys(Collection exts) { getRepository().getMetaDataFactory().addFieldExtensionKeys(exts); } /////////////// // Commentable /////////////// public String[] getComments() { return (_comments == null) ? EMPTY_COMMENTS : _comments; } public void setComments(String[] comments) { _comments = comments; } //////////////////////////////// // ValueMetaData implementation //////////////////////////////// public FieldMetaData getFieldMetaData() { return this; } public Class getType() { return _val.getType(); } public void setType(Class type) { _val.setType(type); if (type.isArray()) _elem.setType(type.getComponentType()); else if (type == Properties.class) { _key.setType(String.class); _elem.setType(String.class); } } public int getTypeCode() { return _val.getTypeCode(); } public void setTypeCode(int code) { _val.setTypeCode(code); } public boolean isTypePC() { return _val.isTypePC(); } public ClassMetaData getTypeMetaData() { return _val.getTypeMetaData(); } public Class getDeclaredType() { return _val.getDeclaredType(); } public void setDeclaredType(Class type) { _val.setDeclaredType(type); if (type.isArray()) _elem.setDeclaredType(type.getComponentType()); else if (type == Properties.class) { _key.setDeclaredType(String.class); _elem.setDeclaredType(String.class); } } public int getDeclaredTypeCode() { return _val.getDeclaredTypeCode(); } public void setDeclaredTypeCode(int type) { _val.setDeclaredTypeCode(type); } public boolean isDeclaredTypePC() { return _val.isDeclaredTypePC(); } public ClassMetaData getDeclaredTypeMetaData() { return _val.getDeclaredTypeMetaData(); } public boolean isEmbedded() { return _val.isEmbedded(); } public void setEmbedded(boolean embedded) { _val.setEmbedded(embedded); } public boolean isEmbeddedPC() { return _val.isEmbeddedPC(); } public ClassMetaData getEmbeddedMetaData() { return _val.getEmbeddedMetaData(); } public ClassMetaData addEmbeddedMetaData(int access) { return _val.addEmbeddedMetaData(access); } public ClassMetaData addEmbeddedMetaData() { return _val.addEmbeddedMetaData(); } public int getCascadeDelete() { return _val.getCascadeDelete(); } public void setCascadeDelete(int delete) { _val.setCascadeDelete(delete); } public int getCascadePersist() { return _val.getCascadePersist(); } public void setCascadePersist(int persist) { _val.setCascadePersist(persist); } public void setCascadePersist(int cascade, boolean checkPUDefault) { _val.setCascadePersist(cascade, checkPUDefault); } public int getCascadeAttach() { return _val.getCascadeAttach(); } public void setCascadeAttach(int attach) { _val.setCascadeAttach(attach); } public int getCascadeDetach() { return _val.getCascadeDetach(); } public void setCascadeDetach(int detach) { _val.setCascadeDetach(detach); } public int getCascadeRefresh() { return _val.getCascadeRefresh(); } public void setCascadeRefresh(int refresh) { _val.setCascadeRefresh(refresh); } public boolean isSerialized() { return _val.isSerialized(); } public void setSerialized(boolean serialized) { _val.setSerialized(serialized); } public String getValueMappedBy() { return _val.getValueMappedBy(); } public void setValueMappedBy(String mapped) { _val.setValueMappedBy(mapped); } public FieldMetaData getValueMappedByMetaData () { return _val.getValueMappedByMetaData (); } public Class<?> getTypeOverride () { return _val.getTypeOverride (); } public void setTypeOverride(Class type) { _val.setTypeOverride (type); } public void copy (ValueMetaData vmd) { _val.copy (vmd); } /** * Check if this field is used by other field as "order by" value. * * @since 1.1.0 */ public boolean isUsedInOrderBy() { return _usedInOrderBy; } /** * Whether this field is used by other field as "order by" value . * * @since 1.1.0 */ public void setUsedInOrderBy(boolean isUsed) { _usedInOrderBy = isUsed; } /** * Serializable wrapper around a {@link Method} or {@link Field}. For * space considerations, this does not support {@link Constructor}s. */ public static class MemberProvider implements Externalizable { private transient Member _member; public MemberProvider() { // for externalization } MemberProvider(Member member) { if (member instanceof Constructor) throw new IllegalArgumentException(); _member = member; } public Member getMember() { return _member; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { boolean isField = in.readBoolean(); Class<?> cls = (Class<?>) in.readObject(); String memberName = (String) in.readObject(); try { if (isField) _member = AccessController.doPrivileged( J2DoPrivHelper.getDeclaredFieldAction( cls, memberName)); else { Class<?>[] parameterTypes = (Class[]) in.readObject(); _member = AccessController.doPrivileged( J2DoPrivHelper.getDeclaredMethodAction( cls, memberName, parameterTypes)); } } catch (SecurityException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } catch (PrivilegedActionException pae) { IOException ioe = new IOException( pae.getException().getMessage()); ioe.initCause(pae); throw ioe; } } public void writeExternal(ObjectOutput out) throws IOException { boolean isField = _member instanceof Field; out.writeBoolean(isField); out.writeObject(_member.getDeclaringClass()); out.writeObject(_member.getName()); if (!isField) out.writeObject(((Method) _member).getParameterTypes()); } } public boolean isValueGenerated() { return _generated; } public void setValueGenerated(boolean generated) { this._generated = generated; } public boolean isElementCollection() { return _isElementCollection; } public void setElementCollection(boolean isElementCollection) { this._isElementCollection = isElementCollection; } public String getMappedByIdValue() { return _mappedByIdValue; } public void setMappedByIdValue(String mappedByIdValue) { this._mappedByIdValue = mappedByIdValue; } public boolean isMappedById() { return (_mappedByIdValue != null); } /** * Gets the access type used by this field. If no access type is set for * this field then return the access type used by the declaring class. */ public int getAccessType() { if (AccessCode.isUnknown(_access)) { int fCode = AccessCode.toFieldCode(getDeclaringMetaData() .getAccessType()); return fCode; } return _access; } /** * Sets access type of this field. The access code is verified for validity * as well as against the access style used by the declaring class. */ public void setAccessType(int fCode) { ClassMetaData owner = getDeclaringMetaData(); owner.mergeFieldAccess(this, fCode); _access = fCode; } public int getAssociationType() { return _associationType; } public void setAssociationType(int type) { _associationType = type; } public boolean isPersistentCollection() { return _persistentCollection; } public void setPersistentCollection(boolean persistentCollection) { _persistentCollection = persistentCollection; } private Class<?> _relationType = Unknown.class; public Class<?> getRelationType() { if (_relationType == Unknown.class) { if (isDeclaredTypePC()) _relationType = getDeclaredType(); else if (getElement().isDeclaredTypePC()) _relationType = getElement().getDeclaredType(); else if (getKey().isDeclaredTypePC()) _relationType = getKey().getDeclaredType(); else _relationType = null; } return _relationType; } private class Unknown{}; public boolean isDelayCapable() { if (_delayCapable != null) { return _delayCapable.booleanValue(); } if (getTypeCode() != JavaTypes.COLLECTION || isLRS()) { _delayCapable = Boolean.FALSE; return _delayCapable; } else { // Verify the proxy manager is configured to handle delay loading ProxyManager pm = getRepository().getConfiguration().getProxyManagerInstance(); if (pm != null) { _delayCapable = pm.getDelayCollectionLoading(); } else { _delayCapable = Boolean.FALSE; } } return _delayCapable; } public void setDelayCapable(Boolean delayCapable) { _delayCapable = delayCapable; } /** * Whether to include schema name in generated files */ public boolean getUseSchemaElement() { return _useSchemaElement; } /** * Whether to include schema name in generated files */ public void setUseSchemaElement(boolean _useSchemaElement) { this._useSchemaElement = _useSchemaElement; } public String getSetterName() { String setterName = "set" + StringUtil.capitalize(_name); if (_name.length() > 1 && Character.isLowerCase(_name.charAt(0)) && Character.isUpperCase(_name.charAt(1))) { // We have the special case where the first char is lower, and the // following char is capital. We need to support using the // setaStart() (correct) and setAStart() (incorrect -- old way) Class<?> type = getDeclaringMetaData().getDescribedType(); setterName = "set" + _name; try { type.getDeclaredMethod(setterName, getType()); return setterName; } catch (Exception e) { } setterName = "set" + StringUtil.capitalize(_name); try { type.getDeclaredMethod(setterName, getType()); } catch (Exception e) { } } return setterName; } }