/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed 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 com.evolveum.midpoint.prism.marshaller;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.schema.DefinitionStoreUtils;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import org.jetbrains.annotations.NotNull;
import javax.xml.namespace.QName;
import java.util.List;
import static com.evolveum.midpoint.prism.schema.SchemaRegistry.ComparisonResult.EQUAL;
/**
* @author mederly
*/
public class ItemInfo<ID extends ItemDefinition> {
private QName itemName;
private ID itemDefinition;
private QName typeName;
private ComplexTypeDefinition complexTypeDefinition;
/**
* This method is to be called ONLY on the root level, i.e. when unmarshalling starts.
*/
@NotNull
public static <ID extends ItemDefinition> ItemInfo determine(ItemDefinition itemDefinitionExplicit,
QName itemNameFromSource, QName itemNameExplicit, QName itemNameDefault,
QName typeNameFromSource, QName typeNameExplicit,
Class<?> classExplicit,
@NotNull Class<ID> definitionClass, @NotNull ParsingContext pc, @NotNull SchemaRegistry schemaRegistry) throws SchemaException {
// deriving definition
ID definition;
if (itemDefinitionExplicit != null) {
if (!definitionClass.isAssignableFrom(itemDefinitionExplicit.getClass())) {
throw new IllegalArgumentException("ItemDefinition of " + itemDefinitionExplicit + " doesn't match expected "
+ "definition class of " + definitionClass);
}
@SuppressWarnings("unchecked")
ID narrowedDefinitionExplicit = (ID) itemDefinitionExplicit;
definition = narrowedDefinitionExplicit;
} else {
definition = null;
}
definition = augmentWithType(definition, definitionClass, schemaRegistry, typeNameExplicit);
definition = augmentWithType(definition, definitionClass, schemaRegistry, typeNameFromSource);
definition = augmentWithClass(definition, definitionClass, schemaRegistry, classExplicit);
definition = augmentWithItemName(definition, definitionClass, schemaRegistry, itemNameFromSource);
// Sanity check: if typeNameFromSource or typeNameExplicit are not compatible with the type derived from definition,
// we will NOT use the definition. For example, if client is looking for ConditionalSearchFilterType (has no ItemDefinition)
// the definition here would be very probably SearchFilterType (based e.g. on the itemNameFromSource). And it has not
// to be used, as the client explicitly requested ConditionalSearchFilterType.
//
// Exception is PrismReferenceDefinition, as there are no subtypes in that case (yet)
if (definition != null && !(definition instanceof PrismReferenceDefinition)) {
QName typeName = definition.getTypeName();
if (typeNameExplicit != null && !schemaRegistry.isAssignableFrom(typeNameExplicit, typeName)
|| typeNameFromSource != null && !schemaRegistry.isAssignableFrom(typeNameFromSource, typeName)) {
// the result would NOT match typeNameExplicit/typeNameFromSource (very probably)
// so we have to abandon this definition
definition = null;
}
}
ItemInfo<ID> info = new ItemInfo<>();
info.itemDefinition = definition;
// deriving item name
if (itemNameExplicit != null) {
info.itemName = itemNameExplicit;
} else if (itemNameFromSource != null) {
if (QNameUtil.noNamespace(itemNameFromSource) && definition != null) {
info.itemName = new QName(definition.getName().getNamespaceURI(), itemNameFromSource.getLocalPart());
} else {
info.itemName = itemNameFromSource;
}
} else if (definition != null) {
info.itemName = definition.getName();
} else {
info.itemName = itemNameDefault;
}
// type name
if (definition != null) {
info.typeName = definition.getTypeName();
} else if (typeNameExplicit == null && typeNameFromSource == null && classExplicit != null) {
info.typeName = schemaRegistry.determineTypeForClass(classExplicit);
if (info.typeName != null && info.itemName != null) {
// Create artificial definition. We do NOT mark it as dynamic, because it might come from client code-supplied
// information (i.e. not from the dynamic one). This might cause problems during serialization; but more probably
// not - the decision on putting explicit type declaration on the root level is not driven by the 'dynamic' flag.
// And this method is called only at the root level.
if (Objectable.class.isAssignableFrom(classExplicit)) {
throw new IllegalStateException("Prism object type " + info.typeName + " without definition");
} else if (Containerable.class.isAssignableFrom(classExplicit)) {
@SuppressWarnings("unchecked")
Class<Containerable> containerableClass = (Class<Containerable>) classExplicit;
ComplexTypeDefinition ctd = schemaRegistry.findComplexTypeDefinitionByCompileTimeClass(containerableClass);
@SuppressWarnings("unchecked")
ID id = (ID) new PrismContainerDefinitionImpl<>(info.itemName, ctd,
schemaRegistry.getPrismContext(), containerableClass);
info.itemDefinition = id;
} else {
@SuppressWarnings("unchecked")
ID id = (ID) new PrismPropertyDefinitionImpl<>(info.itemName, info.typeName, schemaRegistry.getPrismContext());
info.itemDefinition = id;
}
}
} else if (typeNameExplicit == null) {
info.typeName = typeNameFromSource;
} else if (typeNameFromSource == null) {
info.typeName = typeNameExplicit;
} else if (schemaRegistry.isAssignableFrom(typeNameExplicit, typeNameFromSource)) {
info.typeName = typeNameFromSource;
} else {
info.typeName = typeNameExplicit;
}
// complex type definition
if (info.itemDefinition != null) {
if (info.itemDefinition instanceof PrismContainerDefinition) {
info.complexTypeDefinition = ((PrismContainerDefinition) info.itemDefinition).getComplexTypeDefinition();
} else {
info.complexTypeDefinition = null;
}
} else if (info.typeName != null) {
// TODO probably optimize (e.g. if we already know the class ... )
info.complexTypeDefinition = schemaRegistry.findComplexTypeDefinitionByType(info.typeName);
} else {
info.complexTypeDefinition = null;
}
return info;
}
private static <ID extends ItemDefinition> ID augmentWithType(ID definition, Class<ID> definitionClass,
SchemaRegistry schemaRegistry, QName typeName) throws SchemaException {
if (typeName == null) {
return definition;
}
if (definition != null && QNameUtil.match(definition.getTypeName(), typeName)) { // just an optimization to avoid needless lookups
return definition;
}
ItemDefinition rawDefFromType = schemaRegistry.findItemDefinitionByType(typeName);
if (rawDefFromType == null) {
return definition; // TODO warning if wrong type?
}
if (!definitionClass.isAssignableFrom(rawDefFromType.getClass())) {
throw new SchemaException("Wrong type name " + typeName + " (not a " + definitionClass.getClass().getSimpleName() + ")"); // TODO context of error
}
@SuppressWarnings("unchecked")
ID defFromType = (ID) rawDefFromType;
if (definition instanceof PrismReferenceDefinition && defFromType instanceof PrismObjectDefinition) {
return definition; // nothing to do; this is a reference holding a composite object
}
return schemaRegistry.selectMoreSpecific(definition, defFromType);
}
private static <ID extends ItemDefinition> ID augmentWithItemName(ID definition, Class<ID> definitionClass,
SchemaRegistry schemaRegistry, QName itemName) throws SchemaException {
if (itemName == null || definition != null && QNameUtil.match(definition.getName(), itemName)) { // just an optimization to avoid needless lookups
return definition;
}
ID defFromItemName = DefinitionStoreUtils.getOne(
schemaRegistry.findItemDefinitionsByElementName(itemName, definitionClass),
false, null);
if (defFromItemName == null) { // zero or more than one
return definition;
}
if (definition == null) {
return defFromItemName;
}
SchemaRegistry.ComparisonResult comparisonResult = schemaRegistry.compareDefinitions(definition, defFromItemName);
switch (comparisonResult) {
case EQUAL: return definition; // definition is generally considered 'at least as good' as the new one
case NO_STATIC_CLASS: return definition; // play safe, select original definition (item name is not very trustworthy source of information)
case INCOMPATIBLE: return definition; // TODO probably we should signal a problem
case FIRST_IS_CHILD: return definition;
case SECOND_IS_CHILD: return defFromItemName;
default: throw new AssertionError("Invalid result: " + comparisonResult);
}
}
private static <ID extends ItemDefinition> ID augmentWithClass(ID definition, Class<ID> definitionClass,
SchemaRegistry schemaRegistry, Class<?> clazz) throws SchemaException {
if (clazz == null) {
return definition;
}
if (definition instanceof PrismContainerDefinition && clazz.equals(((PrismContainerDefinition) definition).getCompileTimeClass())) {
return definition;
}
QName typeNameFromClass = schemaRegistry.determineTypeForClass(clazz);
if (typeNameFromClass == null) {
throw new SchemaException("Unknown class " + clazz.getName());
}
if (definition != null && definition.getTypeName().equals(typeNameFromClass)) {
return definition;
}
@SuppressWarnings("unchecked")
List<ID> defFromClass = schemaRegistry.findItemDefinitionsByCompileTimeClass((Class) clazz, (Class) definitionClass);
if (defFromClass.size() != 1) {
return definition;
} else {
return schemaRegistry.selectMoreSpecific(definition, defFromClass.get(0));
}
}
public QName getItemName() {
return itemName;
}
public ID getItemDefinition() {
return itemDefinition;
}
public ComplexTypeDefinition getComplexTypeDefinition() {
return complexTypeDefinition;
}
public QName getTypeName() {
return typeName;
}
@NotNull
public static ItemInfo determineFromValue(@NotNull PrismValue value, QName itemName, ItemDefinition itemDefinition,
@NotNull SchemaRegistry schemaRegistry) {
ItemInfo info = new ItemInfo();
// definition
info.itemDefinition = itemDefinition;
if (info.itemDefinition == null && value.getParent() != null) {
info.itemDefinition = itemDefinition = value.getParent().getDefinition();
if (info.itemDefinition == null) {
info.itemDefinition = schemaRegistry.findItemDefinitionByElementName(value.getParent().getElementName());
}
}
if (info.itemDefinition == null) {
Class<?> realClass = value.getRealClass();
if (realClass != null) {
List<ItemDefinition> definitions = schemaRegistry.findItemDefinitionsByCompileTimeClass(realClass, ItemDefinition.class);
if (definitions.size() == 1) {
info.itemDefinition = definitions.get(0);
}
}
}
// item name
info.itemName = itemName;
if (info.itemName == null && value.getParent() != null) {
info.itemName = value.getParent().getElementName();
}
if (info.itemName == null && info.itemDefinition != null) {
info.itemName = info.itemDefinition.getName();
}
// item type
if (itemDefinition != null) {
info.typeName = itemDefinition.getTypeName();
} else {
Class<?> realClass = value.getRealClass();
if (realClass != null) {
info.typeName = schemaRegistry.determineTypeForClass(realClass);
}
}
return info;
}
}