org.betaconceptframework.astroboa.engine.definition.ContentDefinitionConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.engine.definition.ContentDefinitionConfiguration.java

Source

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

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.CmsRepository;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.context.RepositoryContext;
import org.betaconceptframework.astroboa.engine.definition.visitor.CmsDefinitionVisitor;
import org.betaconceptframework.astroboa.engine.definition.xsom.CmsEntityResolverForValidation;
import org.betaconceptframework.astroboa.engine.definition.xsom.CmsXsomParserFactory;
import org.betaconceptframework.astroboa.util.CmsConstants;
import org.betaconceptframework.astroboa.util.CmsConstants.CmsMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.xml.xsom.XSSchema;
import com.sun.xml.xsom.parser.XSOMParser;

/**
 * Class responsible to load Content Definition Configuration settings.
 * Content Definition configuration contains xml file for content definition information
 * and a properties file containing flags for action on content definition information.
 * For now content definition information is reloaded only if appropriate flag is set to true and
 * at the same time content definition file has been Modified
 * 
 * @author Gregory Chomatas (gchomatas@betaconcept.com)
 * @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
 * 
 */
public class ContentDefinitionConfiguration {

    private PropertiesConfiguration configuration;

    //Key is repository id
    private Map<String, List<FileConfiguration>> definitionFileConfigurations = new HashMap<String, List<FileConfiguration>>();

    private CmsXsomParserFactory cmsXsomParserFactory;
    private CmsDefinitionVisitor definitionVisitor;

    private final Logger logger = LoggerFactory.getLogger(ContentDefinitionConfiguration.class);

    //Default value is production
    private CmsMode cmsMode;

    private List<String> builtinDefinitionSchemas;

    public void setBuiltinDefinitionSchemas(List<String> builtinDefinitionSchemas) {
        this.builtinDefinitionSchemas = builtinDefinitionSchemas;
    }

    public void setCmsXsomParserFactory(CmsXsomParserFactory cmsXsomParserFactory) {
        this.cmsXsomParserFactory = cmsXsomParserFactory;
    }

    public void setDefinitionVisitor(CmsDefinitionVisitor definitionVisitor) {
        this.definitionVisitor = definitionVisitor;
    }

    private void loadConfigurationFiles(CmsRepository associatedRepository, boolean logWarningIfNoXSDFileFound) {

        //Load BetaConcept Definition files from repository home directory
        //which exists in RepositoryContextImpl
        try {

            File[] schemaFiles = retrieveXmlSchemaFiles(associatedRepository, logWarningIfNoXSDFileFound);

            //Create a file configuration for each xsd file
            //This is done in order to track any changes made at runtime to XSD
            //in order to reload definition
            definitionFileConfigurations.put(associatedRepository.getId(), new ArrayList<FileConfiguration>());

            if (ArrayUtils.isEmpty(schemaFiles) && logWarningIfNoXSDFileFound) {
                logger.warn("Found no definition schema files for repository " + associatedRepository);
            } else {
                for (File defFile : schemaFiles) {
                    try {
                        logger.debug("Loading definition file {} for repository {}", defFile.getAbsolutePath(),
                                associatedRepository.getId());

                        XMLConfiguration fileConfiguration = new XMLConfiguration(defFile);
                        fileConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy());
                        definitionFileConfigurations.get(associatedRepository.getId()).add(fileConfiguration);
                    } catch (Exception e) {
                        logger.error("Error loading definition file " + defFile.getAbsolutePath()
                                + " for repository " + associatedRepository.getId()
                                + "Most probably, it is not a valid XML file. All other definitions will be loaded",
                                e);

                        //Load an empty xml configuration
                        XMLConfiguration fileConfiguration = new XMLConfiguration();
                        definitionFileConfigurations.get(associatedRepository.getId()).add(fileConfiguration);
                    }
                }
            }
        } catch (Throwable e) {
            throw new CmsException(e);
        }
    }

    private File[] retrieveXmlSchemaFiles(CmsRepository associatedRepository, boolean logWarningIfNoXSDFileFound) {
        if (associatedRepository == null
                || StringUtils.isBlank(associatedRepository.getRepositoryHomeDirectory())) {
            throw new CmsException("Unable to locate repository home directory."
                    + (associatedRepository == null ? "No associated repository to current thread"
                            : "Undefined repository home dir for repository " + associatedRepository.getId()));
        }

        //Directory where definition xsd files are kept is defined from
        //repository home dir + directory name provided in content-definition.properties file
        String contentDefinitionSchemaPath = getDefinitionHomeDirPath(associatedRepository);

        File contentDefinitionSchemaDir = new File(contentDefinitionSchemaPath);

        //Check if directory exists
        if (!contentDefinitionSchemaDir.exists()) {

            if (logWarningIfNoXSDFileFound) {
                logger.warn("Unable to locate schema home directory {}. Only built in schemas will be loaded",
                        contentDefinitionSchemaPath);
            }
            return new File[] {};
        }

        //Load all xsd files
        return contentDefinitionSchemaDir.listFiles(new XsdFilter());
    }

    private String getDefinitionHomeDirPath(CmsRepository associatedRepository) {
        String contentDefinitionSchemaPath = associatedRepository.getRepositoryHomeDirectory() + File.separator
                + configuration.getString(CmsConstants.BETACONCEPT_CONTENT_DEFINITION_SCHEMA_DIR, "");
        return contentDefinitionSchemaPath;
    }

    public void loadDefinitionToCache() throws Exception {

        RepositoryContext repositoryContext = AstroboaClientContextHolder.getRepositoryContextForActiveClient();

        if (repositoryContext == null || repositoryContext.getCmsRepository() == null) {
            //No repository context found. Do nothing
            logger.warn("Unable to load definition files. No repository context found");
            return;
        }

        if (mustReloadDefinitionFiles(repositoryContext.getCmsRepository())) {
            logger.debug("At least one definition file has been changed. Reloading takes place");
            refreshContentDefinition(repositoryContext.getCmsRepository());
        }

    }

    private boolean isCmsInProductionMode() {
        return CmsMode.production == cmsMode;
    }

    private void refreshContentDefinition(CmsRepository associatedRepository) throws Exception {

        logger.debug("Reloading definition files");

        //Clear cache
        definitionVisitor.clear();

        try {

            if (definitionFileConfigurations == null) {
                definitionFileConfigurations = new HashMap<String, List<FileConfiguration>>();
            }

            //This ensures that warning for empty XSD directory is issued only once
            boolean logWarningIfNoXSDFileFound = definitionFileConfigurations
                    .get(associatedRepository.getId()) == null;

            if (definitionFileConfigurations.containsKey(associatedRepository.getId())) {
                //remove existing file configurations in order to reload REPOSITORY schemas
                //thus loading any new file added
                definitionFileConfigurations.remove(associatedRepository.getId());
            }

            loadConfigurationFiles(associatedRepository, logWarningIfNoXSDFileFound);

            List<FileConfiguration> repositoryDefinitionFileConfigurations = definitionFileConfigurations
                    .get(associatedRepository.getId());

            XSOMParser xsomParser = createXsomParser();

            //Feed parser with user   defined schemas
            feedParserWithUserDefinedSchemas(xsomParser, repositoryDefinitionFileConfigurations);

            //Feed parser with builtin schemas
            feedParserWithBuiltInSchemas(xsomParser);

            //Load schemas to Definitions
            generateDefinitions(xsomParser);

        } catch (Exception e) {
            if (definitionVisitor != null) {
                definitionVisitor.clear();
            }

            //Something went wrong. At least load built in schemas.
            try {
                XSOMParser xsomParser = createXsomParser();

                //Feed parser with builtin schemas
                feedParserWithBuiltInSchemas(xsomParser);

                generateDefinitions(xsomParser);
            } catch (Exception e1) {
                logger.error("Repository " + AstroboaClientContextHolder.getActiveRepositoryId()
                        + " - Loading schemas to Astroboa Definitions failed.", e);
                logger.error("Repository " + AstroboaClientContextHolder.getActiveRepositoryId()
                        + " - Loading built in only schemas as a fallback safe mechanism also failed.", e1);

                if (definitionVisitor != null) {
                    definitionVisitor.clear();
                }

                throw e;
            }

            logger.warn("Repository " + AstroboaClientContextHolder.getActiveRepositoryId()
                    + " - Loading schemas to Astroboa Definitions failed. Check error stack trace for more details. Neverthelss built in only schemas have been successfully loaded",
                    e);
        }

    }

    private void generateDefinitions(XSOMParser xsomParser) throws SAXException, Exception {

        Map<String, XSSchema> schemas = new HashMap<String, XSSchema>();

        int index = 1;

        final Collection<XSSchema> schemaSet = xsomParser.getResult().getSchemas();
        for (XSSchema schema : schemaSet) {
            final String targetNamespace = schema.getTargetNamespace();
            if (StringUtils.isBlank(targetNamespace)) {
                //Put an index as a key
                schemas.put(String.valueOf(index++), schema);
            } else if (!schemas.containsKey(targetNamespace)) {
                schemas.put(schema.getTargetNamespace(), schema);
            }
            schema.visit(definitionVisitor);
        }

        //Definition Visitor will put in cache all definitions
        //Definition cache will obtain repository context to 
        //correctly place definitions according to repository
        definitionVisitor.createContentDefintions();

    }

    private void feedParserWithBuiltInSchemas(XSOMParser xsomParser) {
        if (CollectionUtils.isNotEmpty(builtinDefinitionSchemas)) {
            for (String builtinDefinitionSchema : builtinDefinitionSchemas) {

                try {
                    URL resource = this.getClass().getResource(builtinDefinitionSchema);

                    //Do not parse astroboa-api.x.xsd.
                    if (!builtinDefinitionSchema.contains(CmsConstants.ASTROBOA_API_SCHEMA_FILENAME)) {
                        xsomParser.parse(resource);
                    }
                    definitionVisitor.addXMLSchemaDefinitionForFileName(resource);
                } catch (Exception e) {
                    throw new CmsException("Parse error for definition file " + builtinDefinitionSchema, e);
                }
            }
        }
    }

    private void feedParserWithUserDefinedSchemas(XSOMParser xsomParser,
            List<FileConfiguration> repositoryDefinitionFileConfigurations) {

        List<String> absolutePathsOfFilesToExclude = new ArrayList<String>();

        boolean feedParser = true;

        while (feedParser) {

            feedParser = false;

            //Create XSOM Parser
            if (xsomParser == null) {
                xsomParser = createXsomParser();
            }

            for (FileConfiguration fileConf : repositoryDefinitionFileConfigurations) {
                if (fileConf.getFile() == null) {
                    logger.warn(
                            "Found empty file configuration. This means that one of the XSD provided is not a valid xml. Parsing will continue for the rest of the xsds");
                } else {

                    String absolutePath = fileConf.getFile().getAbsolutePath();

                    if (!absolutePathsOfFilesToExclude.contains(absolutePath)) {

                        logger.debug("Reloadding and parsing file {}", absolutePath);

                        try {
                            fileConf.reload();
                            xsomParser.parse(fileConf.getFile());
                            definitionVisitor.addXMLSchemaDefinitionForFileName(
                                    FileUtils.readFileToByteArray(fileConf.getFile()),
                                    StringUtils.substringAfterLast(absolutePath, File.separator));
                        } catch (Exception e) {
                            //Just issue a warning
                            logger.warn("Parse error for definition file " + absolutePath
                                    + " This file is excluded from building Astroboa Definitions", e);

                            //we need to feed parser again since it sets an error flag to true 
                            //and does not produce any schemas at all.
                            feedParser = true;
                            absolutePathsOfFilesToExclude.add(absolutePath);
                            xsomParser = null;
                            definitionVisitor.clear();
                            break;
                        }
                    }
                }
            }
        }

    }

    private XSOMParser createXsomParser() {
        XSOMParser xsomParser;
        xsomParser = cmsXsomParserFactory.createXsomParser();
        return xsomParser;
    }

    public void setConfigurationFile(String configurationFile) {
        if (configurationFile != null)
            try {
                configuration = new PropertiesConfiguration(configurationFile);
                configuration.setReloadingStrategy(new FileChangedReloadingStrategy());

                final String cmsModeFromFile = configuration.getString(CmsConstants.CMS_MODE,
                        CmsMode.production.toString());
                //Set CmsMode
                if (cmsModeFromFile != null)
                    cmsMode = CmsMode.valueOf(cmsModeFromFile.toLowerCase());
                else
                    cmsMode = CmsMode.production;

            } catch (Exception e) {
                logger.warn("Unable to load content definition properties file", e);
            }
    }

    private boolean mustReloadDefinitionFiles(CmsRepository associatedRepository) {

        //Check reload flag with default value set to false
        if (isCmsInProductionMode()) {
            if (definitionFileConfigurations != null
                    && CollectionUtils.isNotEmpty(definitionFileConfigurations.get(associatedRepository.getId()))) {
                return false;
            } else {
                return true;
            }
        }
        //Debug mode
        else if (configuration != null) {

            final boolean reloadDefinitionFiles = configuration
                    .getBoolean(CmsConstants.RELOAD_CONTENT_DEFINITION_FILE, false);

            //Reloading has been disabled but no definition files have ever been loaded
            //It may be the first time
            if (!reloadDefinitionFiles && (definitionFileConfigurations == null
                    || !definitionFileConfigurations.containsKey(associatedRepository.getId()))) {
                return true;
            }

            if (reloadDefinitionFiles && definitionFileConfigurations != null) {

                List<FileConfiguration> repositoryFileConfigurations = definitionFileConfigurations
                        .get(associatedRepository.getId());

                if (repositoryFileConfigurations == null) {
                    return true;
                }

                //Check if any file has been added or removed
                //Get files that exist in definition directory
                File[] definitionSchemaFiles = retrieveXmlSchemaFiles(associatedRepository, false);

                //At least one definition was added or removed
                if (repositoryFileConfigurations.size() != definitionSchemaFiles.length) {
                    return true;
                }

                //Reload flag is set to true. Check that file is changed
                boolean atLeastOneFileChanged = false;
                for (FileConfiguration fileConf : repositoryFileConfigurations) {
                    if (fileConf.getReloadingStrategy().reloadingRequired()) {
                        fileConf.getReloadingStrategy().reloadingPerformed();
                        logger.info("Definition file {} has been modified and will be reloaded for repository {}",
                                fileConf.getFileName(), associatedRepository.getId());
                        atLeastOneFileChanged = true;
                        break;
                    }
                }

                return atLeastOneFileChanged;

            }

        }

        return false;
    }

    public boolean definitionFileForActiveRepositoryIsValid(String definitionToBeValidated,
            String definitionFileName) throws Exception {

        if (StringUtils.isBlank(definitionToBeValidated) || StringUtils.isBlank(definitionFileName)) {
            return false;
        }

        RepositoryContext repositoryContext = AstroboaClientContextHolder.getRepositoryContextForActiveClient();

        if (repositoryContext == null || repositoryContext.getCmsRepository() == null) {
            //No repository context found. Do nothing
            logger.warn("Unable to validate definition files. No repository context found");
            return false;
        }

        final CmsRepository associatedRepository = repositoryContext.getCmsRepository();

        File[] existingDefinitionFiles = retrieveXmlSchemaFiles(associatedRepository, false);

        CmsEntityResolverForValidation entityResolverForValidation = null;
        XSOMParser xsomParser = null;
        List<InputStream> openStreams = new ArrayList<InputStream>();

        try {
            entityResolverForValidation = createEntityResolverForValidation(existingDefinitionFiles,
                    definitionToBeValidated, definitionFileName);

            //Feed parser with user defined schemas
            xsomParser = cmsXsomParserFactory.createXsomParserForValidation(entityResolverForValidation);

            //Feed parser with builtin schemas
            if (MapUtils.isNotEmpty(entityResolverForValidation.getDefinitionSources())) {
                for (Entry<String, String> definitionSource : entityResolverForValidation.getDefinitionSources()
                        .entrySet()) {

                    if (!StringUtils.equals(CmsConstants.ASTROBOA_MODEL_SCHEMA_FILENAME_WITH_VERSION,
                            definitionSource.getKey())) {
                        try {
                            InputStream openStream = IOUtils.toInputStream(definitionSource.getValue(), "UTF-8");
                            openStreams.add(openStream);

                            InputSource is = new InputSource(openStream);
                            is.setSystemId(definitionSource.getKey());

                            xsomParser.parse(is);
                        } catch (Exception e) {
                            throw new CmsException("Parse error for definition " + definitionSource.getKey(), e);
                        }
                    }
                }
            }

            //Force parser to create XSD schemas. This way more errors can be detected
            xsomParser.getResult().getSchemas();

        } catch (Exception e) {
            throw e;
        } finally {
            xsomParser = null;

            if (entityResolverForValidation != null) {
                entityResolverForValidation.clearDefinitions();
            }

            if (!openStreams.isEmpty()) {
                for (InputStream is : openStreams) {
                    IOUtils.closeQuietly(is);
                }
            }
        }

        return true;

    }

    private CmsEntityResolverForValidation createEntityResolverForValidation(File[] existingDefinitionFiles,
            String definitionToBeValidated, String definitionFileName) throws IOException {

        CmsEntityResolverForValidation entityResolver = new CmsEntityResolverForValidation();

        boolean definitionToBeValidatedHasBeenAdded = false;

        for (File existingDefinitionFile : existingDefinitionFiles) {

            if (StringUtils.equals(definitionFileName, existingDefinitionFile.getName())) {
                entityResolver.addDefinition(definitionFileName, definitionToBeValidated);
                definitionToBeValidatedHasBeenAdded = true;
            } else {
                entityResolver.addExternalDefinition(existingDefinitionFile);
            }
        }

        if (!definitionToBeValidatedHasBeenAdded) {
            //New definition
            entityResolver.addDefinition(definitionFileName, definitionToBeValidated);
        }

        return entityResolver;
    }

}