eu.openanalytics.rsb.component.DataDirectoriesResource.java Source code

Java tutorial

Introduction

Here is the source code for eu.openanalytics.rsb.component.DataDirectoriesResource.java

Source

/*
 *   R Service Bus
 *   
 *   Copyright (c) Copyright of Open Analytics NV, 2010-2015
 *
 *   ===========================================================================
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   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 Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.openanalytics.rsb.component;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import eu.openanalytics.rsb.Constants;
import eu.openanalytics.rsb.Util;
import eu.openanalytics.rsb.rest.types.Directory;
import eu.openanalytics.rsb.rest.types.FileType;

/**
 * @author "OpenAnalytics &lt;rsb.development@openanalytics.eu&gt;"
 */
@Component("dataDirectoriesResource")
@Path("/" + Constants.DATA_DIR_PATH)
// Produces "regular" XML/JSON content types as browsers don't understand subtypes
// correctly
@Produces({ Constants.JSON_CONTENT_TYPE, Constants.XML_CONTENT_TYPE, Constants.RSB_XML_CONTENT_TYPE,
        Constants.RSB_JSON_CONTENT_TYPE })
public class DataDirectoriesResource extends AbstractResource {
    private final Map<String, File> rootMap = new HashMap<String, File>();

    // exposed for unit testing
    Map<String, File> getRootMap() {
        return rootMap;
    }

    @PostConstruct
    public void setupRootMap() throws IOException {
        final List<File> dataDirectoryRoots = getConfiguration().getDataDirectories();
        if (dataDirectoryRoots != null) {
            for (final File dataDirectoryRoot : dataDirectoryRoots) {
                final String rootKey = Base64.encodeBase64URLSafeString(
                        DigestUtils.md5Digest(dataDirectoryRoot.getCanonicalPath().getBytes()));
                rootMap.put(rootKey, dataDirectoryRoot);
            }
        }
    }

    @Path("/")
    @GET
    public Directory browseRoots(@Context final HttpHeaders httpHeaders, @Context final UriInfo uriInfo)
            throws URISyntaxException, IOException {
        final Directory roots = Util.REST_OBJECT_FACTORY.createDirectory();
        roots.setPath("/");
        roots.setName("Remote Data");
        roots.setUri(Util.buildDataDirectoryUri(httpHeaders, uriInfo, "/").toString());
        roots.setEmpty(rootMap.isEmpty());

        for (final Entry<String, File> rootEntry : rootMap.entrySet()) {
            final Directory root = Util.REST_OBJECT_FACTORY.createDirectory();
            final File rootDirectory = rootEntry.getValue();
            root.setPath(rootDirectory.getCanonicalPath());
            root.setName(rootDirectory.getName());
            root.setUri(Util.buildDataDirectoryUri(httpHeaders, uriInfo, rootEntry.getKey()).toString());
            root.setEmpty(isDirectoryEmpty(rootDirectory));
            roots.getDirectories().add(root);
        }

        return roots;
    }

    @Path("/{rootId}{b64extension : (/b64extension)?}")
    @GET
    public Directory browsePath(@PathParam("rootId") final String rootId,
            @PathParam("b64extension") final String b64extension, @Context final HttpHeaders httpHeaders,
            @Context final UriInfo uriInfo) throws URISyntaxException, IOException {

        final File rootDataDir = rootMap.get(rootId);
        if (rootDataDir == null) {
            throw new NotFoundException(new RuntimeException("No root data dir configured"));
        }

        final String extension = (b64extension != null ? new String(Base64.decodeBase64(b64extension)) : "");
        final File targetDataDir = new File(rootDataDir, extension);

        if (!targetDataDir.exists()) {
            throw new NotFoundException(new RuntimeException("Invalid root data dir: " + targetDataDir));
        }

        // ensure the target data dir is below the root dir to prevent tampering
        final String rootDataDirCanonicalPath = rootDataDir.getCanonicalPath();
        final String targetDataDirCanonicalPath = targetDataDir.getCanonicalPath();
        if (!StringUtils.startsWith(targetDataDirCanonicalPath, rootDataDirCanonicalPath)) {
            throw new AccessDeniedException("Target data dir: " + targetDataDirCanonicalPath
                    + " is not below root dir: " + rootDataDirCanonicalPath);
        }

        final Directory result = Util.REST_OBJECT_FACTORY.createDirectory();
        result.setPath(rootDataDirCanonicalPath + extension);
        result.setName(targetDataDir.getName());
        result.setUri(Util.buildDataDirectoryUri(httpHeaders, uriInfo, rootId, b64extension).toString());

        final File[] targetDataDirFiles = targetDataDir.listFiles();
        result.setEmpty(targetDataDirFiles.length == 0);

        for (final File child : targetDataDirFiles) {
            if (FileUtils.isSymlink(child)) {
                getLogger().warn("Symlinks are not supported: " + child);
            } else if (child.isFile()) {
                final FileType fileType = Util.REST_OBJECT_FACTORY.createFileType();
                fileType.setPath(child.getCanonicalPath());
                fileType.setName(child.getName());
                result.getFiles().add(fileType);
            } else if (child.isDirectory()) {
                final Directory childDir = Util.REST_OBJECT_FACTORY.createDirectory();
                childDir.setPath(child.getCanonicalPath());
                childDir.setName(child.getName());
                final String childB64extension = Base64.encodeBase64URLSafeString(
                        StringUtils.difference(rootDataDirCanonicalPath, child.getCanonicalPath()).getBytes());
                childDir.setUri(
                        Util.buildDataDirectoryUri(httpHeaders, uriInfo, rootId, childB64extension).toString());
                childDir.setEmpty(isDirectoryEmpty(child));
                result.getDirectories().add(childDir);
            } else {
                getLogger().warn("Unsupported file type: " + child);
            }
        }

        return result;
    }

    private boolean isDirectoryEmpty(final File file) {
        if (!file.isDirectory()) {
            return true;
        }
        final File[] childFiles = file.listFiles();
        return childFiles == null ? true : childFiles.length == 0;
    }
}