org.pentaho.platform.plugin.services.metadata.MetadataDomainRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.plugin.services.metadata.MetadataDomainRepository.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU General Public License, version 2 as published by the Free Software 
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this 
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html 
 * or from the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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 General Public License for more details.
 * 
 * Copyright 2007 - 2008 Pentaho Corporation.  All rights reserved.
 *  
 */
package org.pentaho.platform.plugin.services.metadata;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.pentaho.metadata.model.Domain;
import org.pentaho.metadata.repository.DomainAlreadyExistsException;
import org.pentaho.metadata.repository.DomainIdNullException;
import org.pentaho.metadata.repository.DomainStorageException;
import org.pentaho.metadata.repository.FileBasedMetadataDomainRepository;
import org.pentaho.metadata.util.LocalizationUtil;
import org.pentaho.metadata.util.XmiParser;
import org.pentaho.platform.api.engine.IAclHolder;
import org.pentaho.platform.api.engine.IFileFilter;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.ISolutionFile;
import org.pentaho.platform.api.engine.ISolutionFilter;
import org.pentaho.platform.api.repository.ISolutionRepository;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.metadata.MetadataPublisher;

import org.pentaho.platform.plugin.services.metadata.messages.Messages;

import org.pentaho.platform.util.messages.LocaleHelper;

/**
 * This is the platform implementation of the IMetadataDomainRepository.
 * 
 * TODO: Update this class to use CacheControl, getting created per session per
 * Marc's input
 * 
 * @author Will Gorman (wgorman@pentaho.com)
 */
public class MetadataDomainRepository extends FileBasedMetadataDomainRepository {

    private static final String LEGACY_LOCATION = "__LEGACY_LOCATION_"; //$NON-NLS-1$

    protected final Log logger = LogFactory.getLog(MetadataDomainRepository.class);

    private static String LEGACY_XMI_FILENAME = "metadata.xmi"; //$NON-NLS-1$

    private static String LEGACY_XMI_FILE_EXTENSION = ".xmi";

    public static String PROPERTIES_FILE_EXTENSION = ".properties"; //$NON-NLS-1$

    public static final int[] ACCESS_TYPE_MAP = new int[] { IAclHolder.ACCESS_TYPE_READ,
            IAclHolder.ACCESS_TYPE_WRITE, IAclHolder.ACCESS_TYPE_UPDATE, IAclHolder.ACCESS_TYPE_DELETE,
            IAclHolder.ACCESS_TYPE_ADMIN, IAclHolder.ACCESS_TYPE_ADMIN };

    private static final String DOMAIN_FOLDER = "system/metadata/domains"; //$NON-NLS-1$

    //
    // Use this approach for accessing the session object when
    // updated to use the CacheManager
    //
    // private IPentahoSession session;
    //
    // public void setSession(IPentahoSession session) {
    // this.session = session;
    // }
    //
    // public IPentahoSession getSession() {
    // return session;
    // }

    public IPentahoSession getSession() {
        return PentahoSessionHolder.getSession();
    }

    protected File getDomainsFolder() {
        String domainsFolder = PentahoSystem.getApplicationContext().getSolutionPath(DOMAIN_FOLDER);

        File folder = new File(domainsFolder);
        return folder;
    }

    public Set<String> getDomainIds() {
        // double check that all the legacy domains are loaded
        // this is necessary because the reloading of domains might have been done by 
        // a user who might not have permissions to all solutions

        // the super call is made to avoid having it called twice, the logic in getDomainIds
        // calls reload if the map is empty.
        synchronized (domains) {
            super.reloadDomains();
            reloadLegacyDomains(false);
        }
        return super.getDomainIds();
    }

    public void reloadDomains() {
        synchronized (domains) {
            // first reload new metadata domains
            super.reloadDomains();
            // then populate the legacy domains
            reloadLegacyDomains(true);
        }
    }

    public void removeDomain(String domainId) {
        synchronized (domains) {
            // determine if domain is legacy or not
            Domain domain = domains.get(domainId);

            if (domain.getProperty(LEGACY_LOCATION) != null) {
                // this is an xmi based domain, remove it
                removeLegacyDomain(domainId);
            } else {
                super.removeDomain(domainId);
            }
        }
    }

    public Domain getDomain(String id) {
        Domain domain = super.getDomain(id);
        if (domain == null) {
            // reload legacy domains if not found
            reloadLegacyDomains(false);
            return super.getDomain(id);
        } else {
            return domain;
        }
    }

    private void removeLegacyDomain(String domainId) {
        ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());
        repo.removeSolutionFile(domainId);
        domains.remove(domainId);
    }

    @SuppressWarnings("unchecked")
    private void reloadLegacyDomains(boolean overwrite) {
        // also load the XMI domains
        ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());
        // filter the repository looking for XMI files
        Document doc = repo.getSolutionTree(ISolutionRepository.ACTION_EXECUTE, new ISolutionFilter() {
            public boolean keepFile(ISolutionFile solutionFile, int actionOperation) {
                return solutionFile.isDirectory() || solutionFile.getExtension().equalsIgnoreCase("xmi") //$NON-NLS-1$
                        || solutionFile.getExtension().equalsIgnoreCase(".xmi"); //$NON-NLS-1$
            }
        });
        int allSuccess = MetadataPublisher.NO_ERROR;
        try {
            // first look for metadata.xmi leaves in solution roots
            // e.g. /tree/pentaho-solutions/somedir/metadata.xmi
            List nodes = doc.selectNodes("/tree/branch/branch/leaf[@isDir='false']"); //$NON-NLS-1$
            for (Object obj : nodes) {
                Element node = (Element) obj;
                Element pathNode = (Element) (node).selectSingleNode("path"); //$NON-NLS-1$
                String path = pathNode.getText();
                // remove the first node
                int idx = path.indexOf(ISolutionRepository.SEPARATOR, 1);
                path = path.substring(idx + 1);
                // make sure the filename is 'metadata.xmi'
                if (path.endsWith(LEGACY_XMI_FILENAME)) {
                    if (overwrite || !domains.containsKey(path)) {
                        allSuccess |= loadMetadata(path);
                    }
                }
            }

            // now look for */resources/metadata/*.xmi
            // e.g. /tree/pentaho-solutions/some/resources/metadata/*.xmi
            nodes = doc.selectNodes("/tree/branch/branch/branch/branch/leaf[@isDir='false']"); //$NON-NLS-1$
            for (Object obj : nodes) {
                // build up the directory path
                Element node = (Element) obj;
                String dir = node.getParent().attributeValue("id"); //$NON-NLS-1$
                // make sure the parent branch ends with /resources/metadata
                if (dir.endsWith("/resources/metadata")) { //$NON-NLS-1$
                    Element pathNode = (Element) (node).selectSingleNode("path"); //$NON-NLS-1$        
                    String path = pathNode.getText();
                    // remove the first node
                    int idx = path.indexOf(ISolutionRepository.SEPARATOR, 1);
                    path = path.substring(idx + 1);
                    if (overwrite || !domains.containsKey(path)) {
                        allSuccess |= loadMetadata(path);
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private int loadMetadata(final String resourceName) {
        int result = MetadataPublisher.NO_ERROR;
        InputStream xmiInputStream;
        xmiInputStream = null;
        ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());
        if (repo.resourceExists(resourceName, ISolutionRepository.ACTION_EXECUTE)) {
            try {
                xmiInputStream = repo.getResourceInputStream(resourceName, true,
                        ISolutionRepository.ACTION_EXECUTE);
                Domain domain = new XmiParser().parseXmi(xmiInputStream);
                domain.setProperty(LEGACY_LOCATION, resourceName);
                // The domain key is the full path and name of the XMI file within the solution repository
                // e.g. mysolution/resources/metadata/mymodel.xmi or
                // mysolution/metadata.xmi
                domain.setId(resourceName);
                importLocalizations(domain, resourceName);
                domains.put(resourceName, domain);
            } catch (Throwable t) {
                logger.error(Messages.getString("MetadataPublisher.ERROR_0001_COULD_NOT_LOAD", resourceName), t); //$NON-NLS-1$
                throw new RuntimeException(Messages.getString("MetadataPublisher.ERROR_0001_COULD_NOT_LOAD"), t); //$NON-NLS-1$
            } finally {
                if (xmiInputStream != null) {
                    try {
                        xmiInputStream.close();
                    } catch (IOException ex) {
                        logger.error(
                                Messages.getString("MetadataPublisher.ERROR_0001_COULD_NOT_LOAD", resourceName), //$NON-NLS-1$
                                ex);
                        throw new RuntimeException(
                                Messages.getString("MetadataPublisher.ERROR_0001_COULD_NOT_LOAD"), ex); //$NON-NLS-1$
                    }
                }
            }
        } else {
            return result;
        }
        return result;
    }

    public void storeDomain(Domain domain, boolean overwrite)
            throws DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {
        synchronized (domains) {
            if (domain.getId() == null) {
                throw new DomainIdNullException(org.pentaho.metadata.messages.Messages
                        .getErrorString("IMetadataDomainRepository.ERROR_0001_DOMAIN_ID_NULL")); //$NON-NLS-1$
            }

            if (!overwrite && domains.get(domain.getId()) != null) {
                throw new DomainAlreadyExistsException(org.pentaho.metadata.messages.Messages.getErrorString(
                        "IMetadataDomainRepository.ERROR_0002_DOMAIN_OBJECT_EXISTS", domain.getId())); //$NON-NLS-1$
            }

            if (domain.getProperty(LEGACY_LOCATION) != null) {
                storeLegacyDomain(domain, overwrite);
            } else {
                super.storeDomain(domain, overwrite);
            }
        }
    }

    private void storeLegacyDomain(Domain domain, boolean overwrite)
            throws DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {

        if (domain.getId() == null) {
            throw new DomainIdNullException(org.pentaho.metadata.messages.Messages
                    .getErrorString("IMetadataDomainRepository.ERROR_0001_DOMAIN_ID_NULL")); //$NON-NLS-1$
        }

        // only allow editing vs. creation
        if (!overwrite) {
            throw new DomainAlreadyExistsException(org.pentaho.metadata.messages.Messages
                    .getErrorString("IMetadataDomainRepository.ERROR_0002_DOMAIN_OBJECT_EXISTS", domain.getId())); //$NON-NLS-1$
        }

        XmiParser parser = new XmiParser();
        String xmi = parser.generateXmi(domain);
        try {
            String domainId = domain.getId();
            // parse the domain id into a path and filename
            // e.g. domainId = mysolution/resources/metadata/mymodel.xmi
            // path = mysolution/resources/metadata
            // fileName = mymodel.xmi
            int idx = domainId.lastIndexOf(ISolutionRepository.SEPARATOR);
            String path = domainId.substring(0, idx);
            String fileName = domainId.substring(idx + 1);
            ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());
            String solutionPath = PentahoSystem.getApplicationContext().getSolutionPath(""); //$NON-NLS-1$
            repo.addSolutionFile(solutionPath, path, fileName, xmi.getBytes(LocaleHelper.getSystemEncoding()),
                    true);

            // adds the domain to the domains list
            domains.put(domain.getId(), domain);

        } catch (Exception e) {
            throw new DomainStorageException(Messages.getErrorString(
                    "MetadataDomainRepository.ERROR_0006_FAILED_TO_STORE_LEGACY_DOMAIN", domain.getId()), e); //$NON-NLS-1$
        }
    }

    /**
     * Returns the File object that is the absolute path to the folder that file
     * name is in.
     * 
     * @param String
     *           filename
     * @return File File object of the parent folder of filename.
     **/
    protected ISolutionFile getFolder(String filename) throws DomainStorageException {

        ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());
        ISolutionFile solutionFile = repo.getSolutionFile(filename, ISolutionRepository.ACTION_EXECUTE);

        if (solutionFile != null) {
            return solutionFile.retrieveParent();
        } else {
            return null;
        }
    }

    /**
     * Returns the localization portion of the passed fileName.
     * 
     * @param fileName
     *           Example: metadata_EN_US.properties
     * @return String example: EN_US
     */
    protected String getLocaleFromPropertyFilename(String filename, String prefix) throws DomainStorageException {

        String locale = "";
        if (!StringUtils.isEmpty(filename)) {
            int startIndex = filename.lastIndexOf(ISolutionRepository.SEPARATOR);

            // increment our start index if we found the last slash
            if (startIndex >= 1) {
                startIndex++;
            } else {
                startIndex = 0;
            }

            int endIndex = filename.indexOf(PROPERTIES_FILE_EXTENSION);

            if (startIndex >= endIndex) {
                throw new DomainStorageException(Messages.getErrorString(
                        "MetadataDomainRepository.ERROR_0010_FAILED_TO_RESOLVE_LOCALE", filename), null); //$NON-NLS-1$
            }

            locale = filename.substring(startIndex + prefix.length() + 1, endIndex);
        }
        return locale;
    }

    /**
     * Returns a list of ISolutionFiles. These files are the property files that
     * are associated with the passed resource name
     * 
     * @param resourceName
     *           The name of the resource that has associated property files
     * @return SolutionFile[] Collection of associated property files.
     * @throws DomainStorageException
     */
    protected ISolutionFile[] getLocalePropertyFiles(String resourceName) throws DomainStorageException {

        try {

            // get the prefix of the xmiResource file
            int lastSlash = resourceName.lastIndexOf(ISolutionRepository.SEPARATOR);
            String filenamePrefix = resourceName.substring((lastSlash > 0 ? lastSlash + 1 : 0),
                    resourceName.indexOf(LEGACY_XMI_FILE_EXTENSION));

            // the parent file is the folder that fileName is in
            ISolutionFile dir = getFolder(resourceName);

            // create the filter and get a list of files - no path to them
            IFileFilter filter = new PropertyFilesFilter(filenamePrefix);
            ISolutionFile[] fileNames = dir.listFiles(filter);

            // if we don't have a list of files
            if (fileNames == null) {

                // create an empty one
                fileNames = new ISolutionFile[0];
            }

            return fileNames;
        } catch (Exception e) {
            throw new DomainStorageException(Messages.getErrorString(
                    "MetadataDomainRepository.ERROR_0007_FAILED_TO_GET_LOCALIZATION_FILE_LIST", resourceName), e); //$NON-NLS-1$ 
        }
    }

    /**
     * Creates a File filter for discovering localization files.
     */
    private class PropertyFilesFilter implements IFileFilter {

        private String propertiesFilePrefix = null;

        /**
         * Creates the filter with the passed String as the pattern the file names
         * are to start with.
         * 
         * @param propertiesFilePrefix
         */
        public PropertyFilesFilter(String propertiesFilePrefix) {
            this.propertiesFilePrefix = propertiesFilePrefix + "_";
        }

        /**
         * Return s a list of files that start with propertiesFilePrefix and end
         * with PROPERTIES_FILE_EXTENSION.
         */
        public boolean accept(ISolutionFile iSolutionfile) {
            return iSolutionfile.getFileName().startsWith(propertiesFilePrefix)
                    && iSolutionfile.getFileName().endsWith(PROPERTIES_FILE_EXTENSION);
        }
    }

    /**
     * Imports the localizations for the passed resourceName into the domain. the
     * property files should be located in the same folder as resourceName
     * following this naming pattern.
     * 
     * If resourceName = "bi-developers/resources/metadata.xmi"
     * 
     * then a United States English property file for it would be
     * "bi-developers/resources/metadata_en_US.properties
     * 
     * Both file names share the string "metadata".
     * 
     * @param domain
     * @param resourceName
     * @throws DomainStorageException
     */
    protected void importLocalizations(Domain domain, String resourceName) throws DomainStorageException {

        // the parent file is the folder that fileName is in
        ISolutionFile[] propertiesFiles = getLocalePropertyFiles(resourceName);

        if (propertiesFiles != null) {
            LocalizationUtil localizationUtil = new LocalizationUtil();

            String locale = null;
            String propertiesFileNamePrefix = getPropertiesFilePrefix(resourceName);
            ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, getSession());

            for (ISolutionFile propertyFile : propertiesFiles) {
                InputStream inputStream = null;
                try {
                    // We need to get the resource input stream by passing the first
                    // parameter as either following examples
                    //     solution-folder/metadata_en_US.properties
                    //     solution-folder/resources/metadata/mymodel_en_US.properties
                    // passing false in the second parameter means to get me the file
                    // as named in parameter 1 - do not manipulate the file name I passeds
                    inputStream = repo.getResourceInputStream(
                            propertyFile.getSolutionPath()
                                    .replaceFirst(String.valueOf(ISolutionRepository.SEPARATOR), "")
                                    + ISolutionRepository.SEPARATOR + propertyFile.getFileName(),
                            false, ISolutionRepository.ACTION_EXECUTE);
                    Properties properties = new Properties();
                    properties.load(inputStream);
                    if (properties.isEmpty()) {
                        String message = Messages.getErrorString(
                                "MetadataDomainRepository.ERROR_0008_EMPTY_LOCALIZATION_PROPERTY_FILE", //$NON-NLS-1$
                                propertyFile.getFileName());
                        DomainStorageException dse = new DomainStorageException(message, null);
                        throw dse;
                    }
                    locale = getLocaleFromPropertyFilename(propertyFile.getFileName(), propertiesFileNamePrefix);
                    if (logger.isDebugEnabled()) {
                        List<String> messages = localizationUtil.analyzeImport(domain, properties, locale);
                        for (String message : messages) {
                            logger.debug(message);
                        }
                    }

                    localizationUtil.importLocalizedProperties(domain, properties, locale);
                } catch (FileNotFoundException fnfe) {
                    throw new DomainStorageException(Messages.getErrorString(
                            "MetadataDomainRepository.ERROR_0009_EXCEPTION_READING_LOCALIZATION_PROPERTY_FILE",
                            propertyFile.getFileName()), fnfe);
                } catch (IOException ioe) {
                    throw new DomainStorageException(Messages.getErrorString(
                            "MetadataDomainRepository.ERROR_0009_EXCEPTION_READING_LOCALIZATION_PROPERTY_FILE",
                            propertyFile.getFileName()), ioe);
                } finally {
                    try {
                        inputStream.close();
                    } catch (IOException ioe) {
                        throw new DomainStorageException(Messages.getErrorString(
                                "MetadataDomainRepository.ERROR_0009_EXCEPTION_READING_LOCALIZATION_PROPERTY_FILE",
                                propertyFile.getFileName()), ioe);
                    }
                }
            }
        }
    }

    /**
     * Returns the prefix that the corresponding properties file should have.
     * 
     * @param String
     *           filename e.g. "/steel-wheels/metadata.xmi"
     * @return String e.g., "metadata"
     */
    protected String getPropertiesFilePrefix(String filename) throws DomainStorageException {

        int beginIndex = filename.lastIndexOf('/');

        // increment since we found a space
        if (beginIndex > 0) {
            beginIndex++;
        }

        int endIndex = filename.indexOf(".xmi");

        if (endIndex == 0) {
            return filename.substring(endIndex);
        } else {
            if (beginIndex >= endIndex) {
                throw new DomainStorageException(Messages.getErrorString(
                        "MetadataDomainRepository.ERROR_0011_FAILED_TO_RESOLVE_PREFIX_FOR_PROPERTY_FILE", filename), //$NON-NLS-1$
                        null);
            }
            return filename.substring(beginIndex, endIndex);
        }
    }
}