/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it 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. * * Astroboa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.engine.definition.visitor; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.BetaConceptNamespaceConstants; import org.betaconceptframework.astroboa.api.model.ValueType; import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition; import org.betaconceptframework.astroboa.api.model.definition.LocalizableCmsDefinition; import org.betaconceptframework.astroboa.api.model.definition.ObjectReferencePropertyDefinition; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.cache.region.DefinitionCacheRegion; import org.betaconceptframework.astroboa.model.impl.ItemQName; import org.betaconceptframework.astroboa.model.impl.definition.ObjectReferencePropertyDefinitionImpl; import org.betaconceptframework.astroboa.model.impl.item.CmsDefinitionItem; import org.betaconceptframework.astroboa.model.impl.item.ItemUtils; import org.betaconceptframework.astroboa.util.CmsConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.sun.xml.xsom.XSAnnotation; import com.sun.xml.xsom.XSAttGroupDecl; import com.sun.xml.xsom.XSAttributeDecl; import com.sun.xml.xsom.XSAttributeUse; import com.sun.xml.xsom.XSComplexType; import com.sun.xml.xsom.XSContentType; import com.sun.xml.xsom.XSElementDecl; import com.sun.xml.xsom.XSFacet; import com.sun.xml.xsom.XSIdentityConstraint; import com.sun.xml.xsom.XSModelGroup; import com.sun.xml.xsom.XSModelGroupDecl; import com.sun.xml.xsom.XSNotation; import com.sun.xml.xsom.XSParticle; import com.sun.xml.xsom.XSSchema; import com.sun.xml.xsom.XSSimpleType; import com.sun.xml.xsom.XSWildcard; import com.sun.xml.xsom.XSXPath; import com.sun.xml.xsom.visitor.XSVisitor; /** * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class CmsDefinitionVisitor implements XSVisitor{ private final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private DefinitionCacheRegion definitionCacheRegion; private Map<String, XSAttGroupDecl> attributeGroupDeclarations = new TreeMap<String, XSAttGroupDecl>(); private Map<String, XSComplexType> complexTypeDeclarations = new TreeMap<String, XSComplexType>(); private Map<String, XSElementDecl> elementDeclarations = new TreeMap<String, XSElementDecl>(); private Map<ItemQName, XSAttributeUse> builtInAttributes = new HashMap<ItemQName, XSAttributeUse>(); private Map<String, Set<String>> topicPropertyPathsPerTaxonomies = new HashMap<String, Set<String>>(); private Set<String> multivalueProperties = new HashSet<String>(); private Map<String, byte[]> xmlSchemaDefinitionsPerFilename = new HashMap<String, byte[]>(); private Map<String, List<String>> contentTypeHierarchy = new HashMap<String, List<String>>(); private Map<QName, String> xmlSchemaDefinitionURLsPerQName = new HashMap<QName, String>(); private List<String> definitionsUnderProcess = new ArrayList<String>(); private Map<String, LocalizableCmsDefinition> internalDefinitionsCache = new HashMap<String, LocalizableCmsDefinition>(); private List<ObjectReferencePropertyDefinition> objectReferencePropertyDefinitions = new ArrayList<ObjectReferencePropertyDefinition>(); public void createContentDefintions() throws Exception { if (attributeGroupDeclarations.isEmpty()) throw new Exception("Cms internal attribute group "+CmsDefinitionItem.contentObjectPropertyAttGroup.getLocalPart() + " is not found"); //Order of this is significant //Visit attribute group declarations for (XSAttGroupDecl attributeGroupDecl: attributeGroupDeclarations.values()) attributeGroupDecl.visit(this); //Visit Elements for (XSElementDecl elementDecl : elementDeclarations.values()){ elementDecl.visit(this); } //Visit ComplexTypes which were not processed at all during element visit for (Entry<String, XSComplexType> complexDecl : complexTypeDeclarations.entrySet()){ //Visit Complex Type only if it has not been visited before //and it is not an Astroboa model complex type String targetNamespace = complexDecl.getValue().getTargetNamespace(); if (StringUtils.isNotBlank(targetNamespace) && ! BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_URI.equals(targetNamespace) && ! BetaConceptNamespaceConstants.ASTROBOA_API_URI.equals(targetNamespace) && ! definitionCacheRegion.hasComplexTypeDefinition(complexDecl.getKey())){ complexDecl.getValue().visit(this); } } //Further process ContentObjectPropertyDefinitions which contain content type restriction processContentObjectPropertyDefinitions(); //Notify cache for several additional information definitionCacheRegion.putTopicPropertyPathsPerTaxonomy(transformSetInMapToList(topicPropertyPathsPerTaxonomies)); definitionCacheRegion.putXMLSchemaDefinitionsPerFilename(xmlSchemaDefinitionsPerFilename); definitionCacheRegion.putMultivalueProperties(new ArrayList<String>(multivalueProperties)); definitionCacheRegion.putContentTypeHierarchy(contentTypeHierarchy); definitionCacheRegion.putLocationURLForDefinition(xmlSchemaDefinitionURLsPerQName); definitionCacheRegion.printDefinitionCacheToLog(); } /** * */ private void processContentObjectPropertyDefinitions() { if (CollectionUtils.isNotEmpty(objectReferencePropertyDefinitions)){ for (ObjectReferencePropertyDefinition objectReferencePropertyDefinition: objectReferencePropertyDefinitions){ List<String> acceptedContentTypes = objectReferencePropertyDefinition.getAcceptedContentTypes(); if (CollectionUtils.isNotEmpty(acceptedContentTypes)){ //Use Set to ensure that values are unique Set<String> expandedContentTypes = new HashSet<String>(); for (String acceptedContentType : acceptedContentTypes){ if (contentTypeHierarchy.containsKey(acceptedContentType)){ //AcceptedContentType is a super type and thus we need to //keep all content types which extend this super type expandedContentTypes.addAll(contentTypeHierarchy.get(acceptedContentType)); } else { expandedContentTypes.add(acceptedContentType); } } ((ObjectReferencePropertyDefinitionImpl)objectReferencePropertyDefinition).addExpandedAcceptedContentTypes(new HashSet<String>(expandedContentTypes)); if (logger.isDebugEnabled()){ logger.debug("Accepted content types {} of property {} have been expanded to {}", new Object[]{objectReferencePropertyDefinition.getAcceptedContentTypes(), objectReferencePropertyDefinition.getFullPath(), objectReferencePropertyDefinition.getExpandedAcceptedContentTypes()}); } } } } } private Map<String, List<String>> transformSetInMapToList( Map<String, Set<String>> mapWithSet) { Map<String, List<String>> mapWithList = new HashMap<String, List<String>>(); if (MapUtils.isNotEmpty(mapWithSet)){ for (Entry<String, Set<String>> entry : mapWithSet.entrySet()){ mapWithList.put(entry.getKey(), new ArrayList<String>(entry.getValue())); } } return mapWithList; } public void annotation(XSAnnotation arg0) { } public void attGroupDecl(XSAttGroupDecl attGroup) { //Process only built-in attribute group if (attGroup.getName().equals(CmsDefinitionItem.contentObjectPropertyAttGroup.getLocalPart())) { String targetNameSpace = attGroup.getTargetNamespace(); Iterator<? extends XSAttributeUse> attrDeclarationsIter = attGroup.iterateDeclaredAttributeUses(); while (attrDeclarationsIter.hasNext()) { XSAttributeUse attributeUse = attrDeclarationsIter.next(); String name = attributeUse.getDecl().getName(); //Use default prefix ItemQName attribute = ItemUtils.createNewItem(BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX, targetNameSpace, name); builtInAttributes.put(attribute, attributeUse); } } } public void attributeDecl(XSAttributeDecl arg0) { } public void attributeUse(XSAttributeUse arg0) { } public void complexType(XSComplexType complexType) { //Visit only complex properties which extend complexCmsPropertyType //Normally this method is called when visiting global complex types if (isElementValidComplexType(complexType)){ CmsPropertyVisitor contentObjectPropertyVisitor = new CmsPropertyVisitor(builtInAttributes, null, false, false, 0, this); complexType.visit(contentObjectPropertyVisitor); cacheDefinition(contentObjectPropertyVisitor.getDefinition()); } else{ logger.debug("Type {} does not extend builtin complex type 'complexCmsPropertyType'", complexType); } } private boolean isElementValidComplexType(XSComplexType complexType) { //According to XSOM API method getBaseType always returns not null String typeName = complexType.getBaseType().getName(); String typeNamespace = complexType.getBaseType().getTargetNamespace(); ItemQName complexTypeAsItemQName = ItemUtils.createNewItem("", typeNamespace, typeName); return complexTypeAsItemQName.equals(CmsDefinitionItem.complexCmsPropertyType); //Complex Type must extend ComplexCmsProperty type } public void cacheInternalDefinition(LocalizableCmsDefinition definition) { if (definition != null){ String typeQName = getDefinitionQName(definition); //Put definition to InternalCache try { internalDefinitionsCache.put(typeQName, definition); } catch (Exception e) { throw new CmsException(e); } if (ValueType.ContentType == definition.getValueType() || ValueType.Complex == definition.getValueType()){ if (definitionsUnderProcess.contains(typeQName)){ definitionsUnderProcess.remove(typeQName); } } } } public boolean internalCacheHasDefinition(String typeName, String typeNamespace) { return typeName != null && typeNamespace != null && internalDefinitionsCache.containsKey("{"+typeNamespace+"}"+typeName); } public LocalizableCmsDefinition getDefinitionFromInternalCache(String typeName, String typeNamespace) { if (typeName != null && typeNamespace != null) { return internalDefinitionsCache.get("{"+typeNamespace+"}"+typeName); } else { return null; } } public void cacheDefinition(LocalizableCmsDefinition definition) { if (definition != null){ //Put definition to CacheManager according to its valueType try { definitionCacheRegion.putDefinition(definition.getValueType(), definition.getName(), definition); } catch (Exception e) { throw new CmsException(e); } if (ValueType.ContentType == definition.getValueType() || ValueType.Complex == definition.getValueType()){ String typeQName = getDefinitionQName(definition); if (definitionsUnderProcess.contains(typeQName)){ definitionsUnderProcess.remove(typeQName); } xmlSchemaDefinitionURLsPerQName.put(definition.getQualifiedName(), definition.url(ResourceRepresentationType.XSD)); //Get base content types if (ValueType.ContentType == definition.getValueType()){ List<String> superContentTypes = ((ContentObjectTypeDefinition)definition).getSuperContentTypes(); if (CollectionUtils.isNotEmpty(superContentTypes)){ for (String superContentType : superContentTypes){ if (! contentTypeHierarchy.containsKey(superContentType)){ contentTypeHierarchy.put(superContentType, new ArrayList<String>()); } if (! contentTypeHierarchy.get(superContentType).contains(definition.getName())){ contentTypeHierarchy.get(superContentType).add(definition.getName()); } } } } //Create property paths DefinitionPropertyPathBuilder definitionPropertyPathBuilder = new DefinitionPropertyPathBuilder(ValueType.Complex == definition.getValueType()); definition.accept(definitionPropertyPathBuilder); objectReferencePropertyDefinitions.addAll(definitionPropertyPathBuilder.getObjectReferencePropertyDefinitions()); //definitionPropertyPathBuilder.loadPropertyPathsForDefinition(((ContentObjectTypeDefinition)definition).getPropertyDefinitions(), definition); //Get Multivalue properties if (CollectionUtils.isNotEmpty(definitionPropertyPathBuilder.getMutlivalueProperties())){ multivalueProperties.addAll(definitionPropertyPathBuilder.getMutlivalueProperties()); } //Get topic property paths per taxonomy and add them to list Map<String, Set<String>> childTopicPropertyPaths = definitionPropertyPathBuilder.getTopicPropertyPathsPerTaxonomy(); if (MapUtils.isNotEmpty(childTopicPropertyPaths)){ for (Entry<String, Set<String>> topicPropertyPathPerTaxonomy : childTopicPropertyPaths.entrySet()){ List<String> topicPropertyPathList = new ArrayList<String>(topicPropertyPathPerTaxonomy.getValue()); String taxonomyName = topicPropertyPathPerTaxonomy.getKey(); if (CollectionUtils.isNotEmpty(topicPropertyPathList)){ if (!topicPropertyPathsPerTaxonomies.containsKey(taxonomyName)){ topicPropertyPathsPerTaxonomies.put(taxonomyName, new HashSet<String>()); } for(String topicPropertyPath: topicPropertyPathList){ if (!topicPropertyPathsPerTaxonomies.get(taxonomyName).contains(topicPropertyPath)){ topicPropertyPathsPerTaxonomies.get(taxonomyName).add(topicPropertyPath); } } } } } } } } public void facet(XSFacet arg0) { } public void identityConstraint(XSIdentityConstraint arg0) { } public void notation(XSNotation arg0) { } public void schema(XSSchema schema) { // Do not process schema of XML Schema!!! if (!XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(schema.getTargetNamespace())) { //Collect all attributeGroups if (MapUtils.isNotEmpty(schema.getAttGroupDecls())) attributeGroupDeclarations.putAll(schema.getAttGroupDecls()); //Collect all elements if (MapUtils.isNotEmpty(schema.getElementDecls())) elementDeclarations.putAll(schema.getElementDecls()); //Collect all complexTypes if (MapUtils.isNotEmpty(schema.getComplexTypes())) complexTypeDeclarations.putAll(schema.getComplexTypes()); } } public void xpath(XSXPath arg0) { } public void elementDecl(XSElementDecl element) { //NOTE in this method only global elements are processed if (element.isGlobal()){ CmsPropertyVisitor contentObjectPropertyVisitor = new CmsPropertyVisitor(builtInAttributes, null, false, false, 0, this); element.visit(contentObjectPropertyVisitor); LocalizableCmsDefinition definition = contentObjectPropertyVisitor.getDefinition(); cacheDefinition(definition); //Check if this element refers to a complex type //In this case complexType Definition should be removed from ComlpexTypeDeclaration map if (definition != null && element.getType()!= null){ String complexTypeRefName = element.getType().getName(); if (complexTypeRefName != null) complexTypeDeclarations.remove(complexTypeRefName); } } } public void modelGroup(XSModelGroup arg0) { } public void modelGroupDecl(XSModelGroupDecl arg0) { } public void wildcard(XSWildcard arg0) { } public void empty(XSContentType arg0) { } public void particle(XSParticle arg0) { } public void simpleType(XSSimpleType arg0) { } public void clear(){ logger.debug("Clearing Definition Visitor"); try { definitionCacheRegion.removeRegion(); } catch (Exception e) { logger.warn("Failed to clear cache region",e); } attributeGroupDeclarations.clear(); complexTypeDeclarations.clear(); elementDeclarations.clear(); builtInAttributes.clear(); topicPropertyPathsPerTaxonomies.clear(); xmlSchemaDefinitionsPerFilename.clear(); xmlSchemaDefinitionURLsPerQName.clear(); multivalueProperties.clear(); contentTypeHierarchy.clear(); internalDefinitionsCache.clear(); definitionsUnderProcess.clear(); objectReferencePropertyDefinitions.clear(); } public void addXMLSchemaDefinitionForFileName(byte[] fileContent,String filename) { logger.debug("Adding schema content for file {}", filename); xmlSchemaDefinitionsPerFilename.put(filename, fileContent); } public void addXMLSchemaDefinitionForFileName(URL definitionURL) { InputStream builtInStream = null; try{ if (definitionURL == null){ logger.warn("Found no XML schema file for definition "); return; } String filename = StringUtils.substringAfterLast(definitionURL.toExternalForm(), CmsConstants.FORWARD_SLASH); builtInStream = definitionURL.openStream(); logger.debug("Adding schema content for file {}", filename); xmlSchemaDefinitionsPerFilename.put(filename, IOUtils.toByteArray(builtInStream)); } catch(Exception e){ logger.error("",e); throw new CmsException(e.getMessage()); } finally{ IOUtils.closeQuietly(builtInStream); } } public void addXMLSchemaDefinitions( Map<String, byte[]> builtInModelAndApiSchemaFiles) { if (MapUtils.isNotEmpty(builtInModelAndApiSchemaFiles)){ logger.debug("Adding schema content for files {}", builtInModelAndApiSchemaFiles.keySet()); xmlSchemaDefinitionsPerFilename.putAll(builtInModelAndApiSchemaFiles); } } public DefinitionCacheRegion getDefinitionCacheRegion() { return definitionCacheRegion; } public void cacheDefinitionWhichIsUnderProcess( LocalizableCmsDefinition definition) { if (definition !=null){ String typeQName = getDefinitionQName(definition); if (! definitionsUnderProcess.contains(typeQName)){ definitionsUnderProcess.add(typeQName); } } } private String getDefinitionQName(LocalizableCmsDefinition definition) { String typeQName = "{"+definition.getQualifiedName().getNamespaceURI()+"}"+definition.getName(); return typeQName; } public boolean isDefinitionUnderProcess(String typeName, String typeNamespace) { return typeName != null && typeNamespace != null && definitionsUnderProcess.contains("{"+typeNamespace+"}"+typeName); } public XSComplexType getComplexType(String complexTypeName){ if (complexTypeName == null){ return null; } return complexTypeDeclarations.get(complexTypeName); } }