pt.webdetails.cpk.CpkEngine.java Source code

Java tutorial

Introduction

Here is the source code for pt.webdetails.cpk.CpkEngine.java

Source

/*!
* Copyright 2002 - 2013 Webdetails, a Pentaho company.  All rights reserved.
*
* This software was developed by Webdetails and is provided under the terms
* of the Mozilla Public License, Version 2.0, or any later version. You may not use
* this file except in compliance with the license. If you need a copy of the license,
* please go to  http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails.
*
* Software distributed under the Mozilla Public License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to
* the license for the specific language governing your rights and limitations.
*/

package pt.webdetails.cpk;

import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.ConfigurationFactory;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import pt.webdetails.cpk.cache.EHCache;
import pt.webdetails.cpk.cache.ICache;
import pt.webdetails.cpk.elements.Element;
import pt.webdetails.cpk.elements.IDataSourceProvider;
import pt.webdetails.cpk.elements.IElement;
import pt.webdetails.cpk.elements.impl.KettleResult;
import pt.webdetails.cpk.elements.impl.KettleResultKey;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class CpkEngine {

    private static Log logger = LogFactory.getLog(CpkEngine.class);
    private static final String DEFAULT_SETTINGS_FILENAME = "cpk.xml";
    private static final String DEFAULT_CACHE_SETTINGS_FILENAME = "ehcache.xml";
    private ICpkEnvironment environment;
    private String settingsFilename;
    private TreeMap<String, IElement> elementsMap;
    private IElement defaultElement;

    private ICache<KettleResultKey, KettleResult> kettleResultCache;

    private String getDefaultCacheName() {
        return CpkEngine.class.getPackage().getName() + ":" + this.getEnvironment().getPluginName();
    }

    private CacheConfiguration getDefaultCacheConfiguration() {
        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setName(this.getDefaultCacheName());
        cacheConfiguration.setMaxEntriesLocalHeap(100);
        cacheConfiguration.setMaxEntriesLocalDisk(10000);
        cacheConfiguration.setTimeToIdleSeconds(0);
        cacheConfiguration.setTimeToLiveSeconds(0);
        cacheConfiguration.overflowToDisk(true);
        cacheConfiguration.diskPersistent(false);
        cacheConfiguration.setDiskExpiryThreadIntervalSeconds(360);
        cacheConfiguration.diskSpoolBufferSizeMB(50);
        cacheConfiguration.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);

        return cacheConfiguration;
    }

    public ICache<KettleResultKey, KettleResult> getKettleResultCache() {
        return this.kettleResultCache;
    }

    private CpkEngine() {
        this.elementsMap = new TreeMap<String, IElement>();
    }

    // singleton
    public static CpkEngine getInstance() {
        return CpkEngineHolder.INSTANCE;
    }

    public synchronized void init(ICpkEnvironment environment) {
        // invalid environment
        if (environment == null) {
            logger.fatal("Failed to initialize CPK Plugin: null environment");
            return;
        }

        // skip initialization, engine was previously initialized
        if (this.environment != null) {
            logger.warn("CPK Plugin '" + this.environment.getPluginName() + "' was already initialized");
            return;
        }

        // initialize engine
        this.environment = environment;
        this.settingsFilename = DEFAULT_SETTINGS_FILENAME;
        this.initializeKettleResultCache();
        this.reload();
    }

    /**
     * Initializes the cache that stores the results of executing kettle elements.
     * First it tries to read the configuration file DEFAULT_CACHE_SETTINGS_FILENAME.
     * If this fails it uses the default hardcoded configuration to initialize the cache.
     */
    private synchronized void initializeKettleResultCache() {
        CacheConfiguration cacheConfiguration;

        InputStream configFile = null;
        try {
            // get configuration from file
            configFile = this.getEnvironment().getContentAccessFactory().getPluginSystemReader("")
                    .getFileInputStream(DEFAULT_CACHE_SETTINGS_FILENAME);
            Configuration cacheManagerConfiguration = ConfigurationFactory.parseConfiguration(configFile);
            Collection<CacheConfiguration> cacheConfigurations = cacheManagerConfiguration.getCacheConfigurations()
                    .values();
            // get one cache configuration, ignore if more are present
            cacheConfiguration = cacheConfigurations.iterator().next();
            // if no name was defined for the cache, use default cache name
            if (cacheConfiguration.getName() == null || cacheConfiguration.getName().isEmpty()) {
                cacheConfiguration.setName(this.getDefaultCacheName());
            }
            logger.debug(this.getEnvironment().getPluginName() + " is using cache configuration from file "
                    + DEFAULT_CACHE_SETTINGS_FILENAME);
        } catch (Exception ioe) {
            // unable to load cache configuration file. Using hardcoded default configuration.
            logger.info("No Eh cache configuration file found " + DEFAULT_CACHE_SETTINGS_FILENAME + ".");
            cacheConfiguration = this.getDefaultCacheConfiguration();
            logger.info(this.getEnvironment().getPluginName() + " is using default hardcoded cache configuration.");
        } finally {
            IOUtils.closeQuietly(configFile);
        }

        this.kettleResultCache = new EHCache<KettleResultKey, KettleResult>(cacheConfiguration);
        this.reload();
    }

    public void reload() {
        logger.info("Initializing CPK Plugin '" + this.environment.getPluginName() + "'");
        long start = System.currentTimeMillis();

        // TODO: check why we need to reload environment
        this.environment.reload();

        // load elements
        this.loadElements();

        if (this.getKettleResultCache() != null) {
            this.getKettleResultCache().clear();
        }

        long end = System.currentTimeMillis();
        logger.info("Finished initialization of CPK PLugin '" + this.environment.getPluginName() + "' in "
                + (end - start) + " ms");
    }

    public ICpkEnvironment getEnvironment() {
        return this.environment;
    }

    // return element or null
    public IElement getElement(String elementId) {
        logger.debug("Getting element '" + elementId + "'");
        return this.elementsMap.get(elementId);
    }

    // return read-only elements collection
    public Collection<IElement> getElements() {
        logger.debug("Getting read-only collection of elements");
        return Collections.unmodifiableCollection(this.elementsMap.values());
    }

    // return read-only elements map < id, element >
    public Map<String, IElement> getElementsMap() {
        logger.debug("Getting read-only map of elements");
        return Collections.unmodifiableMap(this.elementsMap);
    }

    // return default element or null
    public IElement getDefaultElement() {
        logger.debug("Getting default element '" + this.defaultElement.getName() + "'");
        return this.defaultElement;
    }

    // TODO: refactor
    public Status getStatus() {
        if (this.defaultElement != null) {
            return new Status(this.elementsMap, this.defaultElement.getName(), this.environment);
        } else {
            return new Status(this.elementsMap, "", this.environment);
        }
    }

    private void loadElements() {
        try {
            // open settings file
            InputStream is = this.environment.getContentAccessFactory().getPluginSystemReader(null)
                    .getFileInputStream(this.settingsFilename);

            // parse settings file
            SAXReader reader = new SAXReader();
            Document doc = reader.read(is);

            // clean elements map
            this.elementsMap.clear();

            // go through each element type
            List elementTypeNodes = doc.selectNodes("/cpk/elementTypes/elementType");
            for (Object elementTypeNode : elementTypeNodes) {
                Node type = (Node) elementTypeNode;

                // get element type attributes
                String typeName = type.valueOf("./@name");
                String typeClass = type.valueOf("./@class");
                logger.info("Loading '" + typeName + "' elements [" + typeClass + "]");

                // go through each location for elements of that type
                List elementLocations = type.selectNodes("elementLocations/elementLocation");
                for (Object elementLocation : elementLocations) {
                    Node location = (Node) elementLocation;

                    // get location attributes
                    String path = location.valueOf("@path");
                    Boolean isRecursive = Boolean.parseBoolean(location.valueOf("@isRecursive"));
                    String pattern = location.valueOf("@pattern");
                    Boolean adminOnly = Boolean.parseBoolean(location.valueOf("@adminOnly"));

                    // go through each file in that location and load elements
                    Collection<File> files = this.environment.getPluginUtils().getPluginResources(path, isRecursive,
                            pattern);
                    if (files != null) {
                        for (File file : files) {
                            loadElement(typeName, typeClass, file.getAbsolutePath(), adminOnly);
                        }
                    }
                }
            }

            // get default element
            this.defaultElement = findDefaultElement(
                    doc.selectSingleNode("/cpk/elementTypes").valueOf("@defaultElement").toLowerCase());

            // close file
            is.close();
        } catch (IOException e) {
            logger.error("Failed to open settings file '" + this.settingsFilename + "'");
        } catch (DocumentException e) {
            logger.error("Failed to parse settings file '" + this.settingsFilename + "'");
        }
    }

    private void loadElement(String type, String typeClass, String filePath, boolean adminOnly) {
        // id = filename in lowercase
        String id = FilenameUtils.getBaseName(filePath).toLowerCase();

        // skip element if id starts with '_' (private elements)
        if (id.startsWith("_")) {
            logger.debug("Skipped element '" + filePath + "'");
            return;
        }

        logger.info("Loading element '" + filePath + "'");

        // skip element if the id already exists
        if (this.elementsMap.containsKey(id)) {
            logger.warn("Failed: an element '" + id + "' already exists");
            return;
        }

        // skip element if the id is a reserved word
        if (this.environment.getReservedWords().contains(id)) {
            logger.warn("Failed: '" + id + "' is a reserved word");
            return;
        }

        try {
            // create element wrapper
            Element element = (Element) Class.forName(typeClass).newInstance();
            // TODO: using plugin name as id. Should a plugin also have an Id and not just a name?
            String pluginId = this.getEnvironment().getPluginName();
            if (element.init(pluginId, id, type, filePath, adminOnly)) {
                // add element to elements map
                this.elementsMap.put(id, element);
                logger.info("Done " + element.toString());
            }
            // TODO: check if setting the cache should be done in init, passing the cache as an argument.
            if (element instanceof IDataSourceProvider) {
                ((IDataSourceProvider) element).setCache(this.getKettleResultCache());
            }
        } catch (Exception e) {
            logger.error("Failed: missing '" + typeClass + "'");
        }
    }

    private IElement findDefaultElement(String defaultElementId) {
        // check if the default element exists
        if (this.elementsMap.containsKey(defaultElementId)) {
            logger.info("Found default element '" + defaultElementId + "'");
            return this.elementsMap.get(defaultElementId);
        } else {
            logger.info("Didn't find default element '" + defaultElementId + "'");
        }

        // try to find a suitable default element
        for (IElement element : this.elementsMap.values()) {
            if (element.isRenderable()) {
                logger.info("Will use '" + element.getId() + "' as default element");
                return element;
            }
        }

        // no suitable element found
        logger.error("There isn't a default element");
        return null;
    }

    // CpkEngineHolder is loaded on the first execution of CpkEngine.getInstance(), not before
    private static class CpkEngineHolder {
        public static final CpkEngine INSTANCE = new CpkEngine();
    }
}