org.pentaho.platform.repository.solution.SolutionRepositoryServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.repository.solution.SolutionRepositoryServiceImpl.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.
 * 
 * @created Jul 12, 2005 
 * @author James Dixon, Angelo Rodriguez, Steven Barkdull
 */
package org.pentaho.platform.repository.solution;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IAclSolutionFile;
import org.pentaho.platform.api.engine.ICacheManager;
import org.pentaho.platform.api.engine.IContentInfo;
import org.pentaho.platform.api.engine.IFileInfo;
import org.pentaho.platform.api.engine.IPentahoAclEntry;
import org.pentaho.platform.api.engine.IPentahoRequestContext;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.IPermissionMask;
import org.pentaho.platform.api.engine.IPermissionRecipient;
import org.pentaho.platform.api.engine.IPluginManager;
import org.pentaho.platform.api.engine.IPluginOperation;
import org.pentaho.platform.api.engine.ISolutionFile;
import org.pentaho.platform.api.engine.PentahoAccessControlException;
import org.pentaho.platform.api.repository.ISolutionRepository;
import org.pentaho.platform.api.repository.ISolutionRepositoryService;
import org.pentaho.platform.api.repository.SolutionRepositoryServiceException;
import org.pentaho.platform.engine.core.solution.ActionInfo;
import org.pentaho.platform.engine.core.system.PentahoRequestContextHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.security.SecurityHelper;
import org.pentaho.platform.engine.security.SimplePermissionMask;
import org.pentaho.platform.engine.security.SimpleRole;
import org.pentaho.platform.engine.security.SimpleUser;
import org.pentaho.platform.engine.services.messages.Messages;
import org.pentaho.platform.util.StringUtil;
import org.pentaho.platform.util.VersionHelper;
import org.pentaho.platform.util.VersionInfo;
import org.pentaho.platform.util.messages.LocaleHelper;
import org.pentaho.platform.util.xml.XmlHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SolutionRepositoryServiceImpl implements ISolutionRepositoryService {

    private static final long serialVersionUID = -5870073658756939643L;
    private static final Log logger = LogFactory.getLog(SolutionRepositoryServiceImpl.class);
    private static final String URL_ENCODING = "UTF-8";
    private static final String RESPONSE_DOCUMENT_ENCODING = URL_ENCODING; //$NON-NLS-1$
    private static final String RESPONSE_DOCUMENT_VERSION_NUM = "1.0"; //$NON-NLS-1$

    /**
     * contains instance of a sax parser factory. Use getSAXParserFactory() method to get a copy of the factory.
     */
    private static final ThreadLocal<SAXParserFactory> SAX_FACTORY = new ThreadLocal<SAXParserFactory>();

    static {
        // The SolutionRepositoryService creates/uses the ICacheManager from PentahoSystem to create a new
        // cache region specifically for the caching of the solution repository document. This is not put
        // into a session cache intentionally. Client tools like PRD do not maintain a session and would
        // thus never have any benefit from this. Since we are using a cache manager, if the cache is
        // unused long enough entries will age out.

        // We are caching the solution repository document on a per-user basis, as required, because the
        // document is that user's view of the repository, with respect to ACLs.

        // Upon publish, reload, or reset repository calls this cache is cleared in the reset method
        // of SolutionRepositoryBase.
        ICacheManager cacheManager = PentahoSystem.getCacheManager(null);
        if (!cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
            cacheManager.addCacheRegion(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION);
        }
    }

    public SolutionRepositoryServiceImpl() {
        super();
    }

    /**
     * This method will delete a file from the ISolutionRepository and respects IPentahoAclEntry.PERM_DELETE.
     * 
     * @param userSession
     *          An IPentahoSession for the user requesting the delete operation
     * @param solution
     *          The name of the solution, such as 'steel-wheels'
     * @param path
     *          The path within the solution to the file/folder to be deleted (does not include the file/folder itself)
     * @param name
     *          The name of the file or folder which will be deleted in the given solution/path
     * @return Success of the delete operation is returned
     * @throws IOException
     */
    public boolean delete(final IPentahoSession userSession, final String solution, final String path,
            final String name) throws IOException {

        ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, userSession);
        String fullPath = ActionInfo.buildSolutionPath(solution, path, name);
        return repository.removeSolutionFile(fullPath);
    }

    /**
     * This method creates a folder along with it's index.xml file.  
     * This method also verifies that the user has PERM_CREATE permissions before
     * creating the folder.
     * 
     * @param userSession the current user 
     * @param solution the solution path
     * @param path the folder path
     * @param name the name of the new folder
     * @param desc the description of the new folder
     * @return true if success
     * @throws IOException
     */
    public boolean createFolder(IPentahoSession userSession, String solution, String path, String name, String desc)
            throws IOException {
        ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, userSession);
        if (solution == null) {
            solution = ""; //$NON-NLS-1$
        }

        // verify that the name does not contain a path separator before creating the folder
        if (name == null || name.indexOf("/") >= 0 || name.indexOf("\\") >= 0 || //$NON-NLS-1$ //$NON-NLS-2$
                name.indexOf(ISolutionRepository.SEPARATOR) >= 0) {
            return false;
        }

        String parentFolderPath = ActionInfo.buildSolutionPath(solution, path, "" + ISolutionRepository.SEPARATOR); //$NON-NLS-1$
        ISolutionFile parentSolutionFile = repository.getSolutionFile(parentFolderPath,
                ISolutionRepository.ACTION_CREATE);
        if (parentSolutionFile != null && parentSolutionFile.isDirectory()) {
            File parent = new File(PentahoSystem.getApplicationContext().getSolutionPath(parentFolderPath));
            File newFolder = new File(parent, name);
            if (newFolder.exists()) {
                // if the new folder already exists, we need to get out
                return false;
            }
            repository.createFolder(newFolder);

            // create the index file content
            String defaultIndex = "<index><name>" + name + "</name><description>" + (desc != null ? desc : name) //$NON-NLS-1$ //$NON-NLS-2$
                    + "</description><icon>reporting.png</icon><visible>true</visible><display-type>list</display-type></index>"; //$NON-NLS-1$

            // add the index file to the repository
            String indexPath = ActionInfo.buildSolutionPath(solution, path, name);
            String repositoryBaseURL = PentahoSystem.getApplicationContext().getSolutionPath(""); //$NON-NLS-1$
            repository.addSolutionFile(repositoryBaseURL, indexPath, ISolutionRepository.INDEX_FILENAME,
                    defaultIndex.getBytes(), false);
            return true;
        }
        return false;
    }

    protected boolean acceptFilter(String name, String[] filters) {
        if (filters == null || filters.length == 0) {
            return false;
        }
        for (int i = 0; i < filters.length; i++) {
            if (name.endsWith(filters[i])) {
                return true;
            }
        }
        return false;
    }

    protected boolean accept(boolean isAdministrator, ISolutionRepository repository, ISolutionFile file) {
        return isAdministrator || repository.hasAccess(file, IPentahoAclEntry.PERM_EXECUTE);
    }

    protected void processRepositoryFile(IPentahoSession session, boolean isAdministrator,
            ISolutionRepository repository, Node parentNode, ISolutionFile file, String[] filters) {

        final String name = file.getFileName();

        // MDD 10/16/2008 Not always.. what about 'system'
        if (name.startsWith("system") || name.startsWith("tmp") || name.startsWith(".")) { //$NON-NLS-1$
            // slip hidden files (starts with .)
            // skip the system & tmp dir, we DO NOT ever want this to hit the client
            return;
        }

        if (!accept(isAdministrator, repository, file)) {
            // we don't want this file, skip to the next one
            return;
        }

        if (file.isDirectory()) {
            // we always process directories

            // maintain legacy behavior
            if (repository.getRootFolder(ISolutionRepository.ACTION_EXECUTE).getFullPath()
                    .equals(file.getFullPath())) {
                // never output the root folder as part of the repo doc; skip root and process its children
                ISolutionFile[] children = file.listFiles();
                for (ISolutionFile childSolutionFile : children) {
                    processRepositoryFile(session, isAdministrator, repository, parentNode, childSolutionFile,
                            filters);
                }
                return;
            }

            Element child = null;
            final String key = file.getFullPath() + file.getLastModified() + LocaleHelper.getLocale();
            ICacheManager cacheManager = PentahoSystem.getCacheManager(null);
            if (cacheManager != null
                    && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
                child = (Element) cacheManager
                        .getFromRegionCache(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION, key);
            }
            if (child == null) {
                child = parentNode instanceof Document ? ((Document) parentNode).createElement("file") //$NON-NLS-1$
                        : parentNode.getOwnerDocument().createElement("file"); //$NON-NLS-1$
                try {
                    String localizedName = repository.getLocalizedFileProperty(file, "name", //$NON-NLS-1$
                            ISolutionRepository.ACTION_EXECUTE);
                    child.setAttribute("localized-name", //$NON-NLS-1$
                            localizedName == null || "".equals(localizedName) ? name : localizedName); //$NON-NLS-1$
                } catch (Exception e) {
                    child.setAttribute("localized-name", name); //$NON-NLS-1$
                }
                try {
                    String visible = repository.getLocalizedFileProperty(file, "visible", //$NON-NLS-1$
                            ISolutionRepository.ACTION_EXECUTE);
                    child.setAttribute("visible", visible == null || "".equals(visible) ? "false" : visible); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                } catch (Exception e) {
                    e.printStackTrace();
                    child.setAttribute("visible", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                }
                String description = repository.getLocalizedFileProperty(file, "description", //$NON-NLS-1$
                        ISolutionRepository.ACTION_EXECUTE);
                child.setAttribute("description", //$NON-NLS-1$
                        description == null || "".equals(description) ? name : description); //$NON-NLS-1$
                child.setAttribute("name", name); //$NON-NLS-1$
                child.setAttribute("isDirectory", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                child.setAttribute("lastModifiedDate", "" + file.getLastModified()); //$NON-NLS-1$ //$NON-NLS-2$
                if (cacheManager != null
                        && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
                    cacheManager.putInRegionCache(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION, key, child);
                }
            } else {
                Element newChild = parentNode instanceof Document ? ((Document) parentNode).createElement("file") //$NON-NLS-1$
                        : parentNode.getOwnerDocument().createElement("file"); //$NON-NLS-1$
                NamedNodeMap attributes = child.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node attribute = attributes.item(i);
                    newChild.setAttribute(attribute.getNodeName(), attribute.getNodeValue());
                }
                child = newChild;
            }
            parentNode.appendChild(child);

            ISolutionFile[] children = file.listFiles();
            for (ISolutionFile childSolutionFile : children) {
                processRepositoryFile(session, isAdministrator, repository, child, childSolutionFile, filters);
            }
        } else {
            InputStream pluginInputStream = null;
            try {
                int lastPoint = name.lastIndexOf('.');
                String extension = ""; //$NON-NLS-1$
                if (lastPoint != -1) {
                    // ignore anything with no extension
                    extension = name.substring(lastPoint + 1).toLowerCase();
                }

                // xaction and URL support are built in
                boolean addFile = acceptFilter(name, filters) || "xaction".equals(extension) //$NON-NLS-1$
                        || "url".equals(extension); //$NON-NLS-1$
                boolean isPlugin = false;
                // see if there is a plugin for this file type
                IPluginManager pluginManager = PentahoSystem.get(IPluginManager.class, session);
                if (pluginManager != null) {
                    Set<String> types = pluginManager.getContentTypes();
                    isPlugin = types != null && types.contains(extension);
                    addFile |= isPlugin;
                }

                if (addFile) {

                    ICacheManager cacheManager = PentahoSystem.getCacheManager(null);
                    Element child = null;
                    final String key = file.getFullPath() + file.getLastModified() + LocaleHelper.getLocale();
                    if (cacheManager != null
                            && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
                        child = (Element) cacheManager
                                .getFromRegionCache(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION, key);
                    }
                    if (child == null) {
                        child = parentNode instanceof Document ? ((Document) parentNode).createElement("file") //$NON-NLS-1$
                                : parentNode.getOwnerDocument().createElement("file"); //$NON-NLS-1$
                        if (cacheManager != null
                                && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
                            cacheManager.putInRegionCache(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION, key,
                                    child);
                        }
                    } else {
                        Element newChild = parentNode instanceof Document
                                ? ((Document) parentNode).createElement("file") //$NON-NLS-1$
                                : parentNode.getOwnerDocument().createElement("file"); //$NON-NLS-1$

                        NamedNodeMap attributes = child.getAttributes();
                        for (int i = 0; i < attributes.getLength(); i++) {
                            Node attribute = attributes.item(i);
                            newChild.setAttribute(attribute.getNodeName(), attribute.getNodeValue());
                        }

                        parentNode.appendChild(newChild);
                        return;
                    }

                    parentNode.appendChild(child);
                    IFileInfo fileInfo = null;
                    if (name.endsWith(".xaction")) { //$NON-NLS-1$
                        // add special props?
                        // localization..
                        String solution = file.getSolutionPath();
                        String path = ""; //$NON-NLS-1$
                        if (solution.startsWith(ISolutionRepository.SEPARATOR + "")) { //$NON-NLS-1$
                            solution = solution.substring(1);
                        }
                        final int pos = solution.indexOf(ISolutionRepository.SEPARATOR);
                        if (pos != -1) {
                            path = solution.substring(pos + 1);
                            solution = solution.substring(0, pos);
                        }
                        IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext();
                        String contextPath = requestContext.getContextPath();
                        final String paramServiceUrl = contextPath + "ServiceAction?solution=" //$NON-NLS-1$
                                + URLEncoder.encode(solution, URL_ENCODING) + "&path=" + URLEncoder.encode(path, URL_ENCODING) + "&action=" + URLEncoder.encode(name, URL_ENCODING) + "&component=xaction-parameter";
                        child.setAttribute("param-service-url", paramServiceUrl);

                        final String url = contextPath + "ViewAction?solution=" //$NON-NLS-1$
                                + URLEncoder.encode(solution, URL_ENCODING) + "&path=" + URLEncoder.encode(path, URL_ENCODING) + "&action=" + URLEncoder.encode(name, URL_ENCODING);
                        child.setAttribute("url", url);

                    } else if (name.endsWith(".url")) { //$NON-NLS-1$

                        // add special props
                        String props = new String(file.getData());
                        StringTokenizer tokenizer = new StringTokenizer(props, "\n"); //$NON-NLS-1$
                        while (tokenizer.hasMoreTokens()) {
                            String line = tokenizer.nextToken();
                            int pos = line.indexOf('=');
                            if (pos > 0) {
                                String propname = line.substring(0, pos);
                                String value = line.substring(pos + 1);
                                if ((value != null) && (value.length() > 0)
                                        && (value.charAt(value.length() - 1) == '\r')) {
                                    value = value.substring(0, value.length() - 1);
                                }
                                if ("URL".equalsIgnoreCase(propname)) { //$NON-NLS-1$
                                    child.setAttribute("url", value); //$NON-NLS-1$
                                }
                            }
                        }
                    } else if (isPlugin) {
                        // must be a plugin - make it look like a URL
                        try {
                            // get the file info object for this file
                            // not all plugins are going to actually use the inputStream, so we have a special
                            // wrapper inputstream so that we can pay that price when we need to (2X speed boost)
                            pluginInputStream = new PluginFileInputStream(repository, file);
                            fileInfo = pluginManager.getFileInfo(extension, session, file, pluginInputStream);
                            String handlerId = pluginManager.getContentGeneratorIdForType(extension, session);
                            String fileUrl = pluginManager.getContentGeneratorUrlForType(extension, session);
                            String solution = file.getSolutionPath();
                            String path = ""; //$NON-NLS-1$
                            IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext();
                            String contextPath = requestContext.getContextPath();
                            if (solution.startsWith(ISolutionRepository.SEPARATOR + "")) { //$NON-NLS-1$
                                solution = solution.substring(1);
                            }
                            int pos = solution.indexOf(ISolutionRepository.SEPARATOR);
                            if (pos != -1) {
                                path = solution.substring(pos + 1);
                                solution = solution.substring(0, pos);
                            }
                            String url = null;
                            if (!"".equals(fileUrl)) { //$NON-NLS-1$
                                url = contextPath + fileUrl + "?solution="
                                        + URLEncoder.encode(solution, URL_ENCODING) + "&path="
                                        + URLEncoder.encode(path, URL_ENCODING) + "&action=" //$NON-NLS-1$
                                        + URLEncoder.encode(name, URL_ENCODING); //$NON-NLS-2$ //$NON-NLS-3$
                            } else {
                                IContentInfo info = pluginManager.getContentInfoFromExtension(extension, session);
                                for (IPluginOperation operation : info.getOperations()) {
                                    if (operation.getId().equalsIgnoreCase("RUN")) { //$NON-NLS-1$
                                        String command = operation.getCommand();

                                        command = command.replaceAll("\\{solution\\}", //$NON-NLS-1$
                                                URLEncoder.encode(solution, URL_ENCODING));
                                        command = command.replaceAll("\\{path\\}", //$NON-NLS-1$
                                                URLEncoder.encode(path, URL_ENCODING));
                                        command = command.replaceAll("\\{name\\}", //$NON-NLS-1$
                                                URLEncoder.encode(name, URL_ENCODING));
                                        url = contextPath + command;

                                        break;
                                    }
                                }
                                if (url == null) {
                                    url = contextPath + "content/" + handlerId + "?solution="
                                            + URLEncoder.encode(solution, URL_ENCODING) + "&path="
                                            + URLEncoder.encode(path, URL_ENCODING) + "&action=" //$NON-NLS-1$
                                            + URLEncoder.encode(name, URL_ENCODING); //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
                                }
                            }
                            child.setAttribute("url", url); //$NON-NLS-1$

                            // do not come up with fantasy values for a non-existing service.
                            // if there is no param-service then do not claim that there is one.
                            String paramUrl = null;
                            final IContentInfo info = pluginManager.getContentInfoFromExtension(extension, session);
                            for (final IPluginOperation operation : info.getOperations()) {
                                if (operation.getId().equals("PARAMETER")) { //$NON-NLS-1$
                                    String command = operation.getCommand();
                                    command = command.replaceAll("\\{solution\\}", //$NON-NLS-1$
                                            URLEncoder.encode(solution, URL_ENCODING));
                                    command = command.replaceAll("\\{path\\}", //$NON-NLS-1$
                                            URLEncoder.encode(path, URL_ENCODING));
                                    command = command.replaceAll("\\{name\\}", //$NON-NLS-1$
                                            URLEncoder.encode(name, URL_ENCODING));
                                    paramUrl = contextPath + command;

                                    break;
                                }
                            }

                            if (StringUtil.isEmpty(paramUrl) == false) {
                                child.setAttribute("param-service-url", paramUrl); //$NON-NLS-1$
                            }
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }

                    }

                    if (fileInfo != null) {
                        if ("none".equals(fileInfo.getDisplayType())) // $NON-NLS-1$
                        {
                            child.setAttribute("visible", "false"); //$NON-NLS-1$ //$NON-NLS-2$
                        } else {
                            child.setAttribute("visible", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    } else {
                        try {
                            // the visibility flag for action-sequences is controlled by
                            // /action-sequence/documentation/result-type
                            // and we should no longer be looking at 'visible' because it was
                            // never actually used!
                            String visible = "none".equals(repository.getLocalizedFileProperty(file, //$NON-NLS-1$
                                    "documentation/result-type", ISolutionRepository.ACTION_EXECUTE)) ? "false" //$NON-NLS-1$//$NON-NLS-2$
                                            : "true"; //$NON-NLS-1$
                            child.setAttribute("visible", //$NON-NLS-1$
                                    (visible == null || "".equals(visible) || "true".equals(visible)) ? "true" //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
                                            : "false"); //$NON-NLS-1$
                        } catch (Exception e) {
                            child.setAttribute("visible", "true"); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    }

                    // localization
                    try {
                        String localizedName = null;
                        if (name.endsWith(".url")) { //$NON-NLS-1$
                            localizedName = repository.getLocalizedFileProperty(file, "url_name", //$NON-NLS-1$
                                    ISolutionRepository.ACTION_EXECUTE);
                        } else if (fileInfo != null) {
                            localizedName = fileInfo.getTitle();
                        } else {
                            localizedName = repository.getLocalizedFileProperty(file, "title", //$NON-NLS-1$
                                    ISolutionRepository.ACTION_EXECUTE);
                        }
                        child.setAttribute("localized-name", //$NON-NLS-1$
                                localizedName == null || "".equals(localizedName) ? name : localizedName); //$NON-NLS-1$
                    } catch (Exception e) {
                        child.setAttribute("localized-name", name); //$NON-NLS-1$
                    }

                    try {
                        // only folders, urls and xactions have descriptions
                        if (name.endsWith(".url")) { //$NON-NLS-1$
                            String url_description = repository.getLocalizedFileProperty(file, "url_description", //$NON-NLS-1$
                                    ISolutionRepository.ACTION_EXECUTE);
                            String description = repository.getLocalizedFileProperty(file, "description", //$NON-NLS-1$
                                    ISolutionRepository.ACTION_EXECUTE);
                            if (url_description == null && description == null) {
                                child.setAttribute("description", name); //$NON-NLS-1$
                            } else {
                                child.setAttribute("description", //$NON-NLS-1$
                                        url_description == null || "".equals(url_description) ? description //$NON-NLS-1$
                                                : url_description);
                            }
                        } else if (name.endsWith(".xaction")) { //$NON-NLS-1$
                            String description = repository.getLocalizedFileProperty(file, "description", //$NON-NLS-1$
                                    ISolutionRepository.ACTION_EXECUTE);
                            child.setAttribute("description", //$NON-NLS-1$
                                    description == null || "".equals(description) ? name : description); //$NON-NLS-1$
                        } else if (fileInfo != null) {
                            child.setAttribute("description", fileInfo.getDescription()); //$NON-NLS-1$
                        } else {
                            child.setAttribute("description", name); //$NON-NLS-1$
                        }
                    } catch (Exception e) {
                        child.setAttribute("description", "xxxxxxx"); //$NON-NLS-1$ //$NON-NLS-2$
                    }

                    // add permissions for each file/folder
                    child.setAttribute("name", name); //$NON-NLS-1$
                    child.setAttribute("isDirectory", "" + file.isDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
                    child.setAttribute("lastModifiedDate", "" + file.getLastModified()); //$NON-NLS-1$ //$NON-NLS-2$
                }
            } catch (UnsupportedEncodingException e) {
                // if that happens, you are running on a JDK without UTF-8 support. Get a proper JDK!
                throw new IllegalStateException(e);
            } finally {
                IOUtils.closeQuietly(pluginInputStream);
            }
        } // else isfile
    }

    public org.w3c.dom.Document getSolutionRepositoryDoc(IPentahoSession session, String[] filters)
            throws ParserConfigurationException {
        // The SolutionRepositoryService creates/uses the ICacheManager from PentahoSystem to create a new
        // cache region specifically for the caching of the solution repository document. This is not put
        // into a session cache intentionally. Client tools like PRD do not maintain a session and would
        // thus never have any benefit from this. Since we are using a cache manager, if the cache is
        // unused long enough entries will age out.

        // We are caching the solution repository document on a per-user basis, as required, because the
        // document is that user's view of the repository, with respect to ACLs.

        // Upon publish, reload, or reset repository calls this cache is cleared in the reset method
        // of SolutionRepositoryBase.

        ICacheManager cacheManager = PentahoSystem.getCacheManager(null);
        org.w3c.dom.Document document = null;
        if (cacheManager != null
                && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
            document = (org.w3c.dom.Document) cacheManager.getFromRegionCache(
                    ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION,
                    session.getName() + LocaleHelper.getLocale());
        }

        if (document == null) {
            ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, session);
            ISolutionFile rootFile = repository.getRootFolder(ISolutionRepository.ACTION_EXECUTE);
            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            org.w3c.dom.Element root = document.createElement("repository"); //$NON-NLS-1$
            document.appendChild(root);
            root.setAttribute("path", rootFile.getFullPath()); //$NON-NLS-1$
            root.setAttribute("product-id", VersionHelper.getVersionInfo().getProductID());
            root.setAttribute("version-major", VersionHelper.getVersionInfo().getVersionMajor());
            root.setAttribute("version-minor", VersionHelper.getVersionInfo().getVersionMinor());
            root.setAttribute("version-relase", VersionHelper.getVersionInfo().getVersionRelease());
            root.setAttribute("version-milestone", VersionHelper.getVersionInfo().getVersionMilestone());
            root.setAttribute("version-build", VersionHelper.getVersionInfo().getVersionBuild());
            boolean isAdministrator = SecurityHelper.isPentahoAdministrator(session);
            processRepositoryFile(session, isAdministrator, repository, root, rootFile, filters);
            // only attempt to add to the cache if by this point it exists, it's possible that
            // the implementation of the ICacheManager might not allow the creation of new
            // or custom caches like this.
            if (cacheManager != null
                    && cacheManager.cacheEnabled(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION)) {
                cacheManager.putInRegionCache(ISolutionRepository.REPOSITORY_SERVICE_CACHE_REGION,
                        session.getName() + LocaleHelper.getLocale(), document);
            }

        }

        return document;
    }

    /**
     * Returns an XML snippet consisting of a single <code>file</code> element. The <code>file</code> element is the same 
     * as would have been returned by <code>getSolutionRepositoryDoc</code>.
     * @param session current session
     * @return doc
     * @throws ParserConfigurationException
     */
    public org.w3c.dom.Document getSolutionRepositoryFileDetails(IPentahoSession session, String fullPath)
            throws ParserConfigurationException {
        ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, session);
        ISolutionFile rootFile = repository.getSolutionFile(fullPath, ISolutionRepository.ACTION_EXECUTE);
        org.w3c.dom.Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        boolean isAdministrator = SecurityHelper.isPentahoAdministrator(session);
        processRepositoryFile(session, isAdministrator, repository, document, rootFile, new String[0]);
        return document;
    }

    private Map<IPermissionRecipient, IPermissionMask> createAclFromXml(final String strXml)
            throws ParserConfigurationException, SAXException, IOException {
        SAXParser parser = getSAXParserFactory().newSAXParser();
        Map<IPermissionRecipient, IPermissionMask> m = new HashMap<IPermissionRecipient, IPermissionMask>();

        DefaultHandler h = new AclParserHandler(m);
        String encoding = XmlHelper.getEncoding(strXml);
        InputStream is = new ByteArrayInputStream(strXml.getBytes(encoding));

        parser.parse(is, h);

        return m;
    }

    private class AclParserHandler extends DefaultHandler {

        Map<IPermissionRecipient, IPermissionMask> acl;

        public AclParserHandler(final Map<IPermissionRecipient, IPermissionMask> acl) {
            this.acl = acl;
        }

        @Override
        public void startElement(final String uri, final String localName, final String qName,
                final Attributes attributes) throws SAXException {
            if (qName.equalsIgnoreCase("entry")) { //$NON-NLS-1$
                String permissions = attributes.getValue("", "permissions"); //$NON-NLS-1$ //$NON-NLS-2$
                IPermissionRecipient permRecipient = null;
                String user = attributes.getValue("", "user"); //$NON-NLS-1$ //$NON-NLS-2$
                if (null != user) {
                    permRecipient = new SimpleUser(user);
                } else {
                    permRecipient = new SimpleRole(attributes.getValue("", "role")); //$NON-NLS-1$ //$NON-NLS-2$
                }
                this.acl.put(permRecipient, new SimplePermissionMask(Integer.parseInt(permissions)));
            }
        }
    }

    /**
     * Sets the ACL Xml for a particular file in the solution repository
     * 
     * @param solution
     * @param path
     * @param filename
     * @param strAclXml
     * @param userSession
     */
    public void setAcl(final String solution, final String path, final String filename, final String strAclXml,
            final IPentahoSession userSession)
            throws SolutionRepositoryServiceException, IOException, PentahoAccessControlException {

        if (StringUtil.doesPathContainParentPathSegment(solution)
                || StringUtil.doesPathContainParentPathSegment(path)) {
            String msg = Messages.getString("AdhocWebService.ERROR_0008_MISSING_OR_INVALID_REPORT_NAME"); //$NON-NLS-1$
            throw new SolutionRepositoryServiceException(msg);
        }

        ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, userSession);
        String fullPath = ActionInfo.buildSolutionPath(solution, path, filename);
        ISolutionFile solutionFile = repository.getSolutionFile(fullPath, ISolutionRepository.ACTION_SHARE);

        // ouch, i hate instanceof
        if (solutionFile instanceof IAclSolutionFile) {
            Map<IPermissionRecipient, IPermissionMask> acl;
            try {
                acl = createAclFromXml(strAclXml);

                // TODO sbarkdull, fix these really really lame exception msgs
            } catch (ParserConfigurationException e) {
                throw new SolutionRepositoryServiceException("ParserConfigurationException", e); //$NON-NLS-1$
            } catch (SAXException e) {
                throw new SolutionRepositoryServiceException("SAXException", e); //$NON-NLS-1$
            } catch (IOException e) {
                throw new SolutionRepositoryServiceException("IOException", e); //$NON-NLS-1$
            }
            repository.setPermissions(solutionFile, acl);
        }
        // TODO sbarkdull, what if its not instanceof
    }

    /**
     * Gets ACLs based on a solution repository file.
     * 
     * @param userSession
     * 
     * @return acl xml
     */
    public String getAclXml(final String solution, final String path, final String filename,
            final IPentahoSession userSession) throws SolutionRepositoryServiceException, IOException {
        if (StringUtil.doesPathContainParentPathSegment(solution)
                || StringUtil.doesPathContainParentPathSegment(path)) {
            String msg = Messages.getString("AdhocWebService.ERROR_0008_MISSING_OR_INVALID_REPORT_NAME"); //$NON-NLS-1$
            throw new SolutionRepositoryServiceException(msg);
        }

        ISolutionRepository repository = PentahoSystem.get(ISolutionRepository.class, userSession);
        String fullPath = ActionInfo.buildSolutionPath(solution, path, filename);
        ISolutionFile solutionFile = repository.getSolutionFile(fullPath, ISolutionRepository.ACTION_EXECUTE);

        String strXml = null;
        // ouch, i hate instanceof
        if (solutionFile instanceof IAclSolutionFile) {
            Map<IPermissionRecipient, IPermissionMask> filePermissions = repository.getPermissions((solutionFile));
            strXml = getAclAsXml(filePermissions);
        } else {
            strXml = "<acl notsupported='true'/>"; //$NON-NLS-1$
        }
        return strXml;
    }

    // TODO sbarkdull, this method belongs in an AclUtils class?
    // turn acl into an XML representation, and return the document.
    // probably belongs in the SecurityHelper class, but does this class still exist?
    private String getAclAsXml(final Map<IPermissionRecipient, IPermissionMask> filePermissions) {
        StringBuffer sb = new StringBuffer(XmlHelper.createXmlProcessingInstruction(RESPONSE_DOCUMENT_VERSION_NUM,
                RESPONSE_DOCUMENT_ENCODING));

        sb.append("<acl>"); //$NON-NLS-1$
        for (Map.Entry<IPermissionRecipient, IPermissionMask> filePerm : filePermissions.entrySet()) {
            IPermissionRecipient permRecipient = filePerm.getKey();
            if (permRecipient instanceof SimpleRole) {
                sb.append("<entry role='" + permRecipient.getName() + "' permissions='" //$NON-NLS-1$//$NON-NLS-2$
                        + filePerm.getValue().getMask() + "'/>"); //$NON-NLS-1$
            } else {
                // entry belongs to a user
                sb.append("<entry user='" + permRecipient.getName() + "' permissions='" //$NON-NLS-1$//$NON-NLS-2$
                        + filePerm.getValue().getMask() + "'/>"); //$NON-NLS-1$
            }
        }
        sb.append("</acl>"); //$NON-NLS-1$
        return sb.toString();
    }

    /**
     * Get a SAX Parser Factory
     * 
     * NOTE: Need sax parser factory per thread for thread safety. See: http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/parsers/SAXParserFactory.html
     * 
     * @return
     */
    protected static SAXParserFactory getSAXParserFactory() {
        SAXParserFactory threadLocalSAXParserFactory = SAX_FACTORY.get();
        if (null == threadLocalSAXParserFactory) {
            threadLocalSAXParserFactory = SAXParserFactory.newInstance();
            SAX_FACTORY.set(threadLocalSAXParserFactory);
        }
        return threadLocalSAXParserFactory;
    }

    /**
     * This class is basically a wrapper for the solution file inputstream, but it will only
     * pay the price *IF* we need to actually open the inputstream.  This has a huge performance
     * benefit (9s down to 3s).
     */
    protected class PluginFileInputStream extends InputStream {

        private ISolutionRepository repository;
        private ISolutionFile file;
        private InputStream inputStream;
        private final String relativePath;

        public PluginFileInputStream(ISolutionRepository repository, ISolutionFile file) {
            this.repository = repository;
            this.file = file;
            relativePath = file.getSolutionPath() + "/" + file.getFileName(); //$NON-NLS-1$
        }

        public int read() throws IOException {
            if (inputStream == null) {
                inputStream = repository.getResourceInputStream(relativePath, true,
                        ISolutionRepository.ACTION_EXECUTE);
            }
            return inputStream.read();
        }

        public int read(byte[] b) throws IOException {
            if (inputStream == null) {
                inputStream = repository.getResourceInputStream(relativePath, true,
                        ISolutionRepository.ACTION_EXECUTE);
            }
            return inputStream.read(b);
        }

        public int read(byte[] b, int off, int len) throws IOException {
            if (inputStream == null) {
                inputStream = repository.getResourceInputStream(relativePath, true,
                        ISolutionRepository.ACTION_EXECUTE);
            }
            return inputStream.read(b, off, len);
        }

        public synchronized void mark(int readlimit) {
            if (inputStream != null) {
                inputStream.mark(readlimit);
            }
        }

        public boolean markSupported() {
            if (inputStream != null) {
                return inputStream.markSupported();
            }
            return super.markSupported();
        }

        public synchronized void reset() throws IOException {
            if (inputStream != null) {
                inputStream.reset();
            }
            super.reset();
        }

        public long skip(long n) throws IOException {
            if (inputStream != null) {
                inputStream.skip(n);
            }
            return super.skip(n);
        }

        public void close() throws IOException {
            if (inputStream != null) {
                inputStream.close();
            }
        }

        public int available() throws IOException {
            if (inputStream != null) {
                return inputStream.available();
            }
            return 0;
        }

    }

}