/*
* 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);
}
}