/* * Copyright (c) 2010-2014 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.schema; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.EntityResolver; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; /** * * @author Radovan Semancik * */ public class PrismSchemaImpl implements PrismSchema { //private static final long serialVersionUID = 5068618465625931984L; //private static final Trace LOGGER = TraceManager.getTrace(PrismSchema.class); @NotNull protected final Collection<Definition> definitions = new ArrayList<>(); protected String namespace; // may be null if not properly initialized protected PrismContext prismContext; // Item definitions that couldn't be created when parsing the schema because of unresolvable CTD. // (Caused by the fact that the type resides in another schema.) // These definitions are to be resolved after parsing the set of schemas. @NotNull private final List<DefinitionSupplier> delayedItemDefinitions = new ArrayList<>(); protected PrismSchemaImpl(PrismContext prismContext) { this.prismContext = prismContext; } public PrismSchemaImpl(@NotNull String namespace, PrismContext prismContext) { if (StringUtils.isEmpty(namespace)) { throw new IllegalArgumentException("Namespace can't be null or empty."); } this.namespace = namespace; this.prismContext = prismContext; } //region Trivia @Override public String getNamespace() { return namespace; } public void setNamespace(@NotNull String namespace) { this.namespace = namespace; } @NotNull @Override public Collection<Definition> getDefinitions() { return definitions; } @SuppressWarnings("unchecked") @NotNull @Override public <T extends Definition> List<T> getDefinitions(@NotNull Class<T> type) { return definitions.stream() .filter(def -> type.isAssignableFrom(def.getClass())) .map(def -> (T) def) .collect(Collectors.toList()); } void addDelayedItemDefinition(DefinitionSupplier supplier) { delayedItemDefinitions.add(supplier); } @NotNull List<DefinitionSupplier> getDelayedItemDefinitions() { return delayedItemDefinitions; } @Override public boolean isEmpty() { return definitions.isEmpty(); } public void add(@NotNull Definition def) { definitions.add(def); } @Override public PrismContext getPrismContext() { return prismContext; } //endregion //region XSD parsing and serialization // TODO: cleanup this chaos public static PrismSchema parse(Element element, boolean isRuntime, String shortDescription, PrismContext prismContext) throws SchemaException { return parse(element, prismContext.getEntityResolver(), new PrismSchemaImpl(prismContext), isRuntime, shortDescription, false, prismContext); } public static PrismSchema parse(Element element, EntityResolver resolver, boolean isRuntime, String shortDescription, boolean allowDelayedItemDefinitions, PrismContext prismContext) throws SchemaException { return parse(element, resolver, new PrismSchemaImpl(prismContext), isRuntime, shortDescription, allowDelayedItemDefinitions, prismContext); } protected static PrismSchema parse(Element element, PrismSchemaImpl schema, boolean isRuntime, String shortDescription, PrismContext prismContext) throws SchemaException { return parse(element, prismContext.getEntityResolver(), schema, isRuntime, shortDescription, false, prismContext); } private static PrismSchema parse(Element element, EntityResolver resolver, PrismSchemaImpl schema, boolean isRuntime, String shortDescription, boolean allowDelayedItemDefinitions, PrismContext prismContext) throws SchemaException { if (element == null) { throw new IllegalArgumentException("Schema element must not be null in "+shortDescription); } DomToSchemaProcessor processor = new DomToSchemaProcessor(); processor.setEntityResolver(resolver); processor.setPrismContext(prismContext); processor.setShortDescription(shortDescription); processor.setRuntime(isRuntime); processor.setAllowDelayedItemDefinitions(allowDelayedItemDefinitions); processor.parseDom(schema, element); return schema; } @NotNull @Override public Document serializeToXsd() throws SchemaException { SchemaToDomProcessor processor = new SchemaToDomProcessor(); processor.setPrismContext(prismContext); return processor.parseSchema(this); } //endregion //region Creating definitions /** * Creates a new property container definition and adds it to the schema. * * This is a preferred way how to create definition in the schema. * * @param localTypeName * type name "relative" to schema namespace * @return new property container definition */ public PrismContainerDefinitionImpl createPropertyContainerDefinition(String localTypeName) { QName typeName = new QName(getNamespace(), localTypeName); QName name = new QName(getNamespace(), toElementName(localTypeName)); ComplexTypeDefinition cTypeDef = new ComplexTypeDefinitionImpl(typeName, prismContext); PrismContainerDefinitionImpl def = new PrismContainerDefinitionImpl(name, cTypeDef, prismContext); definitions.add(cTypeDef); definitions.add(def); return def; } public PrismContainerDefinitionImpl createPropertyContainerDefinition(String localElementName, String localTypeName) { QName typeName = new QName(getNamespace(), localTypeName); QName name = new QName(getNamespace(), localElementName); ComplexTypeDefinition cTypeDef = findComplexTypeDefinitionByType(typeName); if (cTypeDef == null) { cTypeDef = new ComplexTypeDefinitionImpl(typeName, prismContext); definitions.add(cTypeDef); } PrismContainerDefinitionImpl def = new PrismContainerDefinitionImpl(name, cTypeDef, prismContext); definitions.add(def); return def; } public ComplexTypeDefinition createComplexTypeDefinition(QName typeName) { ComplexTypeDefinition cTypeDef = new ComplexTypeDefinitionImpl(typeName, prismContext); definitions.add(cTypeDef); return cTypeDef; } /** * Creates a top-level property definition and adds it to the schema. * * This is a preferred way how to create definition in the schema. * * @param localName * element name "relative" to schema namespace * @param typeName * XSD type name of the element * @return new property definition */ public PrismPropertyDefinition createPropertyDefinition(String localName, QName typeName) { QName name = new QName(getNamespace(), localName); return createPropertyDefinition(name, typeName); } /* * Creates a top-level property definition and adds it to the schema. * * This is a preferred way how to create definition in the schema. * * @param localName * element name "relative" to schema namespace * @param localTypeName * XSD type name "relative" to schema namespace * @return new property definition */ // public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName) { // QName name = new QName(getNamespace(), localName); // QName typeName = new QName(getNamespace(), localTypeName); // return createPropertyDefinition(name, typeName); // } /** * Creates a top-level property definition and adds it to the schema. * * This is a preferred way how to create definition in the schema. * * @param name * element name * @param typeName * XSD type name of the element * @return new property definition */ public PrismPropertyDefinition createPropertyDefinition(QName name, QName typeName) { PrismPropertyDefinition def = new PrismPropertyDefinitionImpl(name, typeName, prismContext); definitions.add(def); return def; } /** * Internal method to create a "nice" element name from the type name. */ private String toElementName(String localTypeName) { String elementName = StringUtils.uncapitalize(localTypeName); if (elementName.endsWith("Type")) { return elementName.substring(0, elementName.length() - 4); } return elementName; } //endregion //region Pretty printing @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); for (int i=0;i<indent;i++) { sb.append(INDENT_STRING); } sb.append(toString()).append("\n"); Iterator<Definition> i = definitions.iterator(); while (i.hasNext()) { Definition def = i.next(); sb.append(def.debugDump(indent+1)); if (i.hasNext()) { sb.append("\n"); } } return sb.toString(); } @Override public String toString() { return "Schema(ns=" + namespace + ")"; } //endregion //region Implementation of DefinitionsStore methods (commented out) // private final SearchContexts searchContexts = new SearchContexts(this); // pre-existing object to avoid creating them each time // // @Override // public GlobalDefinitionSearchContext<PrismObjectDefinition<? extends Objectable>> findObjectDefinition() { // return searchContexts.pod; // } // // @Override // public GlobalDefinitionSearchContext<PrismContainerDefinition<? extends Containerable>> findContainerDefinition() { // return searchContexts.pcd; // } // // @Override // public GlobalDefinitionSearchContext<ItemDefinition<?>> findItemDefinition() { // return searchContexts.id; // } // // @Override // public GlobalDefinitionSearchContext<ComplexTypeDefinition> findComplexTypeDefinition() { // return searchContexts.ctd; // } // // @Override // public GlobalDefinitionSearchContext<PrismPropertyDefinition> findPropertyDefinition() { // return searchContexts.ppd; // } // // @Override // public GlobalDefinitionSearchContext<PrismReferenceDefinition> findReferenceDefinition() { // return searchContexts.prd; // } //endregion //region Finding definitions // items @NotNull public <ID extends ItemDefinition> List<ID> findItemDefinitionsByCompileTimeClass( @NotNull Class<?> compileTimeClass, @NotNull Class<ID> definitionClass) { List<ID> found = new ArrayList<>(); for (Definition def: definitions) { if (definitionClass.isAssignableFrom(def.getClass())) { if (def instanceof PrismContainerDefinition) { @SuppressWarnings("unchecked") ID contDef = (ID) def; if (compileTimeClass.equals(((PrismContainerDefinition) contDef).getCompileTimeClass())) { found.add(contDef); } } else if (def instanceof PrismPropertyDefinition) { if (compileTimeClass.equals(prismContext.getSchemaRegistry().determineClassForType(def.getTypeName()))) { @SuppressWarnings("unchecked") ID itemDef = (ID) def; found.add(itemDef); } } else { // skipping the definition (PRD is not supported yet) } } } return found; } @Override public <ID extends ItemDefinition> ID findItemDefinitionByType(@NotNull QName typeName, @NotNull Class<ID> definitionClass) { // TODO: check for multiple definition with the same type for (Definition definition : definitions) { if (definitionClass.isAssignableFrom(definition.getClass())) { @SuppressWarnings("unchecked") ID itemDef = (ID) definition; if (QNameUtil.match(typeName, itemDef.getTypeName())) { return itemDef; } } } return null; } @NotNull @Override public <ID extends ItemDefinition> List<ID> findItemDefinitionsByElementName(@NotNull QName elementName, @NotNull Class<ID> definitionClass) { List<ID> rv = new ArrayList<ID>(); for (Definition definition : definitions) { if (definitionClass.isAssignableFrom(definition.getClass())) { @SuppressWarnings("unchecked") ID itemDef = (ID) definition; if (QNameUtil.match(elementName, itemDef.getName())) { rv.add(itemDef); } } } return rv; } // private Map<Class<? extends Objectable>, PrismObjectDefinition> classToDefCache = Collections.synchronizedMap(new HashMap<>()); // @Override // public <O extends Objectable> PrismObjectDefinition<O> findObjectDefinitionByCompileTimeClass(@NotNull Class<O> type) { // if (classToDefCache.containsKey(type)) { // there may be null values // return classToDefCache.get(type); // } // PrismObjectDefinition<O> definition = scanForPrismObjectDefinition(type); // classToDefCache.put(type, definition); // return definition; // } // private <T extends Objectable> PrismObjectDefinition<T> scanForPrismObjectDefinition(Class<T> type) { // for (Definition def: getDefinitions()) { // if (def instanceof PrismObjectDefinition<?>) { // PrismObjectDefinition<?> objDef = (PrismObjectDefinition<?>)def; // if (type.equals(objDef.getCompileTimeClass())) { // classToDefCache.put(type, objDef); // return (PrismObjectDefinition<T>) objDef; // } // } // } // return null; // } @Override public <C extends Containerable> ComplexTypeDefinition findComplexTypeDefinitionByCompileTimeClass(@NotNull Class<C> compileTimeClass) { for (Definition def: definitions) { if (def instanceof ComplexTypeDefinition) { ComplexTypeDefinition ctd = (ComplexTypeDefinition) def; if (compileTimeClass.equals(ctd.getCompileTimeClass())) { return ctd; } } } return null; } @Nullable @Override public <TD extends TypeDefinition> TD findTypeDefinitionByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { Collection<TD> definitions = findTypeDefinitionsByType(typeName, definitionClass); return !definitions.isEmpty() ? definitions.iterator().next() : null; // TODO treat multiple results somehow } @NotNull @Override @SuppressWarnings("unchecked") public <TD extends TypeDefinition> Collection<TD> findTypeDefinitionsByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { return (List) definitions.stream() .filter(def -> definitionClass.isAssignableFrom(def.getClass()) && QNameUtil.match(typeName, def.getTypeName())) .collect(Collectors.toList()); } @Nullable @Override public <TD extends TypeDefinition> TD findTypeDefinitionByCompileTimeClass(@NotNull Class<?> compileTimeClass, @NotNull Class<TD> definitionClass) { // TODO: check for multiple definition with the same type for (Definition definition : definitions) { if (definitionClass.isAssignableFrom(definition.getClass()) && compileTimeClass.equals(((TD) definition).getCompileTimeClass())) { return (TD) definition; } } return null; } //endregion }