/* * 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.common.refinery; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.processor.ResourceSchemaImpl; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.namespace.QName; import java.util.*; /** * @author semancik * */ public class RefinedResourceSchemaImpl implements RefinedResourceSchema { private static final String USER_DATA_KEY_PARSED_RESOURCE_SCHEMA = RefinedResourceSchema.class.getName()+".parsedResourceSchema"; private static final String USER_DATA_KEY_REFINED_SCHEMA = RefinedResourceSchema.class.getName()+".refinedSchema"; // TODO really don't remember why we include originalResourceSchema here instead of simply extending ResourceSchemaImpl ... // TODO Maybe that's because we need to create new RefinedResourceSchema(s) based on existing ResourceSchema(s)? private ResourceSchema originalResourceSchema; private RefinedResourceSchemaImpl(@NotNull ResourceSchema originalResourceSchema) { this.originalResourceSchema = originalResourceSchema; } @Override public List<? extends RefinedObjectClassDefinition> getRefinedDefinitions() { return originalResourceSchema.getDefinitions(RefinedObjectClassDefinition.class); } @Override public List<? extends RefinedObjectClassDefinition> getRefinedDefinitions(ShadowKindType kind) { List<RefinedObjectClassDefinition> rv = new ArrayList<>(); for (RefinedObjectClassDefinition def: getRefinedDefinitions()) { if (MiscSchemaUtil.matchesKind(kind, def.getKind())) { rv.add(def); } } return rv; } @Override public ResourceSchema getOriginalResourceSchema() { return originalResourceSchema; } @Override public RefinedObjectClassDefinition getRefinedDefinition(ShadowKindType kind, String intent) { for (RefinedObjectClassDefinition acctDef: getRefinedDefinitions(kind)) { if (intent == null && acctDef.isDefault()) { return acctDef; } if (acctDef.getIntent() != null && acctDef.getIntent().equals(intent)) { return acctDef; } if (acctDef.getIntent() == null && intent == null) { return acctDef; } } return null; } @Override public CompositeRefinedObjectClassDefinition determineCompositeObjectClassDefinition(ResourceShadowDiscriminator discriminator) { if (discriminator.getKind() == null && discriminator.getObjectClass() == null) { return null; } RefinedObjectClassDefinition structuralObjectClassDefinition; if (discriminator.getKind() == null && discriminator.getObjectClass() != null) { structuralObjectClassDefinition = getRefinedDefinition(discriminator.getObjectClass()); } else { structuralObjectClassDefinition = getRefinedDefinition(discriminator.getKind(), discriminator.getIntent()); } if (structuralObjectClassDefinition == null) { return null; } Collection<RefinedObjectClassDefinition> auxiliaryObjectClassDefinitions = structuralObjectClassDefinition.getAuxiliaryObjectClassDefinitions(); return new CompositeRefinedObjectClassDefinitionImpl(structuralObjectClassDefinition, auxiliaryObjectClassDefinitions); } @Override public CompositeRefinedObjectClassDefinition determineCompositeObjectClassDefinition(PrismObject<ShadowType> shadow) throws SchemaException { return determineCompositeObjectClassDefinition(shadow, null); } @Override public CompositeRefinedObjectClassDefinition determineCompositeObjectClassDefinition(PrismObject<ShadowType> shadow, Collection<QName> additionalAuxiliaryObjectClassQNames) throws SchemaException { ShadowType shadowType = shadow.asObjectable(); RefinedObjectClassDefinition structuralObjectClassDefinition = null; ShadowKindType kind = shadowType.getKind(); String intent = shadowType.getIntent(); QName structuralObjectClassQName = shadowType.getObjectClass(); if (kind != null) { structuralObjectClassDefinition = getRefinedDefinition(kind, intent); } if (structuralObjectClassDefinition == null) { // Fallback to objectclass only if (structuralObjectClassQName == null) { return null; } structuralObjectClassDefinition = getRefinedDefinition(structuralObjectClassQName); } if (structuralObjectClassDefinition == null) { return null; } List<QName> auxiliaryObjectClassQNames = shadowType.getAuxiliaryObjectClass(); if (additionalAuxiliaryObjectClassQNames != null) { auxiliaryObjectClassQNames.addAll(additionalAuxiliaryObjectClassQNames); } Collection<RefinedObjectClassDefinition> auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { RefinedObjectClassDefinition auxiliaryObjectClassDef = getRefinedDefinition(auxiliaryObjectClassQName); if (auxiliaryObjectClassDef == null) { throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+shadow+" does not exist"); } auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); } return new CompositeRefinedObjectClassDefinitionImpl(structuralObjectClassDefinition, auxiliaryObjectClassDefinitions); } @Override public CompositeRefinedObjectClassDefinition determineCompositeObjectClassDefinition(QName structuralObjectClassQName, ShadowKindType kind, String intent) { RefinedObjectClassDefinition structuralObjectClassDefinition = null; Collection<RefinedObjectClassDefinition> auxiliaryObjectClassDefinitions; if (kind != null) { structuralObjectClassDefinition = getRefinedDefinition(kind, intent); } if (structuralObjectClassDefinition == null) { // Fallback to objectclass only if (structuralObjectClassQName == null) { throw new IllegalArgumentException("No kind nor objectclass defined"); } structuralObjectClassDefinition = getRefinedDefinition(structuralObjectClassQName); } if (structuralObjectClassDefinition == null) { return null; } auxiliaryObjectClassDefinitions = structuralObjectClassDefinition.getAuxiliaryObjectClassDefinitions(); return new CompositeRefinedObjectClassDefinitionImpl(structuralObjectClassDefinition, auxiliaryObjectClassDefinitions); } @Override public RefinedObjectClassDefinition getRefinedDefinition(ShadowKindType kind, Collection<String> intents) throws SchemaException { RefinedObjectClassDefinition found = null; for (RefinedObjectClassDefinition acctDef: getRefinedDefinitions(kind)) { RefinedObjectClassDefinition foundCurrent = null; if (intents == null || intents.isEmpty()) { if (acctDef.isDefault()) { foundCurrent = acctDef; } } else { if (intents.contains(acctDef.getIntent())) { foundCurrent = acctDef; } } if (foundCurrent != null) { if (found != null) { if (!QNameUtil.match(found.getTypeName(), foundCurrent.getTypeName())) { throw new SchemaException("More than one ObjectClass found for kind " + kind + ", intents: " + intents + ": " + found.getTypeName() + ", " + foundCurrent.getTypeName()); } } else { found = foundCurrent; } } } return found; } @Override public RefinedObjectClassDefinition getRefinedDefinition(QName objectClassName) { for (RefinedObjectClassDefinition def: getRefinedDefinitions()) { if (def.isDefault() && (QNameUtil.match(def.getTypeName(), objectClassName))) { return def; } } // No default for this object class, so just use the first one. // This is not strictly correct .. but it is a "compatible bug" :-) // TODO: remove this in next major revision for (RefinedObjectClassDefinition def: getRefinedDefinitions()) { if ((QNameUtil.match(def.getTypeName(), objectClassName))) { return def; } } return null; } @Override public RefinedObjectClassDefinition findRefinedDefinitionByObjectClassQName(ShadowKindType kind, QName objectClass) { if (objectClass == null) { return getDefaultRefinedDefinition(kind); } for (RefinedObjectClassDefinition acctDef: getRefinedDefinitions(kind)) { if (acctDef.isDefault() && QNameUtil.match(acctDef.getObjectClassDefinition().getTypeName(), objectClass)) { return acctDef; } } // No default for this object class, so just use the first one. // This is not strictly correct .. but it is a "compatible bug" :-) // TODO: remove this in next major revision for (RefinedObjectClassDefinition acctDef: getRefinedDefinitions(kind)) { if (QNameUtil.match(acctDef.getObjectClassDefinition().getTypeName(), objectClass)) { return acctDef; } } return null; } @Override public LayerRefinedResourceSchema forLayer(LayerType layer) { return new LayerRefinedResourceSchemaImpl(this, layer); } @Override public String toString() { return "rSchema(ns=" + getNamespace() + ")"; } //region Static methods public static RefinedResourceSchema getRefinedSchema(ResourceType resourceType) throws SchemaException { return getRefinedSchema(resourceType, resourceType.asPrismObject().getPrismContext()); } public static LayerRefinedResourceSchema getRefinedSchema(ResourceType resourceType, LayerType layer) throws SchemaException { return getRefinedSchema(resourceType, layer, resourceType.asPrismObject().getPrismContext()); } public static RefinedResourceSchema getRefinedSchema(ResourceType resourceType, PrismContext prismContext) throws SchemaException { PrismObject<ResourceType> resource = resourceType.asPrismObject(); return getRefinedSchema(resource, prismContext); } public static LayerRefinedResourceSchema getRefinedSchema(ResourceType resourceType, LayerType layer, PrismContext prismContext) throws SchemaException { PrismObject<ResourceType> resource = resourceType.asPrismObject(); return getRefinedSchema(resource, layer, prismContext); } public static RefinedResourceSchema getRefinedSchema(PrismObject<ResourceType> resource) throws SchemaException { return getRefinedSchema(resource, resource.getPrismContext()); } public static RefinedResourceSchema getRefinedSchema(PrismObject<ResourceType> resource, PrismContext prismContext) throws SchemaException { if (resource == null){ throw new SchemaException("Could not get refined schema, resource does not exist."); } Object userDataEntry = resource.getUserData(USER_DATA_KEY_REFINED_SCHEMA); if (userDataEntry != null) { if (userDataEntry instanceof RefinedResourceSchema) { return (RefinedResourceSchema)userDataEntry; } else { throw new IllegalStateException("Expected RefinedResourceSchema under user data key "+USER_DATA_KEY_REFINED_SCHEMA+ "in "+resource+", but got "+userDataEntry.getClass()); } } else { RefinedResourceSchema refinedSchema = parse(resource, prismContext); resource.setUserData(USER_DATA_KEY_REFINED_SCHEMA, refinedSchema); return refinedSchema; } } public static LayerRefinedResourceSchema getRefinedSchema(PrismObject<ResourceType> resource, LayerType layer, PrismContext prismContext) throws SchemaException { RefinedResourceSchema refinedSchema = getRefinedSchema(resource, prismContext); if (refinedSchema == null) { return null; } return refinedSchema.forLayer(layer); } public static boolean hasRefinedSchema(ResourceType resourceType) { PrismObject<ResourceType> resource = resourceType.asPrismObject(); return resource.getUserData(USER_DATA_KEY_REFINED_SCHEMA) != null; } public static ResourceSchema getResourceSchema(ResourceType resourceType, PrismContext prismContext) throws SchemaException { PrismObject<ResourceType> resource = resourceType.asPrismObject(); return getResourceSchema(resource, prismContext); } public static ResourceSchema getResourceSchema(PrismObject<ResourceType> resource, PrismContext prismContext) throws SchemaException { Element resourceXsdSchema = ResourceTypeUtil.getResourceXsdSchema(resource); if (resourceXsdSchema == null) { return null; } Object userDataEntry = resource.getUserData(USER_DATA_KEY_PARSED_RESOURCE_SCHEMA); if (userDataEntry != null) { if (userDataEntry instanceof ResourceSchema) { return (ResourceSchema)userDataEntry; } else { throw new IllegalStateException("Expected ResourceSchema under user data key "+ USER_DATA_KEY_PARSED_RESOURCE_SCHEMA+ "in "+resource+", but got "+userDataEntry.getClass()); } } else { InternalMonitor.recordResourceSchemaParse(); ResourceSchemaImpl parsedSchema = ResourceSchemaImpl.parse(resourceXsdSchema, "resource schema of "+resource, prismContext); if (parsedSchema == null) { throw new IllegalStateException("Parsed schema is null: most likely an internall error"); } resource.setUserData(USER_DATA_KEY_PARSED_RESOURCE_SCHEMA, parsedSchema); parsedSchema.setNamespace(ResourceTypeUtil.getResourceNamespace(resource)); return parsedSchema; } } public static void setParsedResourceSchemaConditional(ResourceType resourceType, ResourceSchema parsedSchema) { if (hasParsedSchema(resourceType)) { return; } PrismObject<ResourceType> resource = resourceType.asPrismObject(); resource.setUserData(USER_DATA_KEY_PARSED_RESOURCE_SCHEMA, parsedSchema); } public static boolean hasParsedSchema(ResourceType resourceType) { PrismObject<ResourceType> resource = resourceType.asPrismObject(); return resource.getUserData(USER_DATA_KEY_PARSED_RESOURCE_SCHEMA) != null; } public static RefinedResourceSchema parse(PrismObject<ResourceType> resource, PrismContext prismContext) throws SchemaException { return parse(resource.asObjectable(), prismContext); } public static RefinedResourceSchema parse(ResourceType resourceType, PrismContext prismContext) throws SchemaException { ResourceSchema originalResourceSchema = getResourceSchema(resourceType, prismContext); if (originalResourceSchema == null) { return null; } String contextDescription = "definition of "+resourceType; RefinedResourceSchemaImpl rSchema = new RefinedResourceSchemaImpl(originalResourceSchema); SchemaHandlingType schemaHandling = resourceType.getSchemaHandling(); if (schemaHandling != null) { parseObjectTypeDefsFromSchemaHandling(rSchema, resourceType, schemaHandling, schemaHandling.getObjectType(), null, prismContext, contextDescription); } parseObjectTypesFromSchema(rSchema, resourceType, prismContext, contextDescription); // We need to parse associations and auxiliary object classes in a second pass. We need to have all object classes parsed before correctly setting association // targets for (RefinedObjectClassDefinition rOcDef: rSchema.getRefinedDefinitions()) { ((RefinedObjectClassDefinitionImpl) rOcDef).parseAssociations(rSchema); ((RefinedObjectClassDefinitionImpl) rOcDef).parseAuxiliaryObjectClasses(rSchema); } // We can parse attributes only after we have all the object class info parsed (including auxiliary object classes) for (RefinedObjectClassDefinition rOcDef: rSchema.getRefinedDefinitions()) { ((RefinedObjectClassDefinitionImpl) rOcDef).parseAttributes(rSchema, contextDescription); } return rSchema; } // private static boolean hasAnyObjectTypeDef(SchemaHandlingType schemaHandling) { // if (schemaHandling == null) { // return false; // } // if (!schemaHandling.getObjectType().isEmpty()) { // return true; // } // return false; // } private static void parseObjectTypeDefsFromSchemaHandling(RefinedResourceSchemaImpl rSchema, ResourceType resourceType, SchemaHandlingType schemaHandling, Collection<ResourceObjectTypeDefinitionType> resourceObjectTypeDefs, ShadowKindType impliedKind, PrismContext prismContext, String contextDescription) throws SchemaException { if (resourceObjectTypeDefs == null) { return; } Map<ShadowKindType, RefinedObjectClassDefinition> defaults = new HashMap<>(); for (ResourceObjectTypeDefinitionType accountTypeDefType: resourceObjectTypeDefs) { RefinedObjectClassDefinition rOcDef = RefinedObjectClassDefinitionImpl.parse(accountTypeDefType, resourceType, rSchema, impliedKind, prismContext, contextDescription); if (rOcDef.isDefault()) { if (defaults.containsKey(rOcDef.getKind())) { throw new SchemaException("More than one default "+rOcDef.getKind()+" definitions ("+defaults.get(rOcDef.getKind())+", "+rOcDef+") in " + contextDescription); } else { defaults.put(rOcDef.getKind(), rOcDef); } } rSchema.add(rOcDef); } } public static List<String> getIntentsForKind(RefinedResourceSchema rSchema, ShadowKindType kind) { List<String> intents = new ArrayList<>(); for (ObjectClassComplexTypeDefinition objClassDef : rSchema.getObjectClassDefinitions()) { if (objClassDef.getKind() == kind){ intents.add(objClassDef.getIntent()); } } return intents; } private static void parseObjectTypesFromSchema(RefinedResourceSchemaImpl rSchema, ResourceType resourceType, PrismContext prismContext, String contextDescription) throws SchemaException { RefinedObjectClassDefinition rAccountDefDefault = null; for(ObjectClassComplexTypeDefinition objectClassDef: rSchema.getOriginalResourceSchema().getObjectClassDefinitions()) { QName objectClassname = objectClassDef.getTypeName(); if (rSchema.getRefinedDefinition(objectClassname) != null) { continue; } RefinedObjectClassDefinition rOcDef = RefinedObjectClassDefinitionImpl.parseFromSchema(objectClassDef, resourceType, rSchema, prismContext, "object class " + objectClassname + ", in " + contextDescription); if (objectClassDef.getKind() == ShadowKindType.ACCOUNT && rOcDef.isDefault()) { if (rAccountDefDefault == null) { rAccountDefDefault = rOcDef; } else { throw new SchemaException("More than one default account definitions ("+rAccountDefDefault+", "+rOcDef+") in " + contextDescription); } } rSchema.add(rOcDef); } } //endregion private void add(RefinedObjectClassDefinition rOcDef) { ((ResourceSchemaImpl) originalResourceSchema).add(rOcDef); // TODO FIXME } //region Delegations @Override public ObjectClassComplexTypeDefinition findObjectClassDefinition(QName objectClassQName) { return originalResourceSchema.findObjectClassDefinition(objectClassQName); } @Override public ObjectClassComplexTypeDefinition findObjectClassDefinition(ShadowKindType kind, String intent) { return originalResourceSchema.findObjectClassDefinition(kind, intent); } @Override public ObjectClassComplexTypeDefinition findDefaultObjectClassDefinition(ShadowKindType kind) { return originalResourceSchema.findDefaultObjectClassDefinition(kind); } @Override public String getNamespace() { return originalResourceSchema.getNamespace(); } @Override @NotNull public Collection<Definition> getDefinitions() { return originalResourceSchema.getDefinitions(); } @Override @NotNull public <T extends Definition> List<T> getDefinitions(@NotNull Class<T> type) { return originalResourceSchema.getDefinitions(type); } @Override public PrismContext getPrismContext() { return originalResourceSchema.getPrismContext(); } @Override @NotNull public Document serializeToXsd() throws SchemaException { return originalResourceSchema.serializeToXsd(); } @Override public boolean isEmpty() { return originalResourceSchema.isEmpty(); } @NotNull @Override public <ID extends ItemDefinition> List<ID> findItemDefinitionsByCompileTimeClass( @NotNull Class<?> compileTimeClass, @NotNull Class<ID> definitionClass) { return originalResourceSchema.findItemDefinitionsByCompileTimeClass(compileTimeClass, definitionClass); } @Nullable @Override public <TD extends TypeDefinition> TD findTypeDefinitionByCompileTimeClass(@NotNull Class<?> compileTimeClass, @NotNull Class<TD> definitionClass) { return originalResourceSchema.findTypeDefinitionByCompileTimeClass(compileTimeClass, definitionClass); } @Override @Nullable public <TD extends TypeDefinition> TD findTypeDefinitionByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { return originalResourceSchema.findTypeDefinitionByType(typeName, definitionClass); } @Override public String debugDump() { return originalResourceSchema.debugDump(); } @Override public String debugDump(int indent) { return originalResourceSchema.debugDump(indent); } @Nullable @Override public <ID extends ItemDefinition> ID findItemDefinitionByType( @NotNull QName typeName, @NotNull Class<ID> definitionType) { return originalResourceSchema.findItemDefinitionByType(typeName, definitionType); } @Override @NotNull public <ID extends ItemDefinition> List<ID> findItemDefinitionsByElementName(@NotNull QName elementName, @NotNull Class<ID> definitionClass) { return originalResourceSchema.findItemDefinitionsByElementName(elementName, definitionClass); } @NotNull @Override public <TD extends TypeDefinition> Collection<? extends TD> findTypeDefinitionsByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { return originalResourceSchema.findTypeDefinitionsByType(typeName, definitionClass); } //endregion }