All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.staticfiles.impl.StaticFileServiceImpl Maven / Gradle / Ivy

/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License
 * at:
 *
 *   http://opensource.org/licenses/ecl2.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package org.opencastproject.staticfiles.impl;

import static java.lang.String.format;
import static org.opencastproject.util.RequireUtil.notNull;

import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.OrganizationDirectoryService;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.staticfiles.api.StaticFileService;
import org.opencastproject.staticfiles.jmx.UploadStatistics;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.OsgiUtil;
import org.opencastproject.util.ProgressInputStream;
import org.opencastproject.util.jmx.JmxUtil;

import com.google.common.util.concurrent.AbstractScheduledService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Service.Listener;
import com.google.common.util.concurrent.Service.State;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.ComponentException;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.management.ObjectInstance;

/**
 * Stores and retrieves static file resources.
 */
@Component(
    immediate = true,
    service = StaticFileService.class,
    property = {
        "service.description=Static File Service",
        "service.PID=org.opencastproject.staticfiles.impl.StaticFileServiceImpl"
    }
)
public class StaticFileServiceImpl implements StaticFileService {

  /** The logger */
  private static final Logger logger = LoggerFactory.getLogger(StaticFileServiceImpl.class);

  /** The key to find the root directory for the static file service in the OSGi properties. */
  public static final String STATICFILES_ROOT_DIRECTORY_KEY = "org.opencastproject.staticfiles.rootdir";

  /** The JMX business object for uploaded statistics */
  private UploadStatistics staticFileStatistics = new UploadStatistics();

  /** The JMX bean object instance */
  private ObjectInstance registerMXBean;

  // OSGi service references
  private SecurityService securityService = null;
  private OrganizationDirectoryService orgDirectory = null;

  /** The root directory for storing static files. */
  private String rootDirPath;

  private PurgeTemporaryStorageService purgeService;

  /**
   * OSGI callback for activating this component
   *
   * @param cc
   *          the osgi component context
   */
  @Activate
  public void activate(ComponentContext cc) {
    logger.info("Upload Static Resource Service started.");
    registerMXBean = JmxUtil.registerMXBean(staticFileStatistics, "UploadStatistics");
    rootDirPath = OsgiUtil.getContextProperty(cc, STATICFILES_ROOT_DIRECTORY_KEY);

    final File rootFile = new File(rootDirPath);
    if (!rootFile.exists()) {
      try {
        FileUtils.forceMkdir(rootFile);
      } catch (IOException e) {
        throw new ComponentException(
                String.format("%s does not exists and could not be created", rootFile.getAbsolutePath()));
      }
    }
    if (!rootFile.canRead()) {
      throw new ComponentException(String.format("Cannot read from %s", rootFile.getAbsolutePath()));
    }

    purgeService = new PurgeTemporaryStorageService();
    purgeService.addListener(new Listener() {
      @Override
      public void failed(State from, Throwable failure) {
        logger.warn("Temporary storage purging service failed:", failure);
      }
    }, MoreExecutors.directExecutor());
    purgeService.startAsync();
    logger.info("Purging of temporary storage section scheduled");
  }

  /**
   * Callback from OSGi on service deactivation.
   */
  @Deactivate
  public void deactivate() {
    JmxUtil.unregisterMXBean(registerMXBean);

    purgeService.stopAsync();
    purgeService = null;
  }

  /** OSGi DI */
  @Reference
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  /** OSGi DI */
  @Reference
  public void setOrganizationDirectoryService(OrganizationDirectoryService directoryService) {
    orgDirectory = directoryService;
  }

  @Override
  public String storeFile(String filename, InputStream inputStream) throws IOException {
    notNull(filename, "filename");
    notNull(inputStream, "inputStream");
    final String uuid = UUID.randomUUID().toString();
    final String org = securityService.getOrganization().getId();

    Path file = getTemporaryStorageDir(org).resolve(Paths.get(uuid, filename));
    try (ProgressInputStream progressInputStream = new ProgressInputStream(inputStream)) {
      progressInputStream.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
          long totalNumBytesRead = (Long) evt.getNewValue();
          long oldTotalNumBytesRead = (Long) evt.getOldValue();
          staticFileStatistics.add(totalNumBytesRead - oldTotalNumBytesRead);
        }
      });

      Files.createDirectories(file.getParent());
      Files.copy(progressInputStream, file);
    } catch (IOException e) {
      logger.error("Unable to save file '{}' to {}", filename, file, e);
      throw e;
    }

    return uuid;
  }

  @Override
  public InputStream getFile(final String uuid) throws NotFoundException, IOException {
    if (StringUtils.isBlank(uuid)) {
      throw new IllegalArgumentException("The uuid must not be blank");
    }

    final String org = securityService.getOrganization().getId();

    return Files.newInputStream(getFile(org, uuid));
  }

  @Override
  public void persistFile(final String uuid) throws NotFoundException, IOException {
    final String org = securityService.getOrganization().getId();
    try (DirectoryStream folders = Files.newDirectoryStream(getTemporaryStorageDir(org),
            getDirsEqualsUuidFilter(uuid))) {
      for (Path folder : folders) {
        Files.move(folder, getDurableStorageDir(org).resolve(folder.getFileName()));
      }
    }
  }

  @Override
  public void deleteFile(String uuid) throws NotFoundException, IOException {
    final String org = securityService.getOrganization().getId();
    Path file = getFile(org, uuid);
    Files.deleteIfExists(file);
  }

  @Override
  public String getFileName(String uuid) throws NotFoundException {
    final String org = securityService.getOrganization().getId();
    try {
      Path file = getFile(org, uuid);
      return file.getFileName().toString();
    } catch (IOException e) {
      logger.warn("Error while reading file:", e);
      throw new NotFoundException(e);
    }
  }

  @Override
  public Long getContentLength(String uuid) throws NotFoundException {
    final String org = securityService.getOrganization().getId();
    try {
      Path file = getFile(org, uuid);
      return Files.size(file);
    } catch (IOException e) {
      logger.warn("Error while reading file:", e);
      throw new NotFoundException(e);
    }
  }

  /**
   * Returns a {@link DirectoryStream.Filter} to filter the entries of a directory and only return items which filename
   * starts with the UUID.
   *
   * @param uuid
   *          The UUID to filter by
   * @return the filter
   */
  private static DirectoryStream.Filter getDirsEqualsUuidFilter(final String uuid) {
    return new DirectoryStream.Filter() {
      @Override
      public boolean accept(Path entry) throws IOException {
        return Files.isDirectory(entry) && entry.getFileName().toString().equals(uuid);
      }
    };
  };

  /**
   * Returns the temporary storage directory for an organization.
   *
   * @param org
   *          The organization
   * @return Path to the temporary storage directory
   */
  private Path getTemporaryStorageDir(final String org) {
    return Paths.get(rootDirPath, org, "temp");
  }

  private Path getDurableStorageDir(final String org) {
    return Paths.get(rootDirPath, org);
  }

  private Path getFile(final String org, final String uuid) throws NotFoundException, IOException {
    // First check if the file is part of the durable storage section
    try (DirectoryStream dirs = Files.newDirectoryStream(getDurableStorageDir(org),
            getDirsEqualsUuidFilter(uuid))) {
      for (Path dir : dirs) {
        try (DirectoryStream files = Files.newDirectoryStream(dir)) {
          for (Path file : files) {
            return file;
          }
        }
      }
    }

    // Second check if the file is part of the temporary storage section
    try (DirectoryStream dirs = Files.newDirectoryStream(getTemporaryStorageDir(org),
            getDirsEqualsUuidFilter(uuid))) {
      for (Path dir : dirs) {
        try (DirectoryStream files = Files.newDirectoryStream(dir)) {
          for (Path file : files) {
            return file;
          }
        }
      }
    }

    throw new NotFoundException(format("No file with UUID '%s' found.", uuid));
  }

  /**
   * Deletes all files found in the temporary storage section of an organization.
   *
   * @param org
   *          The organization identifier
   * @throws IOException
   *           if there was an error while deleting the files.
   */
  void purgeTemporaryStorageSection(final String org, final long lifetime) throws IOException {
    logger.debug("Purge temporary storage section of organization '{}'", org);
    final Path temporaryStorageDir = getTemporaryStorageDir(org);
    if (Files.exists(temporaryStorageDir)) {
      try (DirectoryStream tempFilesStream = Files.newDirectoryStream(temporaryStorageDir,
          new DirectoryStream.Filter() {
            @Override
            public boolean accept(Path path) throws IOException {
              return (Files.getLastModifiedTime(path).toMillis() < (new Date()).getTime() - lifetime);
            }
          })) {
        for (Path file : tempFilesStream) {
          FileUtils.deleteQuietly(file.toFile());
        }
      }
    }
  }

  /**
   * Deletes all files found in the temporary storage section of all known organizations.
   *
   * @throws IOException
   *           if there was an error while deleting the files.
   */
  void purgeTemporaryStorageSection() throws IOException {
    logger.info("Start purging temporary storage section of all known organizations");
    for (Organization org : orgDirectory.getOrganizations()) {
      purgeTemporaryStorageSection(org.getId(), TimeUnit.DAYS.toMillis(1));
    }
  }

  /** Scheduled service for purging temporary storage sections. */
  private class PurgeTemporaryStorageService extends AbstractScheduledService {

    @Override
    protected void runOneIteration() throws Exception {
      StaticFileServiceImpl.this.purgeTemporaryStorageSection();
    }

    @Override
    protected Scheduler scheduler() {
      return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.HOURS);
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy