blob: 1dead9b0bfd862ebc4c0a396f3cb18bf4b7649f8 [file] [log] [blame]
/*
* 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.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.MetaDataException;
/**
* Captures fetch group meta-data.
*
* Fetch Group is identified and referred by its immutable name.
* Fetch Group can nest other groups. The nested group reference is the name of the nested group.
*
* Defines two <em>standard</em> fetch group named <tt>default</tt> and <tt>all</tt>.
*/
public class FetchGroup
implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Name of the default fetch group.
*/
public static final String NAME_DEFAULT = "default";
/**
* Name of the "all" fetch group.
*/
public static final String NAME_ALL = "all";
/**
* Default field recursion depth.
*/
public static final int RECURSION_DEPTH_DEFAULT = 1;
/**
* Infinite depth.
*/
public static final int DEPTH_INFINITE = -1;
/**
* Standard default fetch group.
*/
static final FetchGroup DEFAULT = new FetchGroup(NAME_DEFAULT, true);
/**
* Standard "all" fetch group.
*/
static final FetchGroup ALL = new FetchGroup(NAME_ALL, false);
private static final FieldMetaData[] EMPTY_FIELD_ARRAY = {};
private static final Localizer _loc = Localizer.forPackage(FetchGroup.class);
private final String _name;
private final ClassMetaData _meta;
private final boolean _readOnly;
private List<String> _includes;
private Set<String> _containedBy;
private Map<FieldMetaData,Number> _depths;
private Boolean _postLoad;
/**
* Constructor; supply immutable name.
*
* @param cm class meta data that owns this group. Can be null for standard groups.
* @param name must not by null or empty.
*/
FetchGroup(ClassMetaData cm, String name) {
_meta = cm;
_name = name;
_readOnly = false;
}
/**
* Internal constructor for built-in fetch groups.
*/
private FetchGroup(String name, boolean postLoad) {
_meta = null;
_name = name;
_postLoad = (postLoad) ? Boolean.TRUE : Boolean.FALSE;
_readOnly = true;
}
/**
* Copy state from the given fetch group.
*/
void copy(FetchGroup fg) {
if (fg._includes != null) {
for (String included : fg._includes) {
addDeclaredInclude(included);
}
}
if (fg._containedBy != null) {
this._containedBy = new HashSet<>(fg._containedBy);
}
if (fg._depths != null) {
for (Map.Entry<FieldMetaData,Number> entry : fg._depths.entrySet()) {
setRecursionDepth(entry.getKey(), entry.getValue().intValue());
}
}
if (fg._postLoad != null) {
_postLoad = fg._postLoad;
}
}
/**
* Fetch group name.
*/
public String getName() {
return _name;
}
/**
* Includes given fetch group within this receiver.
*/
public void addDeclaredInclude(String fgName) {
if (_readOnly)
throw new UnsupportedOperationException();
if (StringUtil.isEmpty(fgName))
throw new MetaDataException(_loc.get("null-include-fg", this));
if (_includes == null)
_includes = new ArrayList<>();
if (!_includes.contains(fgName))
_includes.add(fgName);
}
/**
* Affirms if given fetch group is included by this receiver. Includes
* superclass definition of fetch group and optionally other included
* groups.
*
* @param recurse if true then recursively checks within the included
* fecth groups
*/
public boolean includes(String fgName, boolean recurse) {
// check our includes
if (_includes != null) {
if (_includes.contains(fgName))
return true;
if (recurse && _meta !=null) {
FetchGroup fg;
for (String included : _includes) {
fg = _meta.getFetchGroup(included);
if (fg != null && fg.includes(fgName, true)) {
return true;
}
}
}
}
if (_meta != null) {
// check superclass includes
ClassMetaData sup = _meta.getPCSuperclassMetaData();
if (sup != null) {
FetchGroup supFG = sup.getFetchGroup(_name);
if (supFG != null)
return supFG.includes(fgName, recurse);
}
}
return false;
}
/**
* Adds this receiver as one of the included fetch groups of the given
* parent.
* The parent fetch group will include this receiver as a side-effect of
* this call.
*
* @see #includes(String, boolean)
* @see #addDeclaredInclude(String)
*
* @return true if given parent is a new addition. false othrwise.
* @since 1.1.0
*/
public boolean addContainedBy(FetchGroup parent) {
parent.addDeclaredInclude(this.getName());
if (_containedBy==null)
_containedBy = new HashSet<>();
return _containedBy.add(parent.getName());
}
/**
* Gets the name of the fetch groups in which this receiver has been
* included.
*
* @see #addContainedBy(FetchGroup)
* @since 1.1.0
*/
public Set<String> getContainedBy() {
if (_containedBy == null)
return Collections.emptySet();
return Collections.unmodifiableSet(_containedBy);
}
/**
* Return the fetch group names declared included by this group.
*/
public String[] getDeclaredIncludes() {
// only used during serialization; no need to cache
return (_includes == null) ? new String[0] : _includes.toArray(new String[_includes.size()]);
}
/**
* Recursion depth for the given field. This is the depth of relations of
* the same class as this one we can fetch through the given field.
*/
public void setRecursionDepth(FieldMetaData fm, int depth) {
if (_readOnly)
throw new UnsupportedOperationException();
if (depth < -1)
throw new MetaDataException(_loc.get("invalid-fg-depth", _name, fm,
depth));
if (_depths == null)
_depths = new HashMap<>();
_depths.put(fm, depth);
}
/**
* Recursion depth for the given field. This is the depth of relations of
* the same class as this one we can fetch through the given field.
*/
public int getRecursionDepth(FieldMetaData fm) {
Number depth = findRecursionDepth(fm);
return (depth == null) ? RECURSION_DEPTH_DEFAULT : depth.intValue();
}
/**
* Return the recursion depth declared for the given field, or
* 0 if none.
*/
public int getDeclaredRecursionDepth(FieldMetaData fm) {
Number depth = (_depths == null) ? null : _depths.get(fm);
return (depth == null) ? 0 : depth.intValue();
}
/**
* Helper to find recursion depth recursively in our includes.
*/
private Number findRecursionDepth(FieldMetaData fm) {
Number depth = (_depths == null) ? null : _depths.get(fm);
if (depth != null)
return depth;
// check for superclass declaration of depth
Number max = null;
if (_meta != null && fm.getDeclaringMetaData() != _meta) {
ClassMetaData sup = _meta.getPCSuperclassMetaData();
if (sup != null) {
FetchGroup supFG = sup.getFetchGroup(_name);
if (supFG != null)
max = supFG.findRecursionDepth(fm);
}
}
if (_includes == null)
return max;
// find largest included depth
FetchGroup fg;
for (String included : _includes) {
fg = _meta.getFetchGroup(included);
depth = (fg == null) ? null : fg.findRecursionDepth(fm);
if (depth != null && (max == null || depth.intValue() > max.intValue()))
max = depth;
}
return max;
}
/**
* Return the fields with declared recursion depths in this group.
*/
public FieldMetaData[] getDeclaredRecursionDepthFields() {
// used in serialization only; no need to cache
if (_depths == null)
return EMPTY_FIELD_ARRAY;
return _depths.keySet().toArray(new FieldMetaData[_depths.size()]);
}
/**
* Whether loading this fetch group causes a post-load callback on the
* loaded instance.
*/
public void setPostLoad (boolean flag) {
if (_readOnly && flag != isPostLoad())
throw new UnsupportedOperationException();
_postLoad = (flag) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Whether loading this fetch group causes a post-load callback on the
* loaded instance.
*/
public boolean isPostLoad () {
if (_postLoad != null)
return _postLoad;
if (_meta != null) {
ClassMetaData sup = _meta.getPCSuperclassMetaData();
if (sup != null) {
FetchGroup supFG = sup.getFetchGroup(_name);
if (supFG != null && supFG.isPostLoad())
return true;
}
}
if (_includes == null)
return false;
FetchGroup fg;
for (String included : _includes) {
fg = _meta.getFetchGroup(included);
if (fg != null && fg.isPostLoad())
return true;
}
return false;
}
/**
* Whether the post-load value is declared for this group.
*/
public boolean isPostLoadExplicit() {
return _postLoad != null;
}
/**
* Resolve and validate fetch group meta-data.
*/
public void resolve() {
if (_includes == null)
return;
// validate includes
FetchGroup fg;
for (String name : _includes) {
if (name.equals(_name))
throw new MetaDataException(_loc.get("cyclic-fg", this, name));
fg = _meta.getFetchGroup(name);
if (fg == null)
throw new MetaDataException(_loc.get("bad-fg-include", this, name));
if (fg.includes(_name, true))
throw new MetaDataException(_loc.get("cyclic-fg", this, name));
}
}
/**
* Affirms equality if the other has the same name and declaring type.
*/
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (!(other instanceof FetchGroup))
return false;
FetchGroup that = (FetchGroup) other;
return _name.equals(that._name)
&& Objects.equals(_meta, that._meta);
}
@Override
public int hashCode() {
return _name.hashCode() + ((_meta == null) ? 0 : _meta.hashCode());
}
@Override
public String toString() {
return ((_meta == null) ? "Builtin" : _meta.toString ()) + "." + _name;
}
}