/*
* Copyright (c) 2015 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.io.appschema.writer;
import static eu.esdihumboldt.hale.common.align.model.functions.JoinFunction.PARAMETER_JOIN;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
import com.google.common.collect.ListMultimap;
import eu.esdihumboldt.hale.common.align.io.EntityResolver;
import eu.esdihumboldt.hale.common.align.io.impl.DefaultEntityResolver;
import eu.esdihumboldt.hale.common.align.io.impl.JaxbAlignmentIO;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.PropertyType;
import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.ChildContext;
import eu.esdihumboldt.hale.common.align.model.Entity;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.align.model.ParameterValue;
import eu.esdihumboldt.hale.common.align.model.Property;
import eu.esdihumboldt.hale.common.align.model.Type;
import eu.esdihumboldt.hale.common.align.model.functions.JoinFunction;
import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter;
import eu.esdihumboldt.hale.common.align.model.functions.join.JoinParameter.JoinCondition;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeIndex;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.Cardinality;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.NillableFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.HasValueFlag;
import eu.esdihumboldt.hale.io.appschema.model.ChainConfiguration;
import eu.esdihumboldt.hale.io.appschema.model.FeatureChaining;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlAttributeFlag;
/**
* Utility method for app-schema mapping generation.
*
* @author Stefano Costa, GeoSolutions
*/
public class AppSchemaMappingUtils {
/**
* Base GML namespace, common to all GML versions.
*/
public static final String GML_BASE_NAMESPACE = "http://www.opengis.net/gml";
/**
* GML identifier tag name.
*/
public static final String GML_ID = "id";
/**
* GML abstract feature type name.
*/
public static final String GML_ABSTRACT_FEATURE_TYPE = "AbstractFeatureType";
/**
* GML abstract geometry type name.
*/
public static final String GML_ABSTRACT_GEOMETRY_TYPE = "AbstractGeometryType";
/**
* GML nil reason type name.
*/
public static final String GML_NIL_REASON_TYPE = "NilReasonType";
/**
* GML nil reason attribute name.
*/
public static final String GML_NIL_REASON = "nilReason";
/**
* xlink:href qualified name.
*/
public static final QName QNAME_XLINK_XREF = new QName("http://www.w3.org/1999/xlink", "href");
/**
* XMLSchema-instance prefix.
*/
public static final String XSI_PREFIX = "xsi";
/**
* XMLSchema-instance URI.
*/
public static final String XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
/**
* xsi:nil qualified name.
*/
public static final QName QNAME_XSI_NIL = new QName(XSI_URI, "nil", XSI_PREFIX);
/**
* HALE INSPIRE extension URI.
*/
public static final String HALE_INSPIRE_EXT_URI = "http://www.esdi-humboldt.eu/hale/inspire/ext";
/**
* Tests whether the provided property definition describes a
* <code>gml:id</code> attribute.
*
* @param propertyDef the property definition
* @return <code>true</code> if <code>properyDef</code> defines a
* <code>gml:id</code> attribute, <code>false</code> otherwise.
*/
public static boolean isGmlId(PropertyDefinition propertyDef) {
if (propertyDef == null) {
return false;
}
QName propertyName = propertyDef.getName();
return hasGmlNamespace(propertyName) && GML_ID.equals(propertyName.getLocalPart());
}
/**
* Tests whether the provided property definition describes a
* <code>nilReason</code> attribute.
*
* <p>
* Returns {@code true} if the property name is {@code nilReason} and the
* property type is either {@code gml:NilReasonType} or the special
* <code>{http://www.esdi-humboldt.eu/hale/inspire/ext}NilReasonType</code>
* .
* </p>
*
* @param propertyDef the property definition
* @return <code>true</code> if <code>properyDef</code> defines a
* <code>nilReason</code> attribute, <code>false</code> otherwise.
*/
public static boolean isNilReason(PropertyDefinition propertyDef) {
if (propertyDef == null) {
return false;
}
QName propertyName = propertyDef.getName();
QName propertyTypeName = propertyDef.getPropertyType().getName();
//
return (hasGmlNamespace(propertyTypeName) || HALE_INSPIRE_EXT_URI.equals(propertyTypeName
.getNamespaceURI()))
&& GML_NIL_REASON_TYPE.equals(propertyTypeName.getLocalPart())
&& GML_NIL_REASON.equals(propertyName.getLocalPart());
}
/**
* Tests whether the provided property definition describes an XML
* attribute.
*
* @param propertyDef the property definition
* @return <code>true</code> if <code>properyDef</code> defines an XML
* attribute, <code>false</code> otherwise.
*/
public static boolean isXmlAttribute(PropertyDefinition propertyDef) {
XmlAttributeFlag xmlAttrFlag = propertyDef.getConstraint(XmlAttributeFlag.class);
return xmlAttrFlag != null && xmlAttrFlag.isEnabled();
}
/**
* Tests whether the provided property definition describes a nillable XML
* element.
*
* @param propertyDef the property definition
* @return <code>true</code> if <code>properyDef</code> is nillable,
* <code>false</code> otherwise.
*/
public static boolean isNillable(PropertyDefinition propertyDef) {
NillableFlag nillableFlag = propertyDef.getConstraint(NillableFlag.class);
return nillableFlag != null && nillableFlag.isEnabled();
}
/**
* Tests whether the provided type definition describes a GML feature type.
*
* @param typeDefinition the type definition
* @return <code>true</code> if <code>typeDefinition</code> defines a GML
* feature type, <code>false</code> otherwise.
*/
public static boolean isFeatureType(TypeDefinition typeDefinition) {
if (typeDefinition == null) {
return false;
}
QName typeName = typeDefinition.getName();
if (hasGmlNamespace(typeName) && GML_ABSTRACT_FEATURE_TYPE.equals(typeName.getLocalPart())) {
return true;
}
else {
return isFeatureType(typeDefinition.getSuperType());
}
}
/**
* Tests whether the provided type definition describes a GML geometry type.
*
* @param typeDefinition the type definition
* @return <code>true</code> if <code>typeDefinition</code> defines a GML
* geometry type, <code>false</code> otherwise.
*/
public static boolean isGeometryType(TypeDefinition typeDefinition) {
if (typeDefinition == null) {
return false;
}
QName typeName = typeDefinition.getName();
if (hasGmlNamespace(typeName) && GML_ABSTRACT_GEOMETRY_TYPE.equals(typeName.getLocalPart())) {
return true;
}
else {
return isGeometryType(typeDefinition.getSuperType());
}
}
private static boolean hasGmlNamespace(QName qname) {
return qname.getNamespaceURI().startsWith(GML_BASE_NAMESPACE);
}
/**
* Tests whether the provided property definition describes a multi-valued
* property.
*
* @param targetPropertyDef the property definition
* @return <code>true</code> if <code>targetPropertyDef</code> defines a
* multi-valued property, <code>false</code> otherwise.
*/
public static boolean isMultiple(PropertyDefinition targetPropertyDef) {
if (targetPropertyDef != null) {
Cardinality cardinality = targetPropertyDef.getConstraint(Cardinality.class);
if (cardinality != null) {
long maxOccurs = cardinality.getMaxOccurs();
if (maxOccurs > 1 || maxOccurs == Cardinality.UNBOUNDED) {
return true;
}
}
}
return false;
}
/**
* Tests whether the provided property definition describes an HREF
* attribute.
*
* @param propertyDef the property definition
* @return <code>true</code> if <code>targetPropertyDef</code> defines an
* HREF attribute, <code>false</code> otherwise.
*/
public static boolean isHRefAttribute(PropertyDefinition propertyDef) {
return propertyDef != null && propertyDef.getName().equals(QNAME_XLINK_XREF);
}
/**
* Determines the closest feature type containing the provided property.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyEntityDef the property definition
* @return the feature type containing the provided property
*/
public static TypeDefinition findOwningFeatureType(PropertyEntityDefinition propertyEntityDef) {
List<ChildContext> propertyPath = propertyEntityDef.getPropertyPath();
return findOwningFeatureType(propertyPath);
}
/**
* Determines the closest feature type containing the provided property.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyPath the property path
* @return the feature type containing the provided property
*/
public static TypeDefinition findOwningFeatureType(List<ChildContext> propertyPath) {
int ftIdx = findOwningFeatureTypeIndex(propertyPath);
if (ftIdx >= 0) {
return propertyPath.get(ftIdx).getChild().getParentType();
}
else {
return null;
}
}
/**
* Determines the path to the closest feature type containing the provided
* property.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyEntityDef the property definition
* @return the path to the feature type containing the provided property
*/
public static List<ChildContext> findOwningFeatureTypePath(
PropertyEntityDefinition propertyEntityDef) {
List<ChildContext> propertyPath = propertyEntityDef.getPropertyPath();
return findOwningFeatureTypePath(propertyPath);
}
/**
* Determines the path to the closest feature type containing the provided
* property.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyPath the property path
* @return the path to the feature type containing the provided property
*/
public static List<ChildContext> findOwningFeatureTypePath(List<ChildContext> propertyPath) {
int ftIdx = findOwningFeatureTypeIndex(propertyPath);
if (ftIdx >= 0) {
return getContainerPropertyPath(propertyPath.subList(0, ftIdx));
}
return Collections.emptyList();
}
/**
* Looks for a feature type among the children of the provided type.
*
* <p>
* NOTE: if more than one children are feature types, only the first one is
* returned.
* </p>
*
* @param typeDef the type definition
* @return the first child of <code>typeDef</code> who is a feature type
*/
public static TypeDefinition findChildFeatureType(TypeDefinition typeDef) {
if (typeDef != null) {
Collection<? extends ChildDefinition<?>> children = typeDef.getChildren();
if (children != null) {
for (ChildDefinition<?> child : children) {
PropertyDefinition childPropertyDef = child.asProperty();
if (childPropertyDef != null) {
TypeDefinition childPropertyType = childPropertyDef.getPropertyType();
if (isFeatureType(childPropertyType)) {
return childPropertyType;
}
}
}
}
}
return null;
}
private static int findOwningFeatureTypeIndex(List<ChildContext> propertyPath) {
for (int i = propertyPath.size() - 1; i >= 0; i--) {
ChildContext childContext = propertyPath.get(i);
TypeDefinition parentType = childContext.getChild().getParentType();
if (isFeatureType(parentType)) {
return i;
}
}
return -1;
}
/**
* Determines which is the closest type containing the specified property,
* among the provided collection of allowed types.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyEntityDef the property definition
* @param allowedTypes the allowed types
* @return the type containing the specified property
*/
public static TypeDefinition findOwningType(PropertyEntityDefinition propertyEntityDef,
Collection<? extends TypeDefinition> allowedTypes) {
List<ChildContext> propertyPath = propertyEntityDef.getPropertyPath();
return findOwningType(propertyPath, allowedTypes);
}
/**
* Determines which is the closest type containing the specified property,
* among the provided collection of allowed types.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyPath the property path
* @param allowedTypes the allowed types
* @return the type containing the specified property
*/
public static TypeDefinition findOwningType(List<ChildContext> propertyPath,
Collection<? extends TypeDefinition> allowedTypes) {
int ftIdx = findOwningTypeIndex(propertyPath, allowedTypes);
if (ftIdx >= 0) {
return propertyPath.get(ftIdx).getChild().getParentType();
}
else {
return null;
}
}
private static int findOwningTypeIndex(List<ChildContext> propertyPath,
Collection<? extends TypeDefinition> allowedTypes) {
for (int i = propertyPath.size() - 1; i >= 0; i--) {
ChildContext childContext = propertyPath.get(i);
TypeDefinition parentType = childContext.getChild().getParentType();
if (allowedTypes.contains(parentType)) {
return i;
}
}
return -1;
}
/**
* Determines the path to the closest type containing the specified
* property, among the provided collection of allowed types.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyEntityDef the property definition
* @param allowedTypes the allowed types
* @return the path to the type containing the specified property
*/
public static List<ChildContext> findOwningTypePath(PropertyEntityDefinition propertyEntityDef,
Collection<? extends TypeDefinition> allowedTypes) {
List<ChildContext> propertyPath = propertyEntityDef.getPropertyPath();
return findOwningTypePath(propertyPath, allowedTypes);
}
/**
* Determines the path to the closest type containing the specified
* property, among the provided collection of allowed types.
*
* <p>
* The lookup is done by traversing the property path backwards (i.e. from
* end to beginning).
* </p>
*
* @param propertyPath the property path
* @param allowedTypes the allowed types
* @return the path to the type containing the specified property
*/
public static List<ChildContext> findOwningTypePath(List<ChildContext> propertyPath,
Collection<? extends TypeDefinition> allowedTypes) {
int ftIdx = findOwningTypeIndex(propertyPath, allowedTypes);
if (ftIdx >= 0) {
return getContainerPropertyPath(propertyPath.subList(0, ftIdx));
}
return Collections.emptyList();
}
/**
* Looks for one of the allowed types, among the children of the provided
* type.
*
* <p>
* NOTE: if more than one of the allowed types are found, only the first one
* is returned.
* </p>
*
* @param typeDef the type definition
* @param allowedTypes the allowed types
* @return the first child of <code>typeDef</code> who is a feature type
*/
public static TypeDefinition findChildType(TypeDefinition typeDef,
Collection<? extends TypeDefinition> allowedTypes) {
if (typeDef != null) {
Collection<? extends ChildDefinition<?>> children = typeDef.getChildren();
if (children != null) {
for (ChildDefinition<?> child : children) {
PropertyDefinition childPropertyDef = child.asProperty();
if (childPropertyDef != null) {
TypeDefinition childPropertyType = childPropertyDef.getPropertyType();
if (allowedTypes.contains(childPropertyType)) {
return childPropertyType;
}
}
}
}
}
return null;
}
/**
* Makes sure the provided property entity is indeed a geometry, and
* retrieves its container property entity (i.e. the geometry property, in
* GML parlance).
*
* @param geometry the geometry entity
* @return the geometry property entity
*/
public static EntityDefinition getGeometryPropertyEntity(PropertyEntityDefinition geometry) {
if (!isGeometryType(geometry.getDefinition().getPropertyType())) {
throw new IllegalArgumentException("Provided entity definition is not a geometry");
}
List<ChildContext> geometryPropertyPath = getContainerPropertyPath(geometry
.getPropertyPath());
return AlignmentUtil.createEntity(geometry.getType(), geometryPropertyPath,
geometry.getSchemaSpace(), geometry.getFilter());
}
private static List<ChildContext> getContainerPropertyPath(List<ChildContext> propertyPath) {
if (propertyPath == null || propertyPath.size() == 0) {
return Collections.emptyList();
}
int lastIdx = propertyPath.size() - 1;
// make sure last element is a property and not a group (e.g. a choice
// element)
while (lastIdx > 0 && propertyPath.get(lastIdx - 1).getChild().asProperty() == null) {
lastIdx--;
}
return propertyPath.subList(0, lastIdx);
}
/**
* Checks whether the property with path <code>nestedPath</code> is actually
* nested inside the property with path <code>containerPath</code>.
*
* @param containerPath the container property path
* @param nestedPath the nested property path
* @return <code>true</code> if it is contained, false otherwise
*/
public static boolean isNested(List<ChildContext> containerPath, List<ChildContext> nestedPath) {
boolean isContained = true;
if (containerPath.size() >= nestedPath.size()) {
isContained = false;
}
else {
for (int i = 0; i < containerPath.size(); i++) {
if (!containerPath.get(i).equals(nestedPath.get(i))) {
isContained = false;
break;
}
}
}
return isContained;
}
/**
* Return the first target {@link Entity}, which is assumed to be a
* {@link Property}.
*
* @param propertyCell the property cell
* @return the target {@link Property}
*/
public static Property getTargetProperty(Cell propertyCell) {
ListMultimap<String, ? extends Entity> targetEntities = propertyCell.getTarget();
if (targetEntities != null && !targetEntities.isEmpty()) {
return (Property) targetEntities.values().iterator().next();
}
return null;
}
/**
* Return the first source {@link Entity}, which is assumed to be a
* {@link Property}.
*
* @param propertyCell the property cell
* @return the target {@link Property}
*/
public static Property getSourceProperty(Cell propertyCell) {
ListMultimap<String, ? extends Entity> sourceEntities = propertyCell.getSource();
if (sourceEntities != null && !sourceEntities.isEmpty()) {
return (Property) sourceEntities.values().iterator().next();
}
return null;
}
/**
* Return the first target {@link Entity}, which is assumed to be a
* {@link Type}.
*
* @param typeCell the type cell
* @return the target {@link Type}
*/
public static Type getTargetType(Cell typeCell) {
ListMultimap<String, ? extends Entity> targetEntities = typeCell.getTarget();
if (targetEntities != null && !targetEntities.isEmpty()) {
return (Type) targetEntities.values().iterator().next();
}
return null;
}
/**
* Convert the provided value to a CQL literal, based on the property
* definition.
*
* <p>
* In practice, this means that for properties whose binding type is a
* {@link Number}, the value is returned as is; otherwise, value is
* translated to a string literal and wrapped in single quotes.
* </p>
*
* @param propertyDef the property definition
* @param value the value to convert
* @return the value as CQL literal
*/
public static String asCqlLiteral(PropertyDefinition propertyDef, String value) {
if (propertyDef != null && value != null) {
TypeDefinition typeDef = propertyDef.getPropertyType();
HasValueFlag hasValue = typeDef.getConstraint(HasValueFlag.class);
if (hasValue != null && hasValue.equals(HasValueFlag.get(true))) {
Binding binding = typeDef.getConstraint(Binding.class);
if (binding != null && Number.class.isAssignableFrom(binding.getBinding())) {
return value;
}
else {
// treat value as a string literal and hope for the best
return "'" + value + "'";
}
}
}
return value;
}
/**
* Test whether the cell represents a Join transformation.
*
* @param typeCell the cell to test
* @return true if the cell represents a Join transformation, false
* otherwise
*/
public static boolean isJoin(Cell typeCell) {
return typeCell != null && JoinFunction.ID.equals(typeCell.getTransformationIdentifier());
}
/**
* @param joinCell the join cell
* @return the {@link JoinParameter} transformation parameter, or
* <code>null</code> if none is found.
*/
public static JoinParameter getJoinParameter(Cell joinCell) {
if (joinCell != null && joinCell.getTransformationParameters() != null) {
List<ParameterValue> joinParameterList = joinCell.getTransformationParameters().get(
PARAMETER_JOIN);
if (joinParameterList != null && joinParameterList.size() > 0) {
return joinParameterList.get(0).as(JoinParameter.class);
}
}
return null;
}
/**
* @param joinParameter the join parameter
* @return the list of join conditions, sorted by join type
*/
public static List<JoinCondition> getSortedJoinConditions(final JoinParameter joinParameter) {
List<JoinCondition> conditions = new ArrayList<JoinCondition>();
if (joinParameter != null) {
conditions.addAll(joinParameter.conditions);
Collections.sort(conditions, new Comparator<JoinCondition>() {
@Override
public int compare(JoinCondition o1, JoinCondition o2) {
TypeEntityDefinition o1Type = AlignmentUtil.getTypeEntity(o1.joinProperty);
TypeEntityDefinition o2Type = AlignmentUtil.getTypeEntity(o2.joinProperty);
return joinParameter.types.indexOf(o1Type)
- joinParameter.types.indexOf(o2Type);
}
});
}
return conditions;
}
/**
* @param cell the cell
* @param parameterName the parameter name
* @return the value of the specified parameter, or <code>null</code> if it
* is not found
*/
public static ParameterValue getTransformationParameter(Cell cell, String parameterName) {
ListMultimap<String, ParameterValue> parameters = cell.getTransformationParameters();
if (parameters != null && !parameters.isEmpty() && parameters.get(parameterName) != null
&& !parameters.get(parameterName).isEmpty()) {
return parameters.get(parameterName).get(0);
}
else {
return null;
}
}
/**
* Converts the given element to a JAXB property type. If any exception
* occurs <code>null</code> is returned.
*
* @param fragment the fragment to convert
* @return the property type or <code>null</code>
*/
public static PropertyType propertyTypeFromDOM(Element fragment) {
try {
JAXBContext jc = JAXBContext.newInstance(JaxbAlignmentIO.ALIGNMENT_CONTEXT,
PropertyType.class.getClassLoader());
Unmarshaller u = jc.createUnmarshaller();
// it will debug problems while unmarshalling
u.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
JAXBElement<PropertyType> root = u.unmarshal(fragment, PropertyType.class);
return root.getValue();
} catch (Exception e) {
return null;
}
}
/**
* Goes through all chain configurations in the provided feature chaining
* configuration and attempts to resolve all unresolved property entity
* definitions.
*
* <p>
* More specifically, resolution for a particular chain configuration is
* attempted if {@link ChainConfiguration#getJaxbNestedTypeTarget()} returns
* a value, while {@link ChainConfiguration#getNestedTypeTarget()} returns
* <code>null</code>.
* </p>
*
* <p>
* Upon successful resolution,
* {@link ChainConfiguration#setJaxbNestedTypeTarget(PropertyType)} is
* invoked with a <code>null</code> argument, to avoid further entity
* resolution attempts.
* </p>
*
* <p>
* A {@link DefaultEntityResolver} instance is used to resolve entities.
* </p>
*
* @param featureChaining the global feature chaining configuration
* @param types the schema to use for entity lookup
* @param ssid the schema space identifier
*/
public static void resolvePropertyTypes(FeatureChaining featureChaining, TypeIndex types,
SchemaSpaceID ssid) {
if (featureChaining != null) {
EntityResolver resolver = new DefaultEntityResolver();
for (String joinCellId : featureChaining.getJoins().keySet()) {
List<ChainConfiguration> chains = featureChaining.getChains(joinCellId);
for (ChainConfiguration chain : chains) {
if (chain.getNestedTypeTarget() == null
&& chain.getJaxbNestedTypeTarget() != null) {
Property resolved = resolver.resolveProperty(
chain.getJaxbNestedTypeTarget(), types, ssid);
if (resolved != null) {
chain.setNestedTypeTarget(resolved.getDefinition());
chain.setJaxbNestedTypeTarget(null);
}
}
}
}
}
}
}