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

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.plugin.services.metadata.PentahoMetadataDomainRepository.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 2006 - 2013 Pentaho Corporation.  All rights reserved.
 */

package org.pentaho.platform.plugin.services.metadata;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.metadata.model.Domain;
import org.pentaho.metadata.model.LogicalModel;
import org.pentaho.metadata.model.concept.IConcept;
import org.pentaho.metadata.repository.DomainAlreadyExistsException;
import org.pentaho.metadata.repository.DomainIdNullException;
import org.pentaho.metadata.repository.DomainStorageException;
import org.pentaho.metadata.repository.IMetadataDomainRepository;
import org.pentaho.metadata.util.LocalizationUtil;
import org.pentaho.metadata.util.XmiParser;
import org.pentaho.platform.api.engine.PentahoAccessControlException;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.platform.api.repository2.unified.UnifiedRepositoryException;
import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData;
import org.pentaho.platform.plugin.services.messages.Messages;
import org.pentaho.platform.repository2.unified.RepositoryUtils;
import org.pentaho.platform.repository2.unified.fileio.RepositoryFileInputStream;
import org.pentaho.platform.api.repository2.unified.IAclNodeHelper;
import org.pentaho.platform.repository2.unified.jcr.JcrAclNodeHelper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

/**
 * Handles the storage and retrieval of Pentaho Metada Domain objects in a repository. It does this by using a
 * pre-defined system location (defined by {@link PentahoMetadataDomainRepositoryInfo}) as the storage location for the
 * Domain files and associated locale files. </p> Since Domain IDs are the unique identifier for Pentaho Metadata
 * domains and may contain any character (including repository folder separator character(s) like '/', a {@link UUID}
 * will be created to store each file. The metadata for the file will be used to store the information (such as the
 * Domain ID). </p>
 *
 * @author <a href="mailto:dkincade@pentaho.com">David M. Kincade</a>
 */
public class PentahoMetadataDomainRepository implements IMetadataDomainRepository,
        IAclAwarePentahoMetadataDomainRepositoryImporter, IPentahoMetadataDomainRepositoryExporter {
    // The logger for this class
    private static final Log logger = LogFactory.getLog(PentahoMetadataDomainRepository.class);

    // The messages object used in generating messages that may be seen by the user
    private static final Messages messages = Messages.getInstance();

    private static final Map<IUnifiedRepository, PentahoMetadataInformationMap> metaMapStore = new HashMap<IUnifiedRepository, PentahoMetadataInformationMap>();

    // The type of repository file (domain, locale)
    private static final String PROPERTY_NAME_TYPE = "file-type";

    private static final String TYPE_DOMAIN = "domain";
    private static final String TYPE_LOCALE = "locale";

    // The repository file metadata key used to store the file's domain id
    private static final String PROPERTY_NAME_DOMAIN_ID = "domain-id";

    // The repository file metadata key used to store the file's locale (properties files)
    private static final String PROPERTY_NAME_LOCALE = "locale";

    // The default encoding for file storage
    private static final String DEFAULT_ENCODING = "UTF-8";

    // The default mime-type for the Pentaho Domain files
    private static final String DOMAIN_MIME_TYPE = "text/xml";

    // The default mime-type for locale files
    private static final String LOCALE_MIME_TYPE = "text/plain";

    // The repository used to store / retrieve objects
    private IUnifiedRepository repository;

    // Mapping between the Pentaho Metadata Domain ID and the repository files for that Domain
    private final PentahoMetadataInformationMap metadataMapping;

    // The parser used to serialize / deserialize metadata files
    private XmiParser xmiParser;

    // The repository utility class
    private RepositoryUtils repositoryUtils;

    // The localization utility class (used to load side-car properties files into a Domain object)
    private LocalizationUtil localizationUtil;

    private IAclNodeHelper aclHelper;

    /**
     * Creates an instance of this class providing the {@link IUnifiedRepository} repository backend.
     *
     * @param repository
     *          the {@link IUnifiedRepository} in which data will be stored / retrieved
     */
    public PentahoMetadataDomainRepository(final IUnifiedRepository repository) {
        this(repository, null, null, null);
    }

    /**
     * Helper constructor used for setting other objects in this class
     *
     * @param repository
     *          the {@link IUnifiedRepository} in which data will be stored / retrieved
     * @param repositoryUtils
     *          utility class for working inside the repository </br>(NOTE: {@code null} is acceptable and will create a
     *          default instance)
     * @param xmiParser
     *          the parser class for serializing / de-serializing Domain objects </br>(NOTE: {@code null} is acceptable
     *          and will create a default instance)
     * @param localizationUtil
     *          the object used to add locale bundles into a Pentaho Metadata Domain object </br>(NOTE: {@code null} is
     *          acceptable and will create a default instance)
     */
    protected PentahoMetadataDomainRepository(final IUnifiedRepository repository,
            final RepositoryUtils repositoryUtils, final XmiParser xmiParser,
            final LocalizationUtil localizationUtil) {
        if (null == repository) {
            throw new IllegalArgumentException();
        }
        this.metadataMapping = getMetadataMapping(repository);
        setRepository(repository);
        setRepositoryUtils(repositoryUtils);
        setLocalizationUtil(localizationUtil);
        setXmiParser(xmiParser);
    }

    /**
     * Store a domain to the repository. The domain should persist between JVM restarts.
     *
     * @param domain
     *          domain object to store
     * @param overwrite
     *          if true, overwrite existing domain
     * @throws DomainIdNullException
     *           if domain id is null or empty
     * @throws DomainAlreadyExistsException
     *           if a domain with the same Domain ID already exists in the repository and {@code overwrite == false}
     * @throws DomainStorageException
     *           if there is a problem storing the domain
     */
    @Override
    public void storeDomain(final Domain domain, final boolean overwrite)
            throws DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {
        logger.debug("storeDomain(domain(id=" + (domain != null ? domain.getId() : "") + ", " + overwrite + ")");
        if (null == domain || StringUtils.isEmpty(domain.getId())) {
            throw new DomainIdNullException(
                    messages.getErrorString("PentahoMetadataDomainRepository.ERROR_0001_DOMAIN_ID_NULL"));
        }

        String xmi = "";
        try {
            // NOTE - a ByteArrayInputStream doesn't need to be closed ...
            // ... so this is safe AS LONG AS we use a ByteArrayInputStream
            xmi = xmiParser.generateXmi(domain);
            //final InputStream inputStream = new ByteArrayInputStream( xmi.getBytes( DEFAULT_ENCODING ) );
            final InputStream inputStream = new ByteArrayInputStream(xmi.getBytes("UTF8"));
            storeDomain(inputStream, domain.getId(), overwrite);
        } catch (DomainStorageException dse) {
            throw dse;
        } catch (DomainAlreadyExistsException dae) {
            throw dae;
        } catch (Exception e) {
            final String errorMessage = messages.getErrorString(
                    "PentahoMetadataDomainRepository.ERROR_0003_ERROR_STORING_DOMAIN", domain.getId(),
                    e.getLocalizedMessage());
            logger.error(errorMessage, e);
            throw new DomainStorageException(xmi + errorMessage, e);
        }
    }

    /**
     * Stores a domain to the repository directly as an Input Stream
     *
     * @param inputStream
     * @param domainId
     * @param overwrite
     */
    @Override
    public void storeDomain(final InputStream inputStream, final String domainId, final boolean overwrite)
            throws DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {
        storeDomain(inputStream, domainId, overwrite, null);
    }

    @Override
    public void storeDomain(InputStream inputStream, String domainId, boolean overwrite, RepositoryFileAcl acl)
            throws DomainIdNullException, DomainAlreadyExistsException, DomainStorageException {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("storeDomain(inputStream, %s, %s, %s)", domainId, overwrite, acl));
        }
        if (null == inputStream) {
            throw new IllegalArgumentException();
        }
        if (StringUtils.isEmpty(domainId)) {
            throw new DomainIdNullException(
                    messages.getErrorString("PentahoMetadataDomainRepository.ERROR_0001_DOMAIN_ID_NULL"));
        }

        // Check to see if the domain already exists
        final RepositoryFile domainFile = getMetadataRepositoryFile(domainId);
        if (!overwrite && domainFile != null) {
            final String errorString = messages
                    .getErrorString("PentahoMetadataDomainRepository.ERROR_0002_DOMAIN_ALREADY_EXISTS", domainId);
            logger.error(errorString);
            throw new DomainAlreadyExistsException(errorString);
        }

        // Check if this is valid xml
        InputStream inputStream2 = null;
        String xmi = null;
        try {
            // try to see if the xmi can be parsed (ie, check if it's valid xmi)
            // first, convert our input stream to a string
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, DEFAULT_ENCODING));
            StringBuilder stringBuilder = new StringBuilder();
            while ((xmi = reader.readLine()) != null) {
                stringBuilder.append(xmi);
            }
            inputStream.close();
            xmi = stringBuilder.toString();
            // now, try to see if the xmi can be parsed (ie, check if it's valid xmi)
            Domain domain = xmiParser.parseXmi(new java.io.ByteArrayInputStream(xmi.getBytes(DEFAULT_ENCODING)));
            // xmi is valid. Create a new inputstream for the actual import action.
            inputStream2 = new java.io.ByteArrayInputStream(xmi.getBytes(DEFAULT_ENCODING));
        } catch (Exception ex) {
            logger.error(ex.getMessage());
            // throw new
            // DomainStorageException(messages.getErrorString("PentahoMetadataDomainRepository.ERROR_0010_ERROR_PARSING_XMI"),
            // ex);
            java.io.ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ex.printStackTrace(new java.io.PrintStream(byteArrayOutputStream));
            throw new DomainStorageException(byteArrayOutputStream.toString(), ex);
        }

        final SimpleRepositoryFileData data = new SimpleRepositoryFileData(inputStream2, DEFAULT_ENCODING,
                DOMAIN_MIME_TYPE);
        final RepositoryFile newDomainFile;
        if (domainFile == null) {
            newDomainFile = createUniqueFile(domainId, null, data);
        } else {
            newDomainFile = repository.updateFile(domainFile, data, null);
        }

        // This invalidates any caching
        flushDomains();

        getAclHelper().setAclFor(newDomainFile, acl);
    }

    protected synchronized IAclNodeHelper getAclHelper() {
        if (aclHelper == null) {
            aclHelper = new JcrAclNodeHelper(repository);
        }
        return aclHelper;
    }

    @Override
    public void setAclFor(String domainId, RepositoryFileAcl acl) {
        getAclHelper().setAclFor(getMetadataRepositoryFile(domainId), acl);
    }

    @Override
    public RepositoryFileAcl getAclFor(String domainId) {
        return getAclHelper().getAclFor(getMetadataRepositoryFile(domainId));
    }

    @Override
    public boolean hasAccessFor(String domainId) {
        return getAclHelper().canAccess(getMetadataRepositoryFile(domainId),
                EnumSet.of(RepositoryFilePermission.READ));
    }

    /*
     * retrieves the data streams for the metadata referenced by domainId. This could be a single .xmi file or an .xmi
     * file and multiple .properties files.
     */
    public Map<String, InputStream> getDomainFilesData(final String domainId) {
        Map<String, InputStream> values = new HashMap<String, InputStream>();
        Set<RepositoryFile> metadataFiles;
        synchronized (metadataMapping) {
            metadataFiles = metadataMapping.getFiles(domainId);
        }
        for (RepositoryFile repoFile : metadataFiles) {
            RepositoryFileInputStream is;
            try {
                is = new RepositoryFileInputStream(repoFile);
            } catch (Exception e) {
                return null; // This pretty much ensures an exception will be thrown later and passed to the client
            }
            String fileName = repoFile.getName().endsWith(".properties") ? repoFile.getName()
                    : domainId + (domainId.endsWith(".xmi") ? "" : ".xmi");
            values.put(fileName, is);
        }
        return values;
    }

    /**
     * retrieve a domain from the repo. This does lazy loading of the repo, so it calls reloadDomains() if not already
     * loaded.
     *
     * @param domainId
     *          domain to get from the repository
     * @return domain object
     */
    @Override
    public Domain getDomain(final String domainId) {
        logger.debug("getDomain(" + domainId + ")");
        if (StringUtils.isEmpty(domainId)) {
            throw new IllegalArgumentException(messages
                    .getErrorString("PentahoMetadataDomainRepository.ERROR_0004_DOMAIN_ID_INVALID", domainId));
        }
        Domain domain = null;
        try {
            // Load the domain file
            final RepositoryFile file = getMetadataRepositoryFile(domainId);
            if (file != null) {
                if (hasAccessFor(domainId)) {
                    SimpleRepositoryFileData data = repository.getDataForRead(file.getId(),
                            SimpleRepositoryFileData.class);
                    if (data != null) {
                        domain = xmiParser.parseXmi(data.getStream());
                        domain.setId(domainId);
                        logger.debug("loaded domain");
                        // Load any I18N bundles
                        loadLocaleStrings(domainId, domain);
                        logger.debug("loaded I18N bundles");
                    } else {
                        throw new UnifiedRepositoryException(messages.getErrorString(
                                "PentahoMetadataDomainRepository.ERROR_0005_ERROR_RETRIEVING_DOMAIN", domainId,
                                "data not found"));
                    }
                } else {
                    throw new PentahoAccessControlException(messages.getErrorString(
                            "PentahoMetadataDomainRepository.ERROR_0005_ERROR_RETRIEVING_DOMAIN", domainId,
                            "access denied"));
                }
            }
        } catch (Exception e) {
            if (!(e instanceof UnifiedRepositoryException || e instanceof PentahoAccessControlException)) {
                throw new UnifiedRepositoryException(messages.getErrorString(
                        "PentahoMetadataDomainRepository.ERROR_0005_ERROR_RETRIEVING_DOMAIN", domainId,
                        e.getLocalizedMessage()), e);
            }
        }

        // Return
        return domain;
    }

    /**
     * return a list of all the domain ids in the repository. triggers a call to reloadDomains if necessary.
     *
     * @return the domain Ids.
     */
    @Override
    public Set<String> getDomainIds() {
        logger.debug("getDomainIds()");
        internalReloadDomains();
        final Set<String> domainIds = new HashSet<String>();
        synchronized (metadataMapping) {
            Collection<String> domains = metadataMapping.getDomainIds();
            for (String domain : domains) {
                if (hasAccessFor(domain)) {
                    domainIds.add(domain);
                }
            }
            return domainIds;
        }
    }

    /**
     * remove a domain from disk and memory.
     *
     * @param domainId
     */
    @Override
    public void removeDomain(final String domainId) {
        logger.debug("removeDomain(" + domainId + ")");
        if (StringUtils.isEmpty(domainId)) {
            throw new IllegalArgumentException(messages
                    .getErrorString("PentahoMetadataDomainRepository.ERROR_0004_DOMAIN_ID_INVALID", domainId));
        }

        // Get the metadata domain file
        Set<RepositoryFile> domainFiles;
        synchronized (metadataMapping) {
            domainFiles = metadataMapping.getFiles(domainId);
            metadataMapping.deleteDomain(domainId);
        }

        // it no node exists, nothing would happen
        getAclHelper().removeAclFor(getMetadataRepositoryFile(domainId));

        for (final RepositoryFile file : domainFiles) {
            if (logger.isTraceEnabled()) {
                logger.trace("Deleting repository file " + toString(file));
            }
            repository.deleteFile(file.getId(), true, null);
        }

        // This invalidates any caching
        if (!domainFiles.isEmpty()) {
            flushDomains();
        }
    }

    /**
     * remove a model from a domain which is stored either on a disk or memory.
     *
     * @param domainId
     * @param modelId
     */
    @Override
    public void removeModel(final String domainId, final String modelId)
            throws DomainIdNullException, DomainStorageException {
        logger.debug("removeModel(" + domainId + ", " + modelId + ")");
        if (StringUtils.isEmpty(domainId)) {
            throw new IllegalArgumentException(messages
                    .getErrorString("PentahoMetadataDomainRepository.ERROR_0004_DOMAIN_ID_INVALID", domainId));
        }
        if (StringUtils.isEmpty(modelId)) {
            throw new IllegalArgumentException(
                    messages.getErrorString("PentahoMetadataDomainRepository.ERROR_0006_MODEL_ID_INVALID"));
        }

        // Get the domain and remove the model
        final Domain domain = getDomain(domainId);
        if (null != domain) {
            boolean found = false;
            final Iterator<LogicalModel> iter = domain.getLogicalModels().iterator();
            while (iter.hasNext()) {
                LogicalModel model = iter.next();
                if (modelId.equals(model.getId())) {
                    iter.remove();
                    found = true;
                    break;
                }
            }

            // Update the domain if we change it
            if (found) {
                try {
                    storeDomain(domain, true);
                    flushDomains();
                } catch (DomainAlreadyExistsException ignored) {
                    // This can't happen since we have setup overwrite to true
                }
            }
        }
    }

    /**
     * reload domains from disk
     */
    @Override
    public void reloadDomains() {
        logger.debug("reloadDomains()");
        internalReloadDomains();
    }

    /**
     * Performs the process of reloading the domain information from the repository
     */
    private void internalReloadDomains() {
        synchronized (metadataMapping) {
            metadataMapping.reset();

            // Reload the metadata about the metadata (that was fun to say)
            final List<RepositoryFile> children = repository.getChildren(getMetadataDir().getId(), "*");
            logger.trace("\tFound " + children.size() + " files in the repository");
            for (final RepositoryFile child : children) {
                if (getAclHelper().canAccess(child, EnumSet.of(RepositoryFilePermission.READ))) {
                    // Get the metadata for this file
                    final Map<String, Serializable> fileMetadata = repository.getFileMetadata(child.getId());
                    if (fileMetadata == null
                            || StringUtils.isEmpty((String) fileMetadata.get(PROPERTY_NAME_DOMAIN_ID))) {
                        logger.warn(messages.getString(
                                "PentahoMetadataDomainRepository.WARN_0001_FILE_WITHOUT_METADATA",
                                child.getName()));
                        continue;
                    }
                    final String domainId = (String) fileMetadata.get(PROPERTY_NAME_DOMAIN_ID);
                    final String type = (String) fileMetadata.get(PROPERTY_NAME_TYPE);
                    final String locale = (String) fileMetadata.get(PROPERTY_NAME_LOCALE);
                    if (logger.isTraceEnabled()) {
                        logger.trace("\tprocessing file [type=" + type + " : domainId=" + domainId + " : locale="
                                + locale + "]");
                    }

                    // Save the data in the map
                    if (StringUtils.equals(type, TYPE_DOMAIN)) {
                        metadataMapping.addDomain(domainId, child);
                    } else if (StringUtils.equals(type, TYPE_LOCALE)) {
                        metadataMapping.addLocale(domainId, locale, child);
                    }
                }
            }
        }
    }

    /**
     * flush the domains from memory
     */
    @Override
    public void flushDomains() {
        logger.debug("flushDomains()");
        internalReloadDomains();
    }

    @Override
    public String generateRowLevelSecurityConstraint(final LogicalModel model) {
        // We will let subclasses handle this issue
        return null;
    }

    /**
     * The aclHolder cannot be null unless the access type requested is ACCESS_TYPE_SCHEMA_ADMIN.
     */
    @Override
    public boolean hasAccess(final int accessType, final IConcept aclHolder) {
        // We will let subclasses handle this computation
        return true;
    }

    /**
     * Adds a set of properties as a locale properties file for the specified Domain ID
     *
     * @param domainId
     *          the domain ID for which this properties file will be added
     * @param locale
     *          the locale for which this properties file will be added
     * @param properties
     *          the properties to be added
     */
    public void addLocalizationFile(final String domainId, final String locale, final Properties properties)
            throws DomainStorageException {
        // This is safe since ByteArray streams don't have to be closed
        if (null != properties) {
            try {
                final OutputStream out = new ByteArrayOutputStream();
                properties.store(out, null);
                addLocalizationFile(domainId, locale, new ByteArrayInputStream(out.toString().getBytes()), true);
            } catch (IOException e) {
                throw new DomainStorageException(
                        messages.getErrorString("PentahoMetadataDomainRepository.ERROR_0008_ERROR_IN_REPOSITORY",
                                e.getLocalizedMessage()),
                        e);
            }
        }
    }

    @Override
    public void addLocalizationFile(final String domainId, final String locale, final InputStream inputStream,
            final boolean overwrite) throws DomainStorageException {
        logger.debug("addLocalizationFile(" + domainId + ", " + locale + ", inputStream)");
        if (null != inputStream) {
            if (StringUtils.isEmpty(domainId) || StringUtils.isEmpty(locale)) {
                throw new IllegalArgumentException(messages
                        .getErrorString("PentahoMetadataDomainRepository.ERROR_0004_DOMAIN_ID_INVALID", domainId));
            }

            synchronized (metadataMapping) {
                // Check for duplicates
                final RepositoryFile localeFile = metadataMapping.getLocaleFile(domainId, locale);
                if (!overwrite && localeFile != null) {
                    throw new DomainStorageException(messages.getErrorString(
                            "PentahoMetadataDomainRepository.ERROR_0009_LOCALE_ALREADY_EXISTS", domainId, locale),
                            null);
                }

                final SimpleRepositoryFileData data = new SimpleRepositoryFileData(inputStream, DEFAULT_ENCODING,
                        LOCALE_MIME_TYPE);
                if (localeFile == null) {
                    final RepositoryFile newLocaleFile = createUniqueFile(domainId, locale, data);
                    metadataMapping.addLocale(domainId, locale, newLocaleFile);
                } else {
                    repository.updateFile(localeFile, data, null);
                }
            }

            // This invalidates any cached information
            flushDomains();
        }
    }

    protected RepositoryFile getMetadataDir() {
        final String metadataDirName = PentahoMetadataDomainRepositoryInfo.getMetadataFolderPath();
        return repository.getFile(metadataDirName);
    }

    protected void loadLocaleStrings(final String domainId, final Domain domain) {
        final Map<String, RepositoryFile> localeFiles = metadataMapping.getLocaleFiles(domainId);
        if (localeFiles != null) {
            for (final String locale : localeFiles.keySet()) {
                final RepositoryFile localeFile = localeFiles.get(locale);
                final Properties properties = loadProperties(localeFile);
                logger.trace("\tLoading properties [" + domain + " : " + locale + "]");
                localizationUtil.importLocalizedProperties(domain, properties, locale);
            }
        }
    }

    protected Properties loadProperties(final RepositoryFile bundle) {
        try {
            Properties properties = null;
            final SimpleRepositoryFileData bundleData = repository.getDataForRead(bundle.getId(),
                    SimpleRepositoryFileData.class);
            if (bundleData != null) {
                properties = new Properties();
                properties.load(bundleData.getStream());
            } else {
                logger.warn("Could not load properties from repository file: " + bundle.getName());
            }
            return properties;
        } catch (IOException e) {
            throw new UnifiedRepositoryException(messages.getErrorString(
                    "PentahoMetadataDomainRepository.ERROR_0008_ERROR_IN_REPOSITORY", e.getLocalizedMessage()), e);
        }
    }

    /**
     * Creates a new repository file (with the supplied data) and applies the proper metadata to this file.
     *
     * @param domainId
     *          the Domain id associated with this file
     * @param locale
     *          the locale associated with this file (or null for a domain file)
     * @param data
     *          the data to put in the file
     * @return the repository file created
     */
    protected RepositoryFile createUniqueFile(final String domainId, final String locale,
            final SimpleRepositoryFileData data) {
        // Generate a "unique" filename
        final String filename = UUID.randomUUID().toString();

        // Create the new file
        final RepositoryFile file = repository.createFile(getMetadataDir().getId(),
                new RepositoryFile.Builder(filename).build(), data, null);

        // Add metadata to the file
        final Map<String, Serializable> metadataMap = new HashMap<String, Serializable>();
        metadataMap.put(PROPERTY_NAME_DOMAIN_ID, domainId);
        if (StringUtils.isEmpty(locale)) {
            // This is a domain file
            metadataMap.put(PROPERTY_NAME_TYPE, TYPE_DOMAIN);
        } else {
            // This is a locale property file
            metadataMap.put(PROPERTY_NAME_TYPE, TYPE_LOCALE);
            metadataMap.put(PROPERTY_NAME_LOCALE, locale);
        }

        // Update the metadata
        repository.setFileMetadata(file.getId(), metadataMap);
        return file;
    }

    protected IUnifiedRepository getRepository() {
        return repository;
    }

    protected XmiParser getXmiParser() {
        return xmiParser;
    }

    protected RepositoryUtils getRepositoryUtils() {
        return repositoryUtils;
    }

    protected LocalizationUtil getLocalizationUtil() {
        return localizationUtil;
    }

    protected void setRepository(final IUnifiedRepository repository) {
        this.repository = repository;
    }

    protected void setXmiParser(final XmiParser xmiParser) {
        this.xmiParser = (xmiParser != null ? xmiParser : new XmiParser());
    }

    protected void setRepositoryUtils(final RepositoryUtils repositoryUtils) {
        this.repositoryUtils = (repositoryUtils != null ? repositoryUtils : new RepositoryUtils(repository));
    }

    protected void setLocalizationUtil(final LocalizationUtil localizationUtil) {
        this.localizationUtil = (localizationUtil != null ? localizationUtil : new LocalizationUtil());
    }

    protected String toString(final RepositoryFile file) {
        try {
            final Map<String, Serializable> fileMetadata = repository.getFileMetadata(file.getId());
            return "[type=" + fileMetadata.get(PROPERTY_NAME_TYPE) + " : domain="
                    + fileMetadata.get(PROPERTY_NAME_DOMAIN_ID) + " : locale="
                    + fileMetadata.get(PROPERTY_NAME_LOCALE) + " : filename=" + file.getName() + "]";
        } catch (Throwable ignore) {
            //ignore
        }
        return "null";
    }

    /**
     * Accesses the metadata mapping (with 1 retry) to find the metadata file for the specified domainId
     */
    protected RepositoryFile getMetadataRepositoryFile(final String domainId) {
        RepositoryFile domainFile = metadataMapping.getDomainFile(domainId);
        if (null == domainFile) {
            reloadDomains();
            domainFile = metadataMapping.getDomainFile(domainId);
        }
        return domainFile;
    }

    /**
     * Returns the MatadataInformationMap for the specified IUnifiedRepository
     *
     * @param repository
     *          the repository for which a map is specified
     * @return the MatadataInformationMap for the repository
     */
    private static synchronized PentahoMetadataInformationMap getMetadataMapping(
            final IUnifiedRepository repository) {
        PentahoMetadataInformationMap metaMap = metaMapStore.get(repository);
        if (null == metaMap) {
            metaMap = new PentahoMetadataInformationMap();
            metaMapStore.put(repository, metaMap);
        }
        return metaMap;
    }
}